503 lines
13 KiB
C
503 lines
13 KiB
C
|
/*
|
||
|
* Copyright (C) 2012 BLX Corporation
|
||
|
*
|
||
|
* 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/timer.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/slab.h>
|
||
|
|
||
|
#include <linux/dma-mapping.h>
|
||
|
|
||
|
#include <sound/core.h>
|
||
|
#include <sound/pcm.h>
|
||
|
#include <sound/pcm_params.h>
|
||
|
#include <sound/soc.h>
|
||
|
#include <linux/gsc3280_dma.h>
|
||
|
|
||
|
#include "gsc3280-pcm.h"
|
||
|
struct dma_chan *rx_chan;
|
||
|
struct dma_chan *tx_chan;
|
||
|
|
||
|
enum{
|
||
|
DMA_READY = 0,
|
||
|
};
|
||
|
static int flag = 0;
|
||
|
static int stop = 0;
|
||
|
static int dma_flags = 0;
|
||
|
|
||
|
struct gsc3280_runtime_data {
|
||
|
|
||
|
unsigned long dma_period;
|
||
|
dma_addr_t dma_start;
|
||
|
dma_addr_t dma_pos;
|
||
|
dma_addr_t dma_end;
|
||
|
|
||
|
struct gsc3280_dma_chan *dma;
|
||
|
dma_addr_t fifo_addr;
|
||
|
};
|
||
|
|
||
|
/* identify hardware playback capabilities */
|
||
|
static const struct snd_pcm_hardware gsc3280_pcm_hardware = {
|
||
|
.info = SNDRV_PCM_INFO_MMAP |
|
||
|
SNDRV_PCM_INFO_MMAP_VALID |
|
||
|
SNDRV_PCM_INFO_INTERLEAVED |
|
||
|
SNDRV_PCM_INFO_BLOCK_TRANSFER,
|
||
|
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE,
|
||
|
|
||
|
// .rates = SNDRV_PCM_RATE_8000_48000,
|
||
|
.rates = SNDRV_PCM_RATE_44100,
|
||
|
.channels_min = 2,
|
||
|
.channels_max = 2,
|
||
|
.period_bytes_min = 16,
|
||
|
.period_bytes_max = 1024 * 7,
|
||
|
.periods_min = 2,
|
||
|
.periods_max = 128,
|
||
|
.buffer_bytes_max = (1024 * 7) * 80,
|
||
|
.fifo_size = 16,
|
||
|
};
|
||
|
|
||
|
|
||
|
static void gsc3280_pcm_dma_transfer_done(void *dev_id);
|
||
|
static void gsc3280_pcm_start_transfer(struct gsc3280_runtime_data *prtd,
|
||
|
struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
unsigned long count;
|
||
|
|
||
|
if(stop){
|
||
|
stop = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//dma_cache_wback_inv((unsigned long )(0x80000000 | runtime->dma_addr), prtd->dma_end - prtd->dma_start);
|
||
|
if(prtd->dma_pos >= prtd->dma_end){
|
||
|
prtd->dma_pos = prtd->dma_start;
|
||
|
|
||
|
if(prtd->dma_pos + prtd->dma_period > prtd->dma_end)
|
||
|
count = prtd->dma_end - prtd->dma_pos;
|
||
|
else
|
||
|
count = prtd->dma_period;
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
gsc3280_dma_set_src_addr(tx_chan, prtd->dma_pos);
|
||
|
gsc3280_dma_set_dma_count(tx_chan, count, DMA_TO_DEVICE);
|
||
|
gsc3280_dma_single_start(tx_chan);
|
||
|
}else{
|
||
|
gsc3280_dma_set_dst_addr(rx_chan, prtd->dma_pos);
|
||
|
gsc3280_dma_set_dma_count(rx_chan, count, DMA_FROM_DEVICE);
|
||
|
gsc3280_dma_single_start(rx_chan);
|
||
|
}
|
||
|
}else{
|
||
|
if(prtd->dma_pos + prtd->dma_period > prtd->dma_end)
|
||
|
count = prtd->dma_end - prtd->dma_pos;
|
||
|
else
|
||
|
count = prtd->dma_period;
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
gsc3280_dma_set_src_addr(tx_chan, prtd->dma_pos);
|
||
|
gsc3280_dma_set_dma_count(tx_chan, count, DMA_TO_DEVICE);
|
||
|
gsc3280_dma_single_start(tx_chan);
|
||
|
}else{
|
||
|
gsc3280_dma_set_dst_addr(rx_chan, prtd->dma_pos);
|
||
|
gsc3280_dma_set_dma_count(rx_chan, count, DMA_FROM_DEVICE);
|
||
|
gsc3280_dma_single_start(rx_chan);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
snd_pcm_period_elapsed(substream);
|
||
|
prtd->dma_pos += count;
|
||
|
}
|
||
|
|
||
|
//static void gsc3280_pcm_dma_transfer_done(struct gsc3280_dma_chan *dma, int err, void *dev_id)
|
||
|
|
||
|
static void gsc3280_pcm_dma_transfer_done(void *dev_id)
|
||
|
{
|
||
|
|
||
|
struct snd_pcm_substream *substream = dev_id;
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct gsc3280_runtime_data *prtd = runtime->private_data;
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
gsc3280_dma_single_stop(tx_chan);
|
||
|
}else{
|
||
|
gsc3280_dma_single_stop(rx_chan);
|
||
|
}
|
||
|
|
||
|
|
||
|
gsc3280_pcm_start_transfer(prtd, substream);
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_hw_params(struct snd_pcm_substream *substream,
|
||
|
struct snd_pcm_hw_params *params)
|
||
|
{
|
||
|
/*dyf add*/
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct gsc3280_runtime_data *prtd = runtime->private_data;
|
||
|
// struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||
|
// struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||
|
// struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||
|
int ret = 0;
|
||
|
unsigned int freq,fmt;
|
||
|
|
||
|
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
|
||
|
// ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||
|
if(ret < 0)
|
||
|
return ret;
|
||
|
// ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||
|
if(ret < 0)
|
||
|
return ret;
|
||
|
freq = params_rate(params) * 256;
|
||
|
// ret = snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
|
||
|
if(ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||
|
runtime->dma_bytes = params_buffer_bytes(params);
|
||
|
|
||
|
prtd->dma_period = params_period_bytes(params);
|
||
|
prtd->dma_start = runtime->dma_addr;
|
||
|
prtd->dma_pos = prtd->dma_start;
|
||
|
prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_hw_free(struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
snd_pcm_set_runtime_buffer(substream, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct gsc3280_cyclic_desc *cdesc;
|
||
|
static int gsc3280_pcm_prepare(struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
struct gsc3280_runtime_data *prtd = substream->runtime->private_data;
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
|
||
|
if(!test_bit(DMA_READY,(void *)&dma_flags)){
|
||
|
#if 0
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
cdesc = gsc3280_dma_single_prep(tx_chan, runtime->dma_addr, runtime->period_size, DMA_TO_DEVICE);
|
||
|
cdesc->period_callback = &gsc3280_pcm_dma_transfer_done;
|
||
|
cdesc->period_callback_param = substream;
|
||
|
}else{
|
||
|
printk(KERN_ALERT"in re prepare record dma_flags is 0x%x\n",dma_flags);
|
||
|
cdesc = gsc3280_dma_single_prep(rx_chan, runtime->dma_addr, runtime->period_size, DMA_FROM_DEVICE);
|
||
|
cdesc->period_callback = &gsc3280_pcm_dma_transfer_done;
|
||
|
cdesc->period_callback_param = substream;
|
||
|
}
|
||
|
set_bit(DMA_READY, (void *)&dma_flags);
|
||
|
#endif
|
||
|
}else{
|
||
|
prtd->dma_pos = prtd->dma_start;
|
||
|
flag = 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||
|
{
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct gsc3280_runtime_data *prtd = runtime->private_data;
|
||
|
|
||
|
switch (cmd) {
|
||
|
case SNDRV_PCM_TRIGGER_START:
|
||
|
case SNDRV_PCM_TRIGGER_RESUME:
|
||
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||
|
stop = 0;
|
||
|
gsc3280_pcm_start_transfer(prtd, substream);
|
||
|
break;
|
||
|
case SNDRV_PCM_TRIGGER_STOP:
|
||
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||
|
stop = 1;
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
gsc3280_dma_single_stop(tx_chan);
|
||
|
}else{
|
||
|
gsc3280_dma_single_stop(rx_chan);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
static snd_pcm_uframes_t gsc3280_pcm_pointer(struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
snd_pcm_uframes_t frames;
|
||
|
unsigned long bytes;
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
bytes = gsc3280_dma_get_src_addr(tx_chan);
|
||
|
}else{
|
||
|
bytes = gsc3280_dma_get_dst_addr(rx_chan);
|
||
|
}
|
||
|
|
||
|
bytes -= runtime->dma_addr;
|
||
|
|
||
|
if(bytes){
|
||
|
if(bytes >= runtime->dma_bytes)
|
||
|
//bytes -= runtime->dma_bytes;
|
||
|
bytes = 0;
|
||
|
}
|
||
|
|
||
|
frames = bytes_to_frames(runtime, bytes);
|
||
|
if (frames >= runtime->buffer_size)
|
||
|
frames = 0;
|
||
|
|
||
|
return frames;
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_open(struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct gsc3280_runtime_data *prtd;
|
||
|
|
||
|
stop = 0;
|
||
|
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
|
||
|
if (prtd == NULL)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
snd_soc_set_runtime_hwparams(substream, &gsc3280_pcm_hardware);
|
||
|
|
||
|
runtime->private_data = prtd;
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
cdesc = gsc3280_dma_single_prep(tx_chan, runtime->dma_addr, runtime->period_size, DMA_TO_DEVICE);
|
||
|
cdesc->period_callback = &gsc3280_pcm_dma_transfer_done;
|
||
|
cdesc->period_callback_param = substream;
|
||
|
}else{
|
||
|
// printk(KERN_ALERT"in re prepare record dma_flags is 0x%x\n",dma_flags); //deleted by huhy
|
||
|
cdesc = gsc3280_dma_single_prep(rx_chan, runtime->dma_addr, runtime->period_size, DMA_FROM_DEVICE);
|
||
|
cdesc->period_callback = &gsc3280_pcm_dma_transfer_done;
|
||
|
cdesc->period_callback_param = substream;
|
||
|
}
|
||
|
set_bit(DMA_READY, (void *)&dma_flags);
|
||
|
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_close(struct snd_pcm_substream *substream)
|
||
|
{
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct gsc3280_runtime_data *prtd = runtime->private_data;
|
||
|
|
||
|
cdesc->period_callback = NULL;
|
||
|
kfree(prtd);
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
gsc3280_dma_single_free(tx_chan);
|
||
|
}else{
|
||
|
gsc3280_dma_single_free(rx_chan);
|
||
|
}
|
||
|
test_and_clear_bit(DMA_READY,(void *)&dma_flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int gsc3280_pcm_mmap(struct snd_pcm_substream *substream,
|
||
|
struct vm_area_struct *vma)
|
||
|
{
|
||
|
return remap_pfn_range(vma, vma->vm_start,
|
||
|
substream->dma_buffer.addr >> PAGE_SHIFT,
|
||
|
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
||
|
}
|
||
|
|
||
|
static struct snd_pcm_ops gsc3280_pcm_ops = {
|
||
|
.open = gsc3280_pcm_open,
|
||
|
.close = gsc3280_pcm_close,
|
||
|
.ioctl = snd_pcm_lib_ioctl,
|
||
|
.hw_params = gsc3280_pcm_hw_params,
|
||
|
.hw_free = gsc3280_pcm_hw_free,
|
||
|
.prepare = gsc3280_pcm_prepare,
|
||
|
.trigger = gsc3280_pcm_trigger,
|
||
|
.pointer = gsc3280_pcm_pointer,
|
||
|
.mmap = gsc3280_pcm_mmap,
|
||
|
};
|
||
|
|
||
|
static int gsc3280_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||
|
{
|
||
|
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||
|
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||
|
size_t size = gsc3280_pcm_hardware.buffer_bytes_max;
|
||
|
|
||
|
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||
|
buf->dev.dev = pcm->card->dev;
|
||
|
buf->private_data = NULL;
|
||
|
|
||
|
buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
|
||
|
&buf->addr, GFP_KERNEL);
|
||
|
|
||
|
if (!buf->area)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
buf->bytes = size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void gsc3280_pcm_free(struct snd_pcm *pcm)
|
||
|
{
|
||
|
struct snd_pcm_substream *substream;
|
||
|
struct snd_dma_buffer *buf;
|
||
|
int stream;
|
||
|
|
||
|
for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
|
||
|
substream = pcm->streams[stream].substream;
|
||
|
if (!substream)
|
||
|
continue;
|
||
|
|
||
|
buf = &substream->dma_buffer;
|
||
|
if (!buf->area)
|
||
|
continue;
|
||
|
|
||
|
dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
|
||
|
buf->addr);
|
||
|
buf->area = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//static u64 gsc3280_pcm_dmamask = DMA_BIT_MASK(32);
|
||
|
|
||
|
int gsc3280_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||
|
struct snd_pcm *pcm)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (dai->driver->playback.channels_min) {
|
||
|
ret = gsc3280_pcm_preallocate_dma_buffer(pcm,
|
||
|
SNDRV_PCM_STREAM_PLAYBACK);
|
||
|
if (ret)
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (dai->driver->capture.channels_min) {
|
||
|
ret = gsc3280_pcm_preallocate_dma_buffer(pcm,
|
||
|
SNDRV_PCM_STREAM_CAPTURE);
|
||
|
if (ret)
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
err:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct snd_soc_platform_driver gsc3280_soc_platform = {
|
||
|
.ops = &gsc3280_pcm_ops,
|
||
|
.pcm_new = gsc3280_pcm_new,
|
||
|
.pcm_free = gsc3280_pcm_free,
|
||
|
};
|
||
|
|
||
|
static bool filter(struct dma_chan *chan, void *slave)
|
||
|
{
|
||
|
struct gsc3280_dma_slave *gss = slave;
|
||
|
|
||
|
if (gss->dma_dev == chan->device->dev) {
|
||
|
chan->private = gss;
|
||
|
return true;
|
||
|
} else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int __devinit gsc3280_pcm_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct pcm_platform_data *pdata;
|
||
|
pdata = pdev->dev.platform_data;
|
||
|
|
||
|
if(!pdata){
|
||
|
dev_dbg(&pdev->dev, "no platform data\n");
|
||
|
return -ENXIO;
|
||
|
}else{
|
||
|
if(pdata->tx_s.dma_dev){
|
||
|
struct gsc3280_dma_slave *gss = &(pdata->tx_s);
|
||
|
dma_cap_mask_t mask;
|
||
|
gss->tx_reg = 0x1c1121c8;
|
||
|
gss->reg_width = 1; //1:16,2:32
|
||
|
gss->mem_width = 2; //1:16,2:32
|
||
|
gss->cfg_hi = 0x6004 | 2; //no pre fetch
|
||
|
//gss->cfg_hi = 0x6004; //has pre fetch
|
||
|
gss->cfg_lo = 0xa00;
|
||
|
// gss->src_msize = 2;
|
||
|
// gss->dst_msize = 2;
|
||
|
gss->src_msize = 2;
|
||
|
gss->dst_msize = 2;
|
||
|
gss->dst_master = 0;
|
||
|
gss->src_master = 1;
|
||
|
gss->fc = 1; //M2P
|
||
|
dma_cap_zero(mask);
|
||
|
dma_cap_set(DMA_SLAVE, mask);
|
||
|
tx_chan = dma_request_channel(mask, filter, gss);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
if(pdata->rx_s.dma_dev){
|
||
|
struct gsc3280_dma_slave *gss = &(pdata->rx_s);
|
||
|
dma_cap_mask_t mask;
|
||
|
gss->rx_reg = 0x1c1121c0;
|
||
|
gss->reg_width = 1; //1:16,2:32
|
||
|
gss->mem_width = 2; //1:16,2:32
|
||
|
//gss->cfg_hi = 0x6004;
|
||
|
gss->cfg_hi = 0x684 | 2;
|
||
|
//gss->cfg_lo = 0xa00;
|
||
|
gss->cfg_lo = 0x600;
|
||
|
gss->src_msize = 2;
|
||
|
gss->dst_msize = 2;
|
||
|
gss->dst_master = 1;
|
||
|
gss->src_master = 0;
|
||
|
gss->fc = 2; //P2M
|
||
|
//dma_cap_zero(mask);
|
||
|
dma_cap_set(DMA_SLAVE, mask);
|
||
|
rx_chan = dma_request_channel(mask, filter, gss);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
return snd_soc_register_platform(&pdev->dev, &gsc3280_soc_platform);
|
||
|
}
|
||
|
|
||
|
static int __devexit gsc3280_pcm_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
snd_soc_unregister_platform(&pdev->dev);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver gsc3280_pcm_driver = {
|
||
|
.probe = gsc3280_pcm_probe,
|
||
|
.remove = __devexit_p(gsc3280_pcm_remove),
|
||
|
.driver = {
|
||
|
.name = "gsc3280-pcm-audio",
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init gsc3280_soc_platform_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&gsc3280_pcm_driver);
|
||
|
}
|
||
|
module_init(gsc3280_soc_platform_init);
|
||
|
|
||
|
static void __exit gsc3280_soc_platform_exit(void)
|
||
|
{
|
||
|
return platform_driver_unregister(&gsc3280_pcm_driver);
|
||
|
}
|
||
|
module_exit(gsc3280_soc_platform_exit);
|
||
|
|
||
|
MODULE_AUTHOR("china-cpu");
|
||
|
MODULE_LICENSE("GPL");
|