1598 lines
43 KiB
C
1598 lines
43 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2023 KY
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
#include <linux/genalloc.h>
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
#include <linux/time.h>
|
|
#include <linux/timex.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/page.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/namei.h>
|
|
|
|
#define KY_DBG_BUFF_SIZE (128*1024)
|
|
struct ky_dbg_buf {
|
|
char *pStart;
|
|
char *pEnd;
|
|
char *pRdPtr;
|
|
char *pWrPtr;
|
|
int uBufSize;
|
|
int uDataSize;
|
|
int type;
|
|
int mode;
|
|
int malloc_flag;
|
|
wait_queue_head_t data;
|
|
struct mutex lock;
|
|
struct task_struct *daemon;
|
|
struct snd_pcm_runtime *runtime;
|
|
};
|
|
struct dentry *g_debug_file;
|
|
static char dump_file_dir[64] = "/tmp";
|
|
static unsigned int dump_id = 0;
|
|
|
|
#define KY_CODEC_TYPE 0
|
|
#define KY_HDMI_TYPE 1
|
|
|
|
#define KY_PLAY_MODE 0
|
|
#define KY_CAPT_MODE 1
|
|
|
|
#define OUTPUT_BUFFER_SIZE 256
|
|
#endif
|
|
|
|
#define DRV_NAME "ky-snd-dma"
|
|
|
|
#define I2S_PERIOD_SIZE 1024
|
|
#define I2S_PERIOD_COUNT 4
|
|
#define I2S0_REG_BASE 0xD4026000
|
|
#define I2S1_REG_BASE 0xD4026800
|
|
#define DATAR 0x10 /* SSP Data Register */
|
|
|
|
#define DMA_I2S0 0
|
|
#define DMA_I2S1 1
|
|
#define DMA_HDMI 2
|
|
|
|
#define HDMI_REFORMAT_ENABLE
|
|
#define I2S_HDMI_REG_BASE 0xC0883900
|
|
#define HDMI_TXDATA 0x80
|
|
#define HDMI_PERIOD_SIZE 480
|
|
|
|
#define SAMPLE_PRESENT_FLAG_OFFSET 31
|
|
#define AUDIO_FRAME_START_BIT_OFFSET 30
|
|
#define PARITY_BIT_OFFSET 27
|
|
#define CHANNEL_STATUS_OFFSET 26
|
|
#define VALID_OFFSET 24
|
|
|
|
#define CS_CTRL1 ((1 << SAMPLE_PRESENT_FLAG_OFFSET) | (1 << AUDIO_FRAME_START_BIT_OFFSET))
|
|
#define CS_CTRL2 ((1 << SAMPLE_PRESENT_FLAG_OFFSET) | (0 << AUDIO_FRAME_START_BIT_OFFSET))
|
|
|
|
#define CS_SAMPLING_FREQUENCY 25
|
|
#define CS_MAX_SAMPLE_WORD 32
|
|
|
|
#define P2(n) n, n^1, n^1, n
|
|
#define P4(n) P2(n), P2(n^1), P2(n^1), P2(n)
|
|
#define P6(n) P4(n), P4(n^1), P4(n^1), P4(n)
|
|
|
|
struct ky_snd_dmadata {
|
|
struct dma_chan *dma_chan;
|
|
unsigned int dma_id;
|
|
dma_cookie_t cookie;
|
|
spinlock_t dma_lock;
|
|
void *private_data;
|
|
|
|
int stream;
|
|
/*DOMAIN:config from userspace*/
|
|
struct snd_pcm_substream *substream;
|
|
unsigned long pos;
|
|
bool playback_data;
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
int play_flag;
|
|
int capt_flag;
|
|
int debug_flag;
|
|
struct ky_dbg_buf play_buf;
|
|
struct ky_dbg_buf capt_buf;
|
|
#endif
|
|
};
|
|
|
|
struct ky_snd_soc_device {
|
|
struct ky_snd_dmadata dmadata[2];
|
|
unsigned long pos;
|
|
bool playback_en;
|
|
bool capture_en;
|
|
spinlock_t lock;
|
|
};
|
|
|
|
struct hdmi_priv {
|
|
dma_addr_t phy_addr;
|
|
void __iomem *buf_base;
|
|
};
|
|
|
|
/* HDMI initalization data */
|
|
struct hdmi_codec_priv {
|
|
uint32_t srate;
|
|
uint32_t channels;
|
|
uint8_t iec_offset;
|
|
};
|
|
|
|
static struct hdmi_codec_priv hdmi_ptr = {0};
|
|
static const bool ParityTable256[256] =
|
|
{
|
|
P6(0), P6(1), P6(1), P6(0)
|
|
};
|
|
static struct hdmi_priv priv;
|
|
static dma_addr_t hdmiraw_dma_addr;
|
|
static dma_addr_t hdmipcm_dma_addr;
|
|
static unsigned char *hdmiraw_dma_area; /* DMA area */
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
static unsigned char *hdmiraw_dma_area_tmp;
|
|
#endif
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
#ifdef CONFIG_ADD_WAV_HEADER
|
|
typedef struct {
|
|
char riffType[4];
|
|
char wavType[4];
|
|
char formatType[4];
|
|
char dataType[4];
|
|
unsigned short audioFormat;
|
|
unsigned short numChannels;
|
|
unsigned short blockAlign;
|
|
unsigned short bitsPerSample;
|
|
unsigned int formatSize;
|
|
unsigned int sampleRate;
|
|
unsigned int bytesPerSecond;
|
|
unsigned int riffSize;
|
|
unsigned int dataSize;
|
|
} head_data_t;
|
|
#endif
|
|
|
|
#if defined CONFIG_KY_PLAY_DEBUG || defined CONFIG_KY_CAPT_DEBUG
|
|
static struct file *try_to_create_pcm_file(char *type, int dma_id)
|
|
{
|
|
char fname[128];
|
|
struct timespec64 ts;
|
|
struct rtc_time tm;
|
|
struct file *filep = NULL;
|
|
|
|
ktime_get_real_ts64(&ts);
|
|
if (ts.tv_nsec)
|
|
ts.tv_sec++;
|
|
ts.tv_sec+=8*60*60;
|
|
rtc_time64_to_tm(ts.tv_sec, &tm);
|
|
|
|
//create file
|
|
memset(fname, 0, sizeof(fname));
|
|
strcat(fname, dump_file_dir);
|
|
if(dma_id < DMA_HDMI) {
|
|
sprintf(fname+strlen(fname), "/dump-i2s%d-%s-%d-%02d-%02d-%02d-%02d-%02d.pcm", dma_id, type,
|
|
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
} else {
|
|
sprintf(fname+strlen(fname), "/dump-hdmi-%s-%d-%02d-%02d-%02d-%02d-%02d.pcm", type,
|
|
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
}
|
|
filep = filp_open(fname, O_RDWR|O_CREAT, 0666);
|
|
if (IS_ERR(filep)) {
|
|
pr_err("filp_open %s error\n", fname);
|
|
return NULL;
|
|
}
|
|
return filep;
|
|
}
|
|
|
|
#ifdef CONFIG_ADD_WAV_HEADER
|
|
static int pcm_add_wave_header(struct file *fp, unsigned int channels,
|
|
unsigned int bits, unsigned int sample_rate,
|
|
unsigned int len)
|
|
{
|
|
loff_t wav_pos = 0;
|
|
ssize_t ret;
|
|
head_data_t wav_header;
|
|
if (NULL == fp) {
|
|
pr_err("Input file ptr is null:%s\n", __func__);
|
|
return -1;
|
|
}
|
|
memcpy(wav_header.riffType, "RIFF", strlen("RIFF"));
|
|
wav_header.riffSize = len - 8;
|
|
memcpy(wav_header.wavType, "WAVE", strlen("WAVE"));
|
|
memcpy(wav_header.formatType, "fmt ", strlen("fmt "));
|
|
wav_header.formatSize = 16;
|
|
wav_header.audioFormat = 1;
|
|
wav_header.numChannels = channels;
|
|
wav_header.sampleRate = sample_rate;
|
|
wav_header.blockAlign = channels * bits / 8;
|
|
wav_header.bitsPerSample = bits;
|
|
wav_header.bytesPerSecond = wav_header.sampleRate * wav_header.blockAlign;
|
|
memcpy(wav_header.dataType, "data", strlen("data"));
|
|
wav_header.dataSize = len - 44;
|
|
ret = kernel_write(fp, &wav_header, 44, &wav_pos);
|
|
if (ret > 0) {
|
|
printk("add wav header size:%ld", ret);
|
|
} else {
|
|
pr_err("kernel_write error:%s,line: %d\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int ky_debug_data_daemon(void *data)
|
|
{
|
|
loff_t pos = 0;
|
|
int ret;
|
|
char *wr_ptr;
|
|
struct file *dump_filep = NULL;
|
|
struct ky_dbg_buf *dbgPara = (struct ky_dbg_buf *)data;
|
|
struct ky_snd_dmadata *dmadata = dbgPara->runtime->private_data;
|
|
|
|
dump_filep = try_to_create_pcm_file(dbgPara->mode == 0 ? "play":"capt", dmadata->dma_id);
|
|
if (IS_ERR(dump_filep)) {
|
|
pr_err("ERR: %s try to create pcm file failed!\n", __func__);
|
|
goto thread_out;
|
|
}
|
|
|
|
/* init the buffer parameter */
|
|
mutex_lock(&dbgPara->lock);
|
|
dbgPara->uBufSize = KY_DBG_BUFF_SIZE;
|
|
dbgPara->pRdPtr = dbgPara->pStart;
|
|
dbgPara->pWrPtr = dbgPara->pStart;
|
|
printk("dbgPara->pStart :%p", dbgPara->pStart);
|
|
dbgPara ->uDataSize = 0;
|
|
dbgPara->pEnd = dbgPara->pStart + KY_DBG_BUFF_SIZE;
|
|
mutex_unlock(&dbgPara->lock);
|
|
|
|
#ifdef CONFIG_ADD_WAV_HEADER
|
|
pos = 44; //wave header offset
|
|
#endif
|
|
|
|
for(;;){
|
|
if (kthread_should_stop()) break;
|
|
while(dbgPara->uDataSize) {
|
|
wr_ptr = dbgPara->pWrPtr;
|
|
if (dbgPara->pRdPtr > wr_ptr) {
|
|
/* write the tail */
|
|
kernel_write(dump_filep, dbgPara->pRdPtr, dbgPara->pEnd - dbgPara->pRdPtr, &pos);
|
|
mutex_lock(&dbgPara->lock);
|
|
dbgPara->uDataSize -= dbgPara->pEnd - dbgPara->pRdPtr;
|
|
if (dbgPara->uDataSize<0)
|
|
dbgPara->uDataSize = 0;
|
|
dbgPara->pRdPtr = dbgPara->pStart;
|
|
mutex_unlock(&dbgPara->lock);
|
|
} else {
|
|
/* write the head */
|
|
kernel_write(dump_filep, dbgPara->pRdPtr, wr_ptr - dbgPara->pRdPtr, &pos);
|
|
mutex_lock(&dbgPara->lock);
|
|
dbgPara->uDataSize -= wr_ptr - dbgPara->pRdPtr;
|
|
if (dbgPara->uDataSize<0)
|
|
dbgPara->uDataSize = 0;
|
|
dbgPara->pRdPtr = (wr_ptr>=dbgPara->pEnd)? dbgPara->pStart : wr_ptr;
|
|
mutex_unlock(&dbgPara->lock);
|
|
}
|
|
}
|
|
|
|
ret = wait_event_interruptible(dbgPara->data, (dbgPara->uDataSize > 0) || kthread_should_stop());
|
|
if (ret < 0) break;
|
|
}
|
|
|
|
thread_out:
|
|
pr_info("thread_out\n");
|
|
pr_info("dump file size:%llu", pos);
|
|
|
|
#ifdef CONFIG_ADD_WAV_HEADER
|
|
pcm_add_wave_header(dump_filep, dbgPara->runtime->channels,
|
|
dbgPara->runtime->sample_bits,
|
|
dbgPara->runtime->rate, pos);
|
|
#endif
|
|
|
|
if (dump_filep) {
|
|
filp_close(dump_filep, NULL);
|
|
}
|
|
if (dbgPara->pStart) {
|
|
vfree(dbgPara->pStart);
|
|
dbgPara->pStart = 0;
|
|
}
|
|
while (!kthread_should_stop()) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
schedule();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ky_debug_data(char *hwbuf, struct ky_dbg_buf *debug_buf,
|
|
int *debug_flag, int *mode_flag,
|
|
struct snd_pcm_runtime *runtime,
|
|
unsigned long bytes, int type, int mode,
|
|
int malloc_flag)
|
|
{
|
|
char thread_name[24];
|
|
struct ky_snd_dmadata *dmadata = runtime->private_data;
|
|
|
|
if (*debug_flag && !(*mode_flag)) {
|
|
/* start task */
|
|
memset(debug_buf, 0, sizeof(*debug_buf));
|
|
debug_buf->type = type;
|
|
debug_buf->mode = mode;
|
|
debug_buf->runtime = runtime;
|
|
debug_buf->malloc_flag = malloc_flag;
|
|
init_waitqueue_head(&debug_buf->data);
|
|
mutex_init(&debug_buf->lock);
|
|
if ((type == KY_CODEC_TYPE) && (mode == KY_PLAY_MODE)) {
|
|
snprintf(thread_name, sizeof(thread_name), "i2s%d-play-dbg", dmadata->dma_id);
|
|
} else if ((type == KY_CODEC_TYPE) && (mode == KY_CAPT_MODE)) {
|
|
snprintf(thread_name, sizeof(thread_name), "i2s%d-capt-dbg", dmadata->dma_id);
|
|
} else if ((type == KY_HDMI_TYPE) && (mode == KY_PLAY_MODE)) {
|
|
snprintf(thread_name, sizeof(thread_name), "hdmi-play-dbg");
|
|
}
|
|
if (thread_name[0] != '\0') {
|
|
debug_buf->daemon = kthread_run(ky_debug_data_daemon, debug_buf, thread_name);
|
|
}
|
|
*mode_flag = 1;
|
|
} else if (!(*debug_flag) && *mode_flag) {
|
|
/* stop task */
|
|
kthread_stop(debug_buf->daemon);
|
|
memset(debug_buf, 0, sizeof(*debug_buf));
|
|
*mode_flag = 0;
|
|
}
|
|
if (!debug_buf->malloc_flag) {
|
|
debug_buf->pStart = vmalloc(KY_DBG_BUFF_SIZE);
|
|
if (!debug_buf->pStart) {
|
|
pr_err("ERR: %s try to vmalloc %d bytes failed!\n", __func__, KY_DBG_BUFF_SIZE);
|
|
} else {
|
|
pr_info("try to vmalloc buffer success\n");
|
|
}
|
|
debug_buf->malloc_flag = 1;
|
|
}
|
|
if (*debug_flag && *mode_flag && debug_buf->pStart && debug_buf->uBufSize) {
|
|
size_t size = bytes;
|
|
if (size > (debug_buf->uBufSize - debug_buf->uDataSize)) {
|
|
size = debug_buf->uBufSize - debug_buf->uDataSize;
|
|
}
|
|
|
|
if (size <= (debug_buf->pEnd - debug_buf->pWrPtr)) {
|
|
memcpy(debug_buf->pWrPtr, hwbuf, size);
|
|
mutex_lock(&debug_buf->lock);
|
|
debug_buf->pWrPtr += size;
|
|
debug_buf->uDataSize += size;
|
|
if (debug_buf->pWrPtr == debug_buf->pEnd)
|
|
debug_buf->pWrPtr = debug_buf->pStart;
|
|
mutex_unlock(&debug_buf->lock);
|
|
} else {
|
|
memcpy(debug_buf->pWrPtr, hwbuf, debug_buf->pEnd - debug_buf->pWrPtr);
|
|
memcpy(debug_buf->pStart, hwbuf+(debug_buf->pEnd - debug_buf->pWrPtr), size-(debug_buf->pEnd - debug_buf->pWrPtr));
|
|
mutex_lock(&debug_buf->lock);
|
|
debug_buf->pWrPtr = debug_buf->pStart + size - (debug_buf->pEnd - debug_buf->pWrPtr);
|
|
debug_buf->uDataSize += size;
|
|
mutex_unlock(&debug_buf->lock);
|
|
}
|
|
/* wakeup the daemon */
|
|
wake_up(&debug_buf->data);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
static int ky_snd_dma_init(struct device *paraent, struct ky_snd_soc_device *dev,
|
|
struct ky_snd_dmadata *dmadata);
|
|
|
|
static const struct snd_pcm_hardware ky_snd_pcm_hardware = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BATCH,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
.rate_min = SNDRV_PCM_RATE_48000,
|
|
.rate_max = SNDRV_PCM_RATE_48000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = I2S_PERIOD_SIZE * I2S_PERIOD_COUNT * 4,
|
|
.period_bytes_min = I2S_PERIOD_SIZE * 4,
|
|
.period_bytes_max = I2S_PERIOD_SIZE * 4,
|
|
.periods_min = I2S_PERIOD_COUNT,
|
|
.periods_max = I2S_PERIOD_COUNT,
|
|
};
|
|
|
|
static const struct snd_pcm_hardware ky_snd_pcm_hardware_hdmi = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BATCH,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
.rate_min = SNDRV_PCM_RATE_48000,
|
|
.rate_max = SNDRV_PCM_RATE_48000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = HDMI_PERIOD_SIZE * 4 * 4,
|
|
.period_bytes_min = HDMI_PERIOD_SIZE * 4,
|
|
.period_bytes_max = HDMI_PERIOD_SIZE * 4,
|
|
.periods_min = 4,
|
|
.periods_max = 4,
|
|
};
|
|
static int ky_dma_slave_config(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct dma_slave_config *slave_config,
|
|
int dma_id)
|
|
{
|
|
int ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
slave_config->dst_maxburst = 32;
|
|
slave_config->src_maxburst = 32;
|
|
|
|
slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
/* for tmda , don't need to config dma controller addr, set to 0*/
|
|
if (dma_id == DMA_I2S0) {
|
|
pr_debug("i2s0_datar\n");
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
slave_config->dst_addr = I2S0_REG_BASE + DATAR;
|
|
slave_config->src_addr = 0;
|
|
}
|
|
else {
|
|
slave_config->src_addr = I2S0_REG_BASE + DATAR;
|
|
slave_config->dst_addr = 0;
|
|
}
|
|
} else if (dma_id == DMA_I2S1) {
|
|
pr_debug("i2s1_datar\n");
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
slave_config->dst_addr = I2S1_REG_BASE + DATAR;
|
|
slave_config->src_addr = 0;
|
|
}
|
|
else {
|
|
slave_config->src_addr = I2S1_REG_BASE + DATAR;
|
|
slave_config->dst_addr = 0;
|
|
}
|
|
} else if (dma_id == DMA_HDMI) {
|
|
pr_debug("i2s_hdmi_datar\n");
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
slave_config->dst_addr = I2S_HDMI_REG_BASE + HDMI_TXDATA;
|
|
slave_config->src_addr = 0;
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
#else
|
|
slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
#endif
|
|
}
|
|
else {
|
|
slave_config->src_addr = I2S_HDMI_REG_BASE + 0x00;
|
|
slave_config->dst_addr = 0;
|
|
}
|
|
}else {
|
|
pr_err("unsupport dma platform\n");
|
|
return -1;
|
|
}
|
|
|
|
pr_debug("leave %s\n", __FUNCTION__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ky_get_stream_is_enable(struct ky_snd_soc_device *dev, int stream)
|
|
{
|
|
bool ret;
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
ret = dev->playback_en;
|
|
else
|
|
ret = dev->capture_en;
|
|
return ret;
|
|
}
|
|
|
|
static void ky_update_stream_status(struct ky_snd_soc_device *dev, int stream, bool enable)
|
|
{
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
dev->playback_en = enable;
|
|
else
|
|
dev->capture_en = enable;
|
|
return;
|
|
}
|
|
|
|
static int ky_snd_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
int ret;
|
|
struct dma_slave_config slave_config;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ky_snd_dmadata *dmadata = runtime->private_data;
|
|
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
struct snd_pcm_substream *substream_tx;
|
|
|
|
struct ky_snd_soc_device *dev = snd_soc_component_get_drvdata(component);
|
|
struct ky_snd_dmadata *txdma = &dev->dmadata[0];
|
|
struct dma_slave_config slave_config_tx;
|
|
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
pr_debug("enter %s!! allocbytes=%d, dmadata=0x%lx\n",
|
|
__FUNCTION__, params_buffer_bytes(params), (unsigned long)dmadata);
|
|
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)) {
|
|
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
|
if (ret < 0)
|
|
goto unlock;
|
|
}
|
|
memset(&slave_config, 0, sizeof(slave_config));
|
|
memset(&slave_config_tx, 0, sizeof(slave_config_tx));
|
|
|
|
if (dmadata->dma_id != DMA_I2S0 && dmadata->dma_id != DMA_I2S1) {
|
|
pr_err("unsupport dma platform\n");
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_CAPTURE
|
|
&& !ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_PLAYBACK)) {
|
|
substream_tx = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
|
dmadata = txdma;
|
|
ky_dma_slave_config(substream_tx, params, &slave_config_tx, dmadata->dma_id);
|
|
slave_config_tx.direction = DMA_MEM_TO_DEV;
|
|
slave_config_tx.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
slave_config_tx.dst_addr = I2S0_REG_BASE + DATAR;
|
|
slave_config_tx.src_addr = 0;
|
|
ret = dmaengine_slave_config(dmadata->dma_chan, &slave_config_tx);
|
|
if (ret)
|
|
goto unlock;
|
|
dmadata->substream = substream_tx;
|
|
dmadata->pos = 0;
|
|
}
|
|
|
|
dmadata = runtime->private_data;
|
|
ky_dma_slave_config(substream, params, &slave_config, dmadata->dma_id);
|
|
|
|
ret = dmaengine_slave_config(dmadata->dma_chan, &slave_config);
|
|
if (ret)
|
|
goto unlock;
|
|
|
|
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
|
if (ret < 0)
|
|
goto unlock;
|
|
|
|
dmadata->substream = substream;
|
|
dmadata->pos = 0;
|
|
|
|
pr_debug("leave %s!!\n", __FUNCTION__);
|
|
unlock:
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
if (ret < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_pcm_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
pr_debug("enter %s!!\n", __FUNCTION__);
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
static int ky_snd_pcm_hdmi_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
//config hdmi and callback
|
|
int ret;
|
|
struct dma_slave_config slave_config;
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ky_snd_dmadata *dmadata = runtime->private_data;
|
|
|
|
pr_debug("enter %s!! allocbytes=%d, dmadata=0x%lx\n",
|
|
__FUNCTION__, params_buffer_bytes(params), (unsigned long)dmadata);
|
|
|
|
memset(&slave_config, 0, sizeof(slave_config));
|
|
if (dmadata->dma_id != DMA_HDMI) {
|
|
pr_err("unsupport adma platform\n");
|
|
return -1;
|
|
}
|
|
ky_dma_slave_config(substream, params, &slave_config, dmadata->dma_id);
|
|
|
|
ret = dmaengine_slave_config(dmadata->dma_chan, &slave_config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
hdmiraw_dma_area = (void *)priv.buf_base;
|
|
hdmiraw_dma_addr = (dma_addr_t)priv.phy_addr;
|
|
|
|
if (hdmiraw_dma_area == NULL) {
|
|
pr_err("hdmi:raw:get mem failed...\n");
|
|
return -ENOMEM;
|
|
}
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
hdmiraw_dma_area_tmp = kzalloc((params_buffer_bytes(params) * 2), GFP_KERNEL);
|
|
if (hdmiraw_dma_area_tmp == NULL) {
|
|
pr_err("hdmi:raw:get mem tmp failed...\n");
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
|
|
hdmipcm_dma_addr = substream->dma_buffer.addr;
|
|
substream->dma_buffer.addr = (dma_addr_t)hdmiraw_dma_addr;
|
|
|
|
dmadata->substream = substream;
|
|
dmadata->pos = 0;
|
|
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
|
|
|
pr_debug("leave %s!!\n", __FUNCTION__);
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_pcm_hdmi_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
pr_debug("enter %s!!\n", __FUNCTION__);
|
|
|
|
substream->dma_buffer.addr = hdmipcm_dma_addr;
|
|
hdmiraw_dma_area = NULL;
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
kfree(hdmiraw_dma_area_tmp);
|
|
#endif
|
|
return snd_pcm_lib_free_pages(substream);
|
|
}
|
|
|
|
static void ky_snd_hdmi_dma_complete(void *arg)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = arg;
|
|
struct snd_pcm_substream *substream = dmadata->substream;
|
|
unsigned int pos = dmadata->pos;
|
|
struct ky_snd_soc_device *dev = (struct ky_snd_soc_device *)dmadata->private_data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
pos += snd_pcm_lib_period_bytes(substream);
|
|
pos %= snd_pcm_lib_buffer_bytes(substream);
|
|
dmadata->pos = pos;
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
if (snd_pcm_running(substream)) {
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void ky_snd_dma_complete(void *arg)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = arg;
|
|
struct snd_pcm_substream *substream = dmadata->substream;
|
|
struct ky_snd_soc_device *dev = (struct ky_snd_soc_device *)dmadata->private_data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)
|
|
&& !ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_PLAYBACK)) {
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
return;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
if (snd_pcm_running(substream)) {
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int ky_snd_dma_submit(struct ky_snd_dmadata *dmadata)
|
|
{
|
|
struct dma_async_tx_descriptor *desc;
|
|
struct snd_pcm_substream *substream = dmadata->substream;
|
|
struct dma_chan *chan = dmadata->dma_chan;
|
|
unsigned long flags = DMA_CTRL_ACK;
|
|
|
|
pr_debug("enter %s!!\n", __FUNCTION__);
|
|
|
|
if (substream->runtime && !substream->runtime->no_period_wakeup)
|
|
flags |= DMA_PREP_INTERRUPT;
|
|
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
if (dmadata->dma_id == DMA_HDMI){
|
|
desc = dmaengine_prep_dma_cyclic(chan,
|
|
substream->runtime->dma_addr,
|
|
snd_pcm_lib_buffer_bytes(substream) * 2,
|
|
snd_pcm_lib_period_bytes(substream) * 2,
|
|
snd_pcm_substream_to_dma_direction(substream),
|
|
flags);
|
|
} else
|
|
#endif
|
|
{
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK){
|
|
desc = dmaengine_prep_dma_cyclic(chan,
|
|
substream->dma_buffer.addr,
|
|
I2S_PERIOD_SIZE * I2S_PERIOD_COUNT * 4,
|
|
I2S_PERIOD_SIZE * 4,
|
|
snd_pcm_substream_to_dma_direction(substream),
|
|
flags);
|
|
}
|
|
else {
|
|
desc = dmaengine_prep_dma_cyclic(chan,
|
|
substream->runtime->dma_addr,
|
|
snd_pcm_lib_buffer_bytes(substream),
|
|
snd_pcm_lib_period_bytes(substream),
|
|
snd_pcm_substream_to_dma_direction(substream),
|
|
flags);
|
|
}
|
|
}
|
|
|
|
if (!desc)
|
|
return -ENOMEM;
|
|
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
if (dmadata->dma_id == DMA_HDMI){
|
|
desc->callback = ky_snd_hdmi_dma_complete;
|
|
} else
|
|
#endif
|
|
{
|
|
desc->callback = ky_snd_dma_complete;
|
|
}
|
|
|
|
desc->callback_param = dmadata;
|
|
dmadata->cookie = dmaengine_submit(desc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_pcm_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = substream->runtime->private_data;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ky_snd_soc_device *dev = snd_soc_component_get_drvdata(component);
|
|
|
|
int ret = 0;
|
|
struct ky_snd_dmadata *txdma = &dev->dmadata[0];
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
pr_debug("pcm_trigger: cmd=%d,dma_id=%d,dir=%d,p=%d,c=%d\n", cmd, dmadata->dma_id, substream->stream,
|
|
dev->playback_en, dev->capture_en);
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_CAPTURE
|
|
&& !ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_PLAYBACK)) {
|
|
dmadata = txdma;
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
memset(dmadata->substream->dma_buffer.area, 0, I2S_PERIOD_SIZE * I2S_PERIOD_COUNT * 4);
|
|
ret = ky_snd_dma_submit(dmadata);
|
|
if (ret < 0)
|
|
goto unlock;
|
|
dma_async_issue_pending(dmadata->dma_chan);
|
|
dmadata->pos = 0;
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
dmaengine_terminate_async(dmadata->dma_chan);
|
|
dmadata->pos = 0;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
dmaengine_pause(dmadata->dma_chan);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
|
|
dmaengine_pause(dmadata->dma_chan);
|
|
else
|
|
dmaengine_terminate_async(dmadata->dma_chan);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
dmaengine_resume(dmadata->dma_chan);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
dmadata = substream->runtime->private_data;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)) {
|
|
dmadata->playback_data = 0;
|
|
dmadata->pos = 0;
|
|
|
|
} else {
|
|
ret = ky_snd_dma_submit(dmadata);
|
|
if (ret < 0)
|
|
goto unlock;
|
|
dma_async_issue_pending(dmadata->dma_chan);
|
|
dmadata->playback_data = 0;
|
|
dmadata->pos = 0;
|
|
}
|
|
ky_update_stream_status(dev, dmadata->stream, true);
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)) {
|
|
dmadata->playback_data = 0;
|
|
dmadata->pos = 0;
|
|
|
|
} else {
|
|
dmaengine_terminate_async(dmadata->dma_chan);
|
|
dmadata->playback_data = 0;
|
|
dmadata->pos = 0;
|
|
}
|
|
ky_update_stream_status(dev, dmadata->stream, false);
|
|
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
dmaengine_pause(dmadata->dma_chan);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
|
|
dmaengine_pause(dmadata->dma_chan);
|
|
else {
|
|
dmaengine_terminate_async(dmadata->dma_chan);
|
|
dmadata->playback_data = 0;
|
|
dmadata->pos = 0;
|
|
ky_update_stream_status(dev, dmadata->stream, false);
|
|
}
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
dmaengine_resume(dmadata->dma_chan);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
unlock:
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
snd_pcm_uframes_t
|
|
ky_snd_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = substream->runtime->private_data;
|
|
struct ky_snd_soc_device *dev = snd_soc_component_get_drvdata(component);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct dma_tx_state state;
|
|
enum dma_status status;
|
|
unsigned int buf_size;
|
|
unsigned int preriod_size;
|
|
unsigned int pos = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
status = dmaengine_tx_status(dmadata->dma_chan, dmadata->cookie, &state);
|
|
if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
|
|
buf_size = I2S_PERIOD_SIZE * I2S_PERIOD_COUNT * 4;
|
|
preriod_size = I2S_PERIOD_SIZE * 4;
|
|
if (state.residue > 0 && state.residue <= buf_size) {
|
|
pos = ((buf_size - state.residue) / preriod_size) * preriod_size;
|
|
}
|
|
runtime->delay = bytes_to_frames(runtime, state.in_flight_bytes);
|
|
}
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_PLAYBACK)) {
|
|
if (dmadata->playback_data == 0 && pos != 0)
|
|
pos = 0;
|
|
else
|
|
dmadata->playback_data = 1;
|
|
}
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
return bytes_to_frames(runtime, pos);
|
|
}
|
|
|
|
static snd_pcm_uframes_t
|
|
ky_snd_pcm_hdmi_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = substream->runtime->private_data;
|
|
return bytes_to_frames(substream->runtime, dmadata->pos);
|
|
}
|
|
|
|
static int ky_snd_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
int ret = 0;
|
|
struct ky_snd_soc_device *dev;
|
|
struct ky_snd_dmadata *dmadata;
|
|
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
|
unsigned long flags;
|
|
|
|
pr_debug("%s enter, rtd->dev=%s,dir=%d\n", __FUNCTION__, dev_name(rtd->dev),substream->stream);
|
|
|
|
if (!component) {
|
|
pr_err("%s!! coundn't find component %s\n", __FUNCTION__, DRV_NAME);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
dev = snd_soc_component_get_drvdata(component);
|
|
if (!dev) {
|
|
pr_err("%s!! get dev error\n", __FUNCTION__);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
dmadata = &dev->dmadata[substream->stream];
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
dmadata->debug_flag = 1;
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
#ifdef CONFIG_KY_PLAY_DEBUG
|
|
dmadata->play_flag = 0;
|
|
#endif
|
|
} else {
|
|
#ifdef CONFIG_KY_CAPT_DEBUG
|
|
dmadata->capt_flag = 0;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (dmadata->dma_id == DMA_HDMI) {
|
|
ret = snd_soc_set_runtime_hwparams(substream, &ky_snd_pcm_hardware_hdmi);
|
|
} else {
|
|
ret = snd_soc_set_runtime_hwparams(substream, &ky_snd_pcm_hardware);
|
|
}
|
|
|
|
if (ret) {
|
|
goto unlock;
|
|
}
|
|
|
|
ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (ret < 0) {
|
|
pr_err("%s!! got serror\n", __FUNCTION__);
|
|
goto unlock;
|
|
}
|
|
substream->runtime->private_data = dmadata;
|
|
|
|
if (dmadata->dma_id == DMA_HDMI) {
|
|
hdmi_ptr.iec_offset = 0;
|
|
hdmi_ptr.srate = 48000;
|
|
}
|
|
unlock:
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
out:
|
|
pr_debug("%s exit dma_id=%d\n", __FUNCTION__, dmadata->dma_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ky_snd_pcm_close(struct snd_soc_component *component, struct snd_pcm_substream *substream)
|
|
{
|
|
struct ky_snd_dmadata *dmadata = substream->runtime->private_data;
|
|
struct dma_chan *chan = dmadata->dma_chan;
|
|
struct ky_snd_soc_device *dev = snd_soc_component_get_drvdata(component);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
pr_debug("%s debug, dir=%d, dma_id=%d\n", __FUNCTION__,substream->stream, dmadata->dma_id);
|
|
if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK
|
|
&& ky_get_stream_is_enable(dev, SNDRV_PCM_STREAM_CAPTURE)) {
|
|
goto unlock;
|
|
}
|
|
dmaengine_terminate_all(chan);
|
|
if (dmadata->dma_id == DMA_HDMI) {
|
|
hdmi_ptr.iec_offset = 0;
|
|
}
|
|
unlock:
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
dmadata->debug_flag = 0;
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
#ifdef CONFIG_KY_PLAY_DEBUG
|
|
if (dmadata->play_flag) {
|
|
/* stop task */
|
|
kthread_stop(dmadata->play_buf.daemon);
|
|
vfree(dmadata->play_buf.pStart);
|
|
memset(&dmadata->play_buf, 0, sizeof(dmadata->play_buf));
|
|
dmadata->play_flag = 0;
|
|
}
|
|
#endif
|
|
} else {
|
|
#ifdef CONFIG_KY_CAPT_DEBUG
|
|
if (dmadata->capt_flag) {
|
|
kthread_stop(dmadata->capt_buf.daemon);
|
|
memset(&dmadata->capt_buf, 0, sizeof(dmadata->capt_buf));
|
|
dmadata->capt_flag = 0;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_pcm_lib_ioctl(struct snd_soc_component *component, struct snd_pcm_substream *substream,
|
|
unsigned int cmd, void *arg)
|
|
{
|
|
return snd_pcm_lib_ioctl(substream, cmd, arg);
|
|
}
|
|
|
|
static const char * const ky_pcm_dma_channel_names[] = {
|
|
[SNDRV_PCM_STREAM_PLAYBACK] = "tx",
|
|
[SNDRV_PCM_STREAM_CAPTURE] = "rx",
|
|
};
|
|
|
|
static int ky_snd_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
int i;
|
|
int ret;
|
|
int chan_num;
|
|
|
|
struct ky_snd_soc_device *dev;
|
|
struct snd_card *card = rtd->card->snd_card;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
|
|
pr_debug("%s enter, dev=%s\n", __FUNCTION__, dev_name(rtd->dev));
|
|
|
|
if (!component) {
|
|
pr_err("%s: coundn't find component %s\n", __FUNCTION__, DRV_NAME);
|
|
return -1;
|
|
}
|
|
|
|
dev = snd_soc_component_get_drvdata(component);
|
|
if (!dev) {
|
|
pr_err("%s: get dev error\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
if (dev->dmadata->dma_id == DMA_HDMI) {
|
|
chan_num = 1;
|
|
pr_debug("%s playback_only, dev=%s\n", __FUNCTION__, dev_name(rtd->dev));
|
|
} else {
|
|
chan_num = 2;
|
|
}
|
|
dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK;
|
|
dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE;
|
|
|
|
for (i = 0; i < chan_num; i++) {
|
|
ret = ky_snd_dma_init(component->dev, dev, &dev->dmadata[i]);
|
|
if (ret)
|
|
goto exit;
|
|
}
|
|
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
|
card->dev, 64 * 1024 * 32, 4 * 1024 * 1024);
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
for (i = 0; i < chan_num; i++) {
|
|
if (dev->dmadata[i].dma_chan)
|
|
dma_release_channel(dev->dmadata[i].dma_chan);
|
|
dev->dmadata[i].dma_chan = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ky_snd_dma_init(struct device *paraent, struct ky_snd_soc_device *dev,
|
|
struct ky_snd_dmadata *dmadata)
|
|
{
|
|
dma_cap_mask_t mask;
|
|
spin_lock_init(&dmadata->dma_lock);
|
|
|
|
dma_cap_zero(mask);
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
dma_cap_set(DMA_CYCLIC, mask);
|
|
|
|
dmadata->dma_chan = dma_request_slave_channel(paraent, ky_pcm_dma_channel_names[dmadata->stream]);
|
|
if (!dmadata->dma_chan) {
|
|
pr_err("DMA channel for %s is not available\n",
|
|
dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
"playback" : "capture");
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_pcm_probe(struct snd_soc_component *component)
|
|
{
|
|
struct ky_snd_soc_device *ky_snd_device = snd_soc_component_get_drvdata(component);
|
|
|
|
ky_snd_device->dmadata[0].private_data = ky_snd_device;
|
|
ky_snd_device->dmadata[1].private_data = ky_snd_device;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ky_snd_pcm_remove(struct snd_soc_component *component)
|
|
{
|
|
int i;
|
|
int chan_num;
|
|
struct ky_snd_soc_device *dev = snd_soc_component_get_drvdata(component);
|
|
|
|
pr_debug("%s enter\n", __FUNCTION__);
|
|
|
|
if (dev->dmadata->dma_id == DMA_HDMI) {
|
|
chan_num = 1;
|
|
} else {
|
|
chan_num = 2;
|
|
}
|
|
for (i = 0; i < chan_num; i++) {
|
|
struct ky_snd_dmadata *dmadata = &dev->dmadata[i];
|
|
struct dma_chan *chan = dmadata->dma_chan;
|
|
|
|
if (chan) {
|
|
dma_release_channel(chan);
|
|
}
|
|
dev->dmadata[i].dma_chan = NULL;
|
|
}
|
|
}
|
|
|
|
static uint32_t parity_even(uint32_t sample)
|
|
{
|
|
bool parity = 0;
|
|
sample ^= sample >> 16;
|
|
sample ^= sample >> 8;
|
|
parity = ParityTable256[sample & 0xff];
|
|
if (parity)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int32_t cal_cs_status_48kHz(int32_t offset)
|
|
{
|
|
if ((offset == CS_SAMPLING_FREQUENCY) || (offset == CS_MAX_SAMPLE_WORD))
|
|
{
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void hdmi_reformat(void *dst, void *src, int len)
|
|
{
|
|
uint32_t *dst32 = (uint32_t *)dst;
|
|
uint16_t *src16 = (uint16_t *)src;
|
|
struct hdmi_codec_priv *dw = &hdmi_ptr;
|
|
uint16_t frm_cnt = len;
|
|
uint32_t ctrl;
|
|
uint32_t sample,parity;
|
|
dw->channels = 2;
|
|
while (frm_cnt--) {
|
|
for (int i = 0; i < dw->channels; i++) {
|
|
//bit 0-23
|
|
sample = ((uint32_t)(*src16++) << 8);
|
|
//bit 24
|
|
sample = sample | (1 << VALID_OFFSET);
|
|
//bit 26
|
|
sample = sample | (cal_cs_status_48kHz(dw->iec_offset) << CHANNEL_STATUS_OFFSET);
|
|
//bit 27
|
|
parity = parity_even(sample);
|
|
sample = sample | (parity << PARITY_BIT_OFFSET);
|
|
|
|
//bit 30 31
|
|
if (dw->iec_offset == 0) {
|
|
ctrl = CS_CTRL1;
|
|
} else {
|
|
ctrl = CS_CTRL2;
|
|
}
|
|
sample = sample | ctrl;
|
|
|
|
*dst32++ = sample;
|
|
}
|
|
|
|
dw->iec_offset++;
|
|
if (dw->iec_offset >= 192){
|
|
dw->iec_offset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
static int ky_snd_pcm_copy(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
int channel, unsigned long hwoff,
|
|
struct iov_iter *iter, unsigned long bytes)
|
|
{
|
|
int ret = 0;
|
|
char *hwbuf;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
struct ky_snd_dmadata *dmadata = runtime->private_data;
|
|
#endif
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
hwbuf = runtime->dma_area + hwoff;
|
|
if (copy_from_iter(hwbuf, bytes, iter) != bytes)
|
|
return -EFAULT;
|
|
#ifdef CONFIG_KY_PLAY_DEBUG
|
|
if (dump_id & (1 << (2 * dmadata->dma_id))) {
|
|
ky_debug_data(hwbuf, &dmadata->play_buf, &dmadata->debug_flag, &dmadata->play_flag, runtime,
|
|
bytes, KY_CODEC_TYPE, KY_PLAY_MODE, 0);
|
|
}
|
|
#endif
|
|
|
|
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
hwbuf = runtime->dma_area + hwoff;
|
|
if (copy_to_iter(hwbuf, bytes, iter) != bytes)
|
|
return -EFAULT;
|
|
#ifdef CONFIG_KY_CAPT_DEBUG
|
|
if ((dump_id & (1 << (2 * dmadata->dma_id + 1)))) {
|
|
ky_debug_data(hwbuf, &dmadata->capt_buf, &dmadata->debug_flag, &dmadata->capt_flag, runtime,
|
|
bytes, KY_CODEC_TYPE, KY_CAPT_MODE,0);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int ky_snd_pcm_hdmi_copy(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
int channel, unsigned long hwoff,
|
|
struct iov_iter *iter, unsigned long bytes)
|
|
{
|
|
int ret = 0;
|
|
char *hwbuf;
|
|
char *hdmihw_area;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
struct ky_snd_dmadata *dmadata = runtime->private_data;
|
|
#endif
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
#ifdef HDMI_REFORMAT_ENABLE
|
|
hwbuf = runtime->dma_area + hwoff;
|
|
if (copy_from_iter(hwbuf, bytes, iter) != bytes)
|
|
return -EFAULT;
|
|
hdmihw_area = hdmiraw_dma_area_tmp + 2 * hwoff;
|
|
hdmi_reformat((int *)hdmihw_area, (short *)hwbuf, bytes_to_frames(substream->runtime, bytes));
|
|
memcpy((void *)(hdmiraw_dma_area + 2 * hwoff), (void *)hdmihw_area, bytes * 2);
|
|
#else
|
|
hwbuf = hdmiraw_dma_area + hwoff;
|
|
if (hwbuf == NULL)
|
|
pr_err("%s addr null !!!!!!!!!!!!\n", __func__);
|
|
if (copy_from_iter(hwbuf, bytes, iter) != bytes)
|
|
return -EFAULT;
|
|
#endif
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
#ifdef CONFIG_KY_PLAY_DEBUG
|
|
if (dump_id & (1 << (2 * dmadata->dma_id))) {
|
|
ky_debug_data(hwbuf, &dmadata->play_buf, &dmadata->debug_flag, &dmadata->play_flag, runtime,
|
|
bytes, KY_HDMI_TYPE,KY_PLAY_MODE, 0);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
hwbuf = runtime->dma_area + hwoff;
|
|
if (copy_to_iter(hwbuf, bytes, iter) != bytes)
|
|
return -EFAULT;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const struct snd_soc_component_driver ky_snd_dma_component = {
|
|
.name = DRV_NAME,
|
|
.probe = ky_snd_pcm_probe,
|
|
.remove = ky_snd_pcm_remove,
|
|
.open = ky_snd_pcm_open,
|
|
.close = ky_snd_pcm_close,
|
|
.ioctl = ky_snd_pcm_lib_ioctl,
|
|
.hw_params = ky_snd_pcm_hw_params,
|
|
.hw_free = ky_snd_pcm_hw_free,
|
|
.trigger = ky_snd_pcm_trigger,
|
|
.pointer = ky_snd_pcm_pointer,
|
|
.pcm_construct = ky_snd_pcm_new,
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
.copy = ky_snd_pcm_copy,
|
|
#endif
|
|
};
|
|
|
|
static const struct snd_soc_component_driver ky_snd_dma_component_hdmi = {
|
|
.name = DRV_NAME,
|
|
.probe = ky_snd_pcm_probe,
|
|
.remove = ky_snd_pcm_remove,
|
|
.open = ky_snd_pcm_open,
|
|
.close = ky_snd_pcm_close,
|
|
.ioctl = ky_snd_pcm_lib_ioctl,
|
|
.hw_params = ky_snd_pcm_hdmi_hw_params,
|
|
.hw_free = ky_snd_pcm_hdmi_hw_free,
|
|
.trigger = ky_snd_pcm_trigger,
|
|
.pointer = ky_snd_pcm_hdmi_pointer,
|
|
.pcm_construct = ky_snd_pcm_new,
|
|
.copy = ky_snd_pcm_hdmi_copy,
|
|
};
|
|
|
|
static int ky_snd_dma_pdev_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct resource *res;
|
|
struct ky_snd_soc_device *device;
|
|
|
|
device = devm_kzalloc(&pdev->dev, sizeof(*device), GFP_KERNEL);
|
|
if (!device) {
|
|
pr_err("%s: alloc memory failed\n", __FUNCTION__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pr_debug("%s enter: dev name %s\n", __func__, dev_name(&pdev->dev));
|
|
|
|
if (of_device_is_compatible(np, "ky,ky-snd-dma-hdmi")){
|
|
device->dmadata->dma_id = DMA_HDMI;
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
pr_debug("%s, start=0x%lx, end=0x%lx\n", __FUNCTION__, (unsigned long)res->start, (unsigned long)res->end);
|
|
priv.phy_addr = res->start;
|
|
priv.buf_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(priv.buf_base)) {
|
|
pr_err("%s audio buf alloc failed\n", __FUNCTION__);
|
|
return PTR_ERR(priv.buf_base);
|
|
}
|
|
ret = snd_soc_register_component(&pdev->dev, &ky_snd_dma_component_hdmi, NULL, 0);
|
|
} else {
|
|
if (of_device_is_compatible(np, "ky,ky-snd-dma0")){
|
|
device->dmadata->dma_id = DMA_I2S0;
|
|
} else if (of_device_is_compatible(np, "ky,ky-snd-dma1")) {
|
|
device->dmadata->dma_id = DMA_I2S1;
|
|
}
|
|
ret = snd_soc_register_component(&pdev->dev, &ky_snd_dma_component, NULL, 0);
|
|
}
|
|
spin_lock_init(&device->lock);
|
|
if (ret != 0) {
|
|
dev_err(&pdev->dev, "failed to register DAI\n");
|
|
return ret;
|
|
}
|
|
dev_set_drvdata(&pdev->dev, device);
|
|
return 0;
|
|
}
|
|
|
|
static int ky_snd_dma_pdev_remove(struct platform_device *pdev)
|
|
{
|
|
snd_soc_unregister_component(&pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id ky_snd_dma_ids[] = {
|
|
{ .compatible = "ky,ky-snd-dma0", },
|
|
{ .compatible = "ky,ky-snd-dma1", },
|
|
{ .compatible = "ky,ky-snd-dma-hdmi", },
|
|
{},
|
|
};
|
|
#endif
|
|
|
|
static struct platform_driver ky_snd_dma_pdrv = {
|
|
.driver = {
|
|
.name = "ky-snd-dma",
|
|
.of_match_table = of_match_ptr(ky_snd_dma_ids),
|
|
},
|
|
.probe = ky_snd_dma_pdev_probe,
|
|
.remove = ky_snd_dma_pdev_remove,
|
|
};
|
|
|
|
static void __exit ky_snd_pcm_exit(void)
|
|
{
|
|
platform_driver_unregister(&ky_snd_dma_pdrv);
|
|
}
|
|
module_exit(ky_snd_pcm_exit);
|
|
|
|
static int ky_snd_pcm_init(void)
|
|
{
|
|
return platform_driver_register(&ky_snd_dma_pdrv);
|
|
}
|
|
late_initcall(ky_snd_pcm_init);
|
|
|
|
#ifdef CONFIG_KY_AUDIO_DATA_DEBUG
|
|
|
|
static int check_path_exists(const char *path)
|
|
{
|
|
struct path kp;
|
|
if (kern_path(path, LOOKUP_PARENT, &kp) == 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void print_dump_config(char *buffer, int size)
|
|
{
|
|
int i;
|
|
unsigned int mask = 0x1;
|
|
const char *dma_chans[] = {"I2S0", "I2S1", "HDMI"};
|
|
int remaining_size;
|
|
int playback_enabled, capture_enabled, line_length;
|
|
snprintf(buffer, size,
|
|
"Dump Configuration:\n"
|
|
"Dmahan Playback Capture\n"
|
|
"------- -------- -------\n");
|
|
remaining_size = size - strlen(buffer);
|
|
if (remaining_size <= 0) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dma_chans); i++) {
|
|
playback_enabled = (dump_id & (mask << (2 * i))) ? 1 : 0;
|
|
capture_enabled = (dump_id & (mask << (2 * i + 1))) ? 1 : 0;
|
|
|
|
line_length = snprintf(NULL, 0, "%6s %5d %10d\n", dma_chans[i], playback_enabled, capture_enabled);
|
|
if (remaining_size < line_length + 1) {
|
|
break;
|
|
}
|
|
snprintf(buffer + strlen(buffer), remaining_size, "%6s %5d %10d\n",
|
|
dma_chans[i], playback_enabled, capture_enabled);
|
|
remaining_size -= line_length;
|
|
}
|
|
}
|
|
|
|
static int config_open(struct inode *inode, struct file *filp)
|
|
{
|
|
filp->private_data = inode->i_private;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t config_read(struct file *file, char __user *buffer, size_t count, loff_t *pos)
|
|
{
|
|
char *output = NULL;
|
|
int len, retval;
|
|
size_t alloc_size = OUTPUT_BUFFER_SIZE;
|
|
|
|
output = vmalloc(alloc_size);
|
|
if (!output) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memset(output, 0, alloc_size);
|
|
|
|
print_dump_config(output, alloc_size);
|
|
len = strlen(output);
|
|
|
|
if (*pos >= len) {
|
|
retval = 0;
|
|
goto out_free;
|
|
}
|
|
|
|
if (count > len - *pos)
|
|
count = len - *pos;
|
|
|
|
if (copy_to_user(buffer, output + *pos, count)) {
|
|
retval = -EFAULT;
|
|
goto out_free;
|
|
}
|
|
|
|
*pos += count;
|
|
retval = count;
|
|
|
|
out_free:
|
|
vfree(output);
|
|
return retval;
|
|
}
|
|
|
|
static ssize_t config_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos)
|
|
{
|
|
char input[32];
|
|
char *p_input = input;
|
|
char *param[4];
|
|
char *token;
|
|
char *endptr;
|
|
int param_count = 0;
|
|
unsigned int dma_chan;
|
|
const char *usage_info=
|
|
"Usage: [dma_chan:%%d] [mode:c/p] [enable:1/0] [/path/to/file]\n"
|
|
"dma_chan: 0: I2S0, 1: I2S1, 2: HDMI\n"
|
|
"mode: c: capture, p: playback\n"
|
|
"enable: 1: enable, 0: disable\n"
|
|
"example: echo 0 p 1 /tmp/dump > /sys/kernel/debug/asoc/dump\n"
|
|
"echo help/h > /sys/kernel/debug/asoc/dump\n";
|
|
|
|
if (count > sizeof(input) - 1)
|
|
count = sizeof(input) - 1;
|
|
|
|
if (copy_from_user(input, buffer, count))
|
|
return -EFAULT;
|
|
|
|
input[count-1] = '\0';
|
|
if (strcmp(input, "help") == 0 || strcmp(input, "h") == 0) {
|
|
pr_info("%s", usage_info);
|
|
return count;
|
|
}
|
|
|
|
while ((token = strsep(&p_input, " ")) != NULL) {
|
|
if (param_count >= 4) {
|
|
pr_err("Too many parameters.\n");
|
|
return -EINVAL;
|
|
}
|
|
param[param_count++] = token;
|
|
}
|
|
|
|
if (param_count > 4 || param_count < 3) {
|
|
pr_info("%s", usage_info);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dma_chan = (unsigned int)simple_strtol(param[0], &endptr, 10);
|
|
if (endptr == param[0] || *endptr != '\0') {
|
|
pr_err("Invalid dma_chan, please input a number\n");
|
|
return -EINVAL;
|
|
}
|
|
if (dma_chan < DMA_I2S0 || dma_chan > DMA_HDMI) {
|
|
pr_err("Invalid dma_chan, please input a number between %d and %d\n", DMA_I2S0, DMA_HDMI);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strcmp(param[1], "p") != 0 && strcmp(param[1], "c") != 0) {
|
|
pr_err("Invalid mode: please input 'c' for capture or 'p' for playback\n");
|
|
return -EINVAL;
|
|
} else if (strcmp(param[2], "1") != 0 && strcmp(param[2], "0") != 0) {
|
|
pr_err("Invalid status: please input '1' to enable or '0' to disable\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strcmp(param[1], "p") == 0 && strcmp(param[2], "0") == 0)
|
|
dump_id &= ~(1 << (2 * dma_chan));
|
|
else if (strcmp(param[1], "p") == 0 && strcmp(param[2], "1") == 0)
|
|
dump_id |= (1 << (2 * dma_chan));
|
|
else if (strcmp(param[1], "c") == 0 && strcmp(param[2], "0") == 0)
|
|
dump_id &= ~(1 << (2 * dma_chan + 1));
|
|
else if (strcmp(param[1], "c") == 0 && strcmp(param[2], "1") == 0)
|
|
dump_id |= (1 << (2 * dma_chan + 1));
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (param_count == 4) {
|
|
if (check_path_exists(param[3]) == 0) {
|
|
pr_err("Path does not exist\n");
|
|
return -EINVAL;
|
|
} else {
|
|
strncpy(dump_file_dir, param[3], sizeof(dump_file_dir) - 1);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = config_read,
|
|
.write = config_write,
|
|
.open = config_open,
|
|
};
|
|
|
|
static int __init dump_debugfs_init(void)
|
|
{
|
|
g_debug_file = debugfs_create_file("dump", 0666, snd_soc_debugfs_root, NULL, &fops);
|
|
if (!g_debug_file) {
|
|
pr_err("Failed to create debugfs file\n");
|
|
return -ENOMEM;
|
|
}
|
|
pr_info("Dump debugfs initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
static void __exit dump_debugfs_exit(void)
|
|
{
|
|
debugfs_remove(g_debug_file);
|
|
pr_info("Debugfs removed\n");
|
|
}
|
|
module_init(dump_debugfs_init);
|
|
module_exit(dump_debugfs_exit);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("KY ASoC PCM Platform Driver");
|
|
MODULE_LICENSE("GPL");
|