Path: blob/master/sound/soc/blackfin/bf5xx-ac97-pcm.c
10817 views
/*1* File: sound/soc/blackfin/bf5xx-ac97-pcm.c2* Author: Cliff Cai <[email protected]>3*4* Created: Tue June 06 20085* Description: DMA Driver for AC97 sound chip6*7* Modified:8* Copyright 2008 Analog Devices Inc.9*10* Bugs: Enter bugs at http://blackfin.uclinux.org/11*12* This program is free software; you can redistribute it and/or modify13* it under the terms of the GNU General Public License as published by14* the Free Software Foundation; either version 2 of the License, or15* (at your option) any later version.16*17* This program is distributed in the hope that it will be useful,18* but WITHOUT ANY WARRANTY; without even the implied warranty of19* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the20* GNU General Public License for more details.21*22* You should have received a copy of the GNU General Public License23* along with this program; if not, see the file COPYING, or write24* to the Free Software Foundation, Inc.,25* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA26*/2728#include <linux/module.h>29#include <linux/init.h>30#include <linux/platform_device.h>31#include <linux/dma-mapping.h>32#include <linux/gfp.h>3334#include <sound/core.h>35#include <sound/pcm.h>36#include <sound/pcm_params.h>37#include <sound/soc.h>3839#include <asm/dma.h>4041#include "bf5xx-ac97-pcm.h"42#include "bf5xx-ac97.h"43#include "bf5xx-sport.h"4445static unsigned int ac97_chan_mask[] = {46SP_FL, /* Mono */47SP_STEREO, /* Stereo */48SP_2DOT1, /* 2.1*/49SP_QUAD,/*Quadraquic*/50SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */51SP_5DOT1, /* 5.1 */52};5354#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)55static void bf5xx_mmap_copy(struct snd_pcm_substream *substream,56snd_pcm_uframes_t count)57{58struct snd_pcm_runtime *runtime = substream->runtime;59struct sport_device *sport = runtime->private_data;60unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];61if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {62bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf +63sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos *64runtime->channels, count, chan_mask);65sport->tx_pos += runtime->period_size;66if (sport->tx_pos >= runtime->buffer_size)67sport->tx_pos %= runtime->buffer_size;68sport->tx_delay_pos = sport->tx_pos;69} else {70bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf +71sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos *72runtime->channels, count);73sport->rx_pos += runtime->period_size;74if (sport->rx_pos >= runtime->buffer_size)75sport->rx_pos %= runtime->buffer_size;76}77}78#endif7980static void bf5xx_dma_irq(void *data)81{82struct snd_pcm_substream *pcm = data;83#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)84struct snd_pcm_runtime *runtime = pcm->runtime;85struct sport_device *sport = runtime->private_data;86bf5xx_mmap_copy(pcm, runtime->period_size);87if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) {88if (sport->once == 0) {89snd_pcm_period_elapsed(pcm);90bf5xx_mmap_copy(pcm, runtime->period_size);91sport->once = 1;92}93}94#endif95snd_pcm_period_elapsed(pcm);96}9798/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes.99* The total rx/tx buffer is for ac97 frame to hold all pcm data100* is 0x20000 * sizeof(struct ac97_frame) / 4.101*/102static const struct snd_pcm_hardware bf5xx_pcm_hardware = {103.info = SNDRV_PCM_INFO_INTERLEAVED |104#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)105SNDRV_PCM_INFO_MMAP |106SNDRV_PCM_INFO_MMAP_VALID |107#endif108SNDRV_PCM_INFO_BLOCK_TRANSFER,109110.formats = SNDRV_PCM_FMTBIT_S16_LE,111.period_bytes_min = 32,112.period_bytes_max = 0x10000,113.periods_min = 1,114.periods_max = PAGE_SIZE/32,115.buffer_bytes_max = 0x20000, /* 128 kbytes */116.fifo_size = 16,117};118119static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,120struct snd_pcm_hw_params *params)121{122size_t size = bf5xx_pcm_hardware.buffer_bytes_max123* sizeof(struct ac97_frame) / 4;124125snd_pcm_lib_malloc_pages(substream, size);126127return 0;128}129130static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)131{132#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)133struct snd_pcm_runtime *runtime = substream->runtime;134struct sport_device *sport = runtime->private_data;135136if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {137sport->once = 0;138if (runtime->dma_area)139memset(runtime->dma_area, 0, runtime->buffer_size);140memset(sport->tx_dma_buf, 0, runtime->buffer_size *141sizeof(struct ac97_frame));142} else143memset(sport->rx_dma_buf, 0, runtime->buffer_size *144sizeof(struct ac97_frame));145#endif146snd_pcm_lib_free_pages(substream);147return 0;148}149150static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)151{152struct snd_pcm_runtime *runtime = substream->runtime;153struct sport_device *sport = runtime->private_data;154155/* An intermediate buffer is introduced for implementing mmap for156* SPORT working in TMD mode(include AC97).157*/158#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)159if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {160sport_set_tx_callback(sport, bf5xx_dma_irq, substream);161sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods,162runtime->period_size * sizeof(struct ac97_frame));163} else {164sport_set_rx_callback(sport, bf5xx_dma_irq, substream);165sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods,166runtime->period_size * sizeof(struct ac97_frame));167}168#else169if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {170sport_set_tx_callback(sport, bf5xx_dma_irq, substream);171sport_config_tx_dma(sport, runtime->dma_area, runtime->periods,172runtime->period_size * sizeof(struct ac97_frame));173} else {174sport_set_rx_callback(sport, bf5xx_dma_irq, substream);175sport_config_rx_dma(sport, runtime->dma_area, runtime->periods,176runtime->period_size * sizeof(struct ac97_frame));177}178#endif179return 0;180}181182static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)183{184struct snd_pcm_runtime *runtime = substream->runtime;185struct sport_device *sport = runtime->private_data;186int ret = 0;187188pr_debug("%s enter\n", __func__);189switch (cmd) {190case SNDRV_PCM_TRIGGER_START:191if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {192#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)193bf5xx_mmap_copy(substream, runtime->period_size);194sport->tx_delay_pos = 0;195#endif196sport_tx_start(sport);197} else198sport_rx_start(sport);199break;200case SNDRV_PCM_TRIGGER_STOP:201case SNDRV_PCM_TRIGGER_SUSPEND:202case SNDRV_PCM_TRIGGER_PAUSE_PUSH:203if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {204#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)205sport->tx_pos = 0;206#endif207sport_tx_stop(sport);208} else {209#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)210sport->rx_pos = 0;211#endif212sport_rx_stop(sport);213}214break;215default:216ret = -EINVAL;217}218return ret;219}220221static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)222{223struct snd_pcm_runtime *runtime = substream->runtime;224struct sport_device *sport = runtime->private_data;225unsigned int curr;226227#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)228if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)229curr = sport->tx_delay_pos;230else231curr = sport->rx_pos;232#else233234if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)235curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame);236else237curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame);238239#endif240return curr;241}242243static int bf5xx_pcm_open(struct snd_pcm_substream *substream)244{245struct snd_soc_pcm_runtime *rtd = substream->private_data;246struct snd_soc_dai *cpu_dai = rtd->cpu_dai;247struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);248struct snd_pcm_runtime *runtime = substream->runtime;249int ret;250251pr_debug("%s enter\n", __func__);252snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);253254ret = snd_pcm_hw_constraint_integer(runtime,255SNDRV_PCM_HW_PARAM_PERIODS);256if (ret < 0)257goto out;258259if (sport_handle != NULL)260runtime->private_data = sport_handle;261else {262pr_err("sport_handle is NULL\n");263return -1;264}265return 0;266267out:268return ret;269}270271#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)272static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,273struct vm_area_struct *vma)274{275struct snd_pcm_runtime *runtime = substream->runtime;276size_t size = vma->vm_end - vma->vm_start;277vma->vm_start = (unsigned long)runtime->dma_area;278vma->vm_end = vma->vm_start + size;279vma->vm_flags |= VM_SHARED;280return 0 ;281}282#else283static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,284snd_pcm_uframes_t pos,285void __user *buf, snd_pcm_uframes_t count)286{287struct snd_pcm_runtime *runtime = substream->runtime;288unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];289pr_debug("%s copy pos:0x%lx count:0x%lx\n",290substream->stream ? "Capture" : "Playback", pos, count);291292if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)293bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos,294(__u16 *)buf, count, chan_mask);295else296bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos,297(__u16 *)buf, count);298return 0;299}300#endif301302static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {303.open = bf5xx_pcm_open,304.ioctl = snd_pcm_lib_ioctl,305.hw_params = bf5xx_pcm_hw_params,306.hw_free = bf5xx_pcm_hw_free,307.prepare = bf5xx_pcm_prepare,308.trigger = bf5xx_pcm_trigger,309.pointer = bf5xx_pcm_pointer,310#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)311.mmap = bf5xx_pcm_mmap,312#else313.copy = bf5xx_pcm_copy,314#endif315};316317static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)318{319struct snd_soc_pcm_runtime *rtd = pcm->private_data;320struct snd_soc_dai *cpu_dai = rtd->cpu_dai;321struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);322struct snd_pcm_substream *substream = pcm->streams[stream].substream;323struct snd_dma_buffer *buf = &substream->dma_buffer;324size_t size = bf5xx_pcm_hardware.buffer_bytes_max325* sizeof(struct ac97_frame) / 4;326327buf->dev.type = SNDRV_DMA_TYPE_DEV;328buf->dev.dev = pcm->card->dev;329buf->private_data = NULL;330buf->area = dma_alloc_coherent(pcm->card->dev, size,331&buf->addr, GFP_KERNEL);332if (!buf->area) {333pr_err("Failed to allocate dma memory\n");334pr_err("Please increase uncached DMA memory region\n");335return -ENOMEM;336}337buf->bytes = size;338339pr_debug("%s, area:%p, size:0x%08lx\n", __func__,340buf->area, buf->bytes);341342if (stream == SNDRV_PCM_STREAM_PLAYBACK)343sport_handle->tx_buf = buf->area;344else345sport_handle->rx_buf = buf->area;346347/*348* Need to allocate local buffer when enable349* MMAP for SPORT working in TMD mode (include AC97).350*/351#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)352if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {353if (!sport_handle->tx_dma_buf) {354sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \355size, &sport_handle->tx_dma_phy, GFP_KERNEL);356if (!sport_handle->tx_dma_buf) {357pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");358return -ENOMEM;359} else360memset(sport_handle->tx_dma_buf, 0, size);361} else362memset(sport_handle->tx_dma_buf, 0, size);363} else {364if (!sport_handle->rx_dma_buf) {365sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \366size, &sport_handle->rx_dma_phy, GFP_KERNEL);367if (!sport_handle->rx_dma_buf) {368pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");369return -ENOMEM;370} else371memset(sport_handle->rx_dma_buf, 0, size);372} else373memset(sport_handle->rx_dma_buf, 0, size);374}375#endif376return 0;377}378379static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)380{381struct snd_pcm_substream *substream;382struct snd_dma_buffer *buf;383int stream;384#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)385struct snd_soc_pcm_runtime *rtd = pcm->private_data;386struct snd_soc_dai *cpu_dai = rtd->cpu_dai;387struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);388size_t size = bf5xx_pcm_hardware.buffer_bytes_max *389sizeof(struct ac97_frame) / 4;390#endif391for (stream = 0; stream < 2; stream++) {392substream = pcm->streams[stream].substream;393if (!substream)394continue;395396buf = &substream->dma_buffer;397if (!buf->area)398continue;399dma_free_coherent(NULL, buf->bytes, buf->area, 0);400buf->area = NULL;401#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)402if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {403if (sport_handle->tx_dma_buf)404dma_free_coherent(NULL, size, \405sport_handle->tx_dma_buf, 0);406sport_handle->tx_dma_buf = NULL;407} else {408409if (sport_handle->rx_dma_buf)410dma_free_coherent(NULL, size, \411sport_handle->rx_dma_buf, 0);412sport_handle->rx_dma_buf = NULL;413}414#endif415}416}417418static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);419420int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,421struct snd_pcm *pcm)422{423int ret = 0;424425pr_debug("%s enter\n", __func__);426if (!card->dev->dma_mask)427card->dev->dma_mask = &bf5xx_pcm_dmamask;428if (!card->dev->coherent_dma_mask)429card->dev->coherent_dma_mask = DMA_BIT_MASK(32);430431if (dai->driver->playback.channels_min) {432ret = bf5xx_pcm_preallocate_dma_buffer(pcm,433SNDRV_PCM_STREAM_PLAYBACK);434if (ret)435goto out;436}437438if (dai->driver->capture.channels_min) {439ret = bf5xx_pcm_preallocate_dma_buffer(pcm,440SNDRV_PCM_STREAM_CAPTURE);441if (ret)442goto out;443}444out:445return ret;446}447448static struct snd_soc_platform_driver bf5xx_ac97_soc_platform = {449.ops = &bf5xx_pcm_ac97_ops,450.pcm_new = bf5xx_pcm_ac97_new,451.pcm_free = bf5xx_pcm_free_dma_buffers,452};453454static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)455{456return snd_soc_register_platform(&pdev->dev, &bf5xx_ac97_soc_platform);457}458459static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)460{461snd_soc_unregister_platform(&pdev->dev);462return 0;463}464465static struct platform_driver bf5xx_pcm_driver = {466.driver = {467.name = "bfin-ac97-pcm-audio",468.owner = THIS_MODULE,469},470471.probe = bf5xx_soc_platform_probe,472.remove = __devexit_p(bf5xx_soc_platform_remove),473};474475static int __init snd_bf5xx_pcm_init(void)476{477return platform_driver_register(&bf5xx_pcm_driver);478}479module_init(snd_bf5xx_pcm_init);480481static void __exit snd_bf5xx_pcm_exit(void)482{483platform_driver_unregister(&bf5xx_pcm_driver);484}485module_exit(snd_bf5xx_pcm_exit);486487MODULE_AUTHOR("Cliff Cai");488MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");489MODULE_LICENSE("GPL");490491492