/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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");