ckfwq/linux-3.0.4/sound/soc/blx/gsc3280-pcm.c

503 lines
13 KiB
C
Raw Normal View History

2024-12-30 10:53:50 +08:00
/*
* 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");