From patchwork Wed Jul 20 03:41:37 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aisheng Dong X-Patchwork-Id: 990082 Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by demeter1.kernel.org (8.14.4/8.14.4) with ESMTP id p6K3HDFK003450 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Wed, 20 Jul 2011 03:17:34 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QjNHT-00081t-Sh; Wed, 20 Jul 2011 03:17:04 +0000 Received: from localhost ([127.0.0.1] helo=canuck.infradead.org) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1QjNHT-0002yW-1e; Wed, 20 Jul 2011 03:17:03 +0000 Received: from ch1ehsobe003.messaging.microsoft.com ([216.32.181.183] helo=ch1outboundpool.messaging.microsoft.com) by canuck.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QjNGj-0002qM-51 for linux-arm-kernel@lists.infradead.org; Wed, 20 Jul 2011 03:16:21 +0000 Received: from mail12-ch1-R.bigfish.com (216.32.181.168) by CH1EHSOBE010.bigfish.com (10.43.70.60) with Microsoft SMTP Server id 14.1.225.22; Wed, 20 Jul 2011 03:16:14 +0000 Received: from mail12-ch1 (localhost.localdomain [127.0.0.1]) by mail12-ch1-R.bigfish.com (Postfix) with ESMTP id 34847A78298; Wed, 20 Jul 2011 03:16:14 +0000 (UTC) X-SpamScore: 6 X-BigFish: VS6(zzc8kzz1202h1082kzz8275bhz2dh2a8h668h839h66h) X-Spam-TCS-SCL: 5:0 X-Forefront-Antispam-Report: CIP:70.37.183.190; KIP:(null); UIP:(null); IPVD:NLI; H:mail.freescale.net; RD:none; EFVD:NLI Received: from mail12-ch1 (localhost.localdomain [127.0.0.1]) by mail12-ch1 (MessageSwitch) id 1311131772814620_949; Wed, 20 Jul 2011 03:16:12 +0000 (UTC) Received: from CH1EHSMHS014.bigfish.com (snatpool1.int.messaging.microsoft.com [10.43.68.244]) by mail12-ch1.bigfish.com (Postfix) with ESMTP id BF67F600050; Wed, 20 Jul 2011 03:16:12 +0000 (UTC) Received: from mail.freescale.net (70.37.183.190) by CH1EHSMHS014.bigfish.com (10.43.70.14) with Microsoft SMTP Server (TLS) id 14.1.225.22; Wed, 20 Jul 2011 03:16:11 +0000 Received: from az33smr02.freescale.net (10.64.34.200) by 039-SN1MMR1-003.039d.mgd.msft.net (10.84.1.16) with Microsoft SMTP Server id 14.1.289.8; Tue, 19 Jul 2011 22:16:10 -0500 Received: from localhost.localdomain (shlinux2.ap.freescale.net [10.192.224.44]) by az33smr02.freescale.net (8.13.1/8.13.0) with ESMTP id p6K3G5dU025756; Tue, 19 Jul 2011 22:16:08 -0500 (CDT) From: Dong Aisheng To: Subject: [PATCH v4 01/10] ASoc: mxs: add mxs-pcm driver Date: Wed, 20 Jul 2011 11:41:37 +0800 Message-ID: <1311133306-9496-2-git-send-email-b29396@freescale.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: <1311133306-9496-1-git-send-email-b29396@freescale.com> References: <1311133306-9496-1-git-send-email-b29396@freescale.com> MIME-Version: 1.0 X-OriginatorOrg: freescale.com X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.7.6 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110719_231617_482194_21AD1BBA X-CRM114-Status: GOOD ( 21.86 ) X-Spam-Score: -0.7 (/) X-Spam-Report: SpamAssassin version 3.3.1 on canuck.infradead.org summary: Content analysis details: (-0.7 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [216.32.181.183 listed in list.dnswl.org] Cc: s.hauer@pengutronix.de, broonie@opensource.wolfsonmicro.com, lrg@ti.com, linux-arm-kernel@lists.infradead.org, w.sang@pengutronix.de X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Wed, 20 Jul 2011 03:17:34 +0000 (UTC) Signed-off-by: Dong Aisheng Cc: Mark Brown Cc: Liam Girdwood Cc: Sascha Hauer Tested-by: Wolfram Sang --- sound/soc/mxs/mxs-pcm.c | 359 +++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/mxs/mxs-pcm.h | 43 ++++++ 2 files changed, 402 insertions(+), 0 deletions(-) diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c new file mode 100644 index 0000000..f9063e7 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Based on sound/soc/imx/imx-pcm-dma-mx2.c + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "mxs-pcm.h" + +static struct snd_pcm_hardware snd_mxs_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 52, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, + +}; + +static void audio_dma_irq(void *data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->offset += iprtd->period_bytes; + iprtd->offset %= iprtd->period_bytes * iprtd->periods; + snd_pcm_period_elapsed(substream); +} + +static bool filter(struct dma_chan *chan, void *param) +{ + struct mxs_pcm_runtime_data *iprtd = param; + struct mxs_pcm_dma_params *dma_params = iprtd->dma_params; + + if (!mxs_dma_is_apbx(chan)) + return false; + + if (chan->chan_id != dma_params->chan_num) + return false; + + chan->private = &iprtd->dma_data; + + return true; +} + +static int mxs_dma_alloc(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + dma_cap_mask_t mask; + + iprtd->dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + iprtd->dma_data.chan_irq = iprtd->dma_params->chan_irq; + iprtd->dma_chan = dma_request_channel(mask, filter, iprtd); + if (!iprtd->dma_chan) + return -EINVAL; + + return 0; +} + +static int snd_mxs_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + unsigned long dma_addr; + struct dma_chan *chan; + int ret; + + ret = mxs_dma_alloc(substream, params); + if (ret) + return ret; + chan = iprtd->dma_chan; + + iprtd->size = params_buffer_bytes(params); + iprtd->periods = params_periods(params); + iprtd->period_bytes = params_period_bytes(params); + iprtd->offset = 0; + iprtd->period_time = HZ / (params_rate(params) / + params_period_size(params)); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + dma_addr = runtime->dma_addr; + + iprtd->buf = substream->dma_buffer.area; + + iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr, + iprtd->period_bytes * iprtd->periods, + iprtd->period_bytes, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (!iprtd->desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return -EINVAL; + } + + iprtd->desc->callback = audio_dma_irq; + iprtd->desc->callback_param = substream; + + return 0; +} + +static int snd_mxs_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + + if (iprtd->dma_chan) { + dma_release_channel(iprtd->dma_chan); + iprtd->dma_chan = NULL; + } + + return 0; +} + +static int snd_mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dmaengine_submit(iprtd->desc); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(iprtd->dma_chan); + + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_mxs_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + + return bytes_to_frames(substream->runtime, iprtd->offset); +} + +static int snd_mxs_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd; + int ret; + + iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; + runtime->private_data = iprtd; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + snd_soc_set_runtime_hwparams(substream, &snd_mxs_hardware); + + return 0; +} + +static int snd_mxs_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + + kfree(iprtd); + + return 0; +} + +static int snd_mxs_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops mxs_pcm_ops = { + .open = snd_mxs_open, + .close = snd_mxs_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxs_pcm_hw_params, + .hw_free = snd_mxs_pcm_hw_free, + .trigger = snd_mxs_pcm_trigger, + .pointer = snd_mxs_pcm_pointer, + .mmap = snd_mxs_pcm_mmap, +}; + +static int mxs_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 = snd_mxs_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_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static u64 mxs_pcm_dmamask = DMA_BIT_MASK(32); +static int mxs_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &mxs_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = mxs_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = mxs_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + return ret; +} + +static void mxs_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static struct snd_soc_platform_driver mxs_soc_platform = { + .ops = &mxs_pcm_ops, + .pcm_new = mxs_pcm_new, + .pcm_free = mxs_pcm_free, +}; + +static int __devinit mxs_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &mxs_soc_platform); +} + +static int __devexit mxs_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver mxs_pcm_driver = { + .driver = { + .name = "mxs-pcm-audio", + .owner = THIS_MODULE, + }, + .probe = mxs_soc_platform_probe, + .remove = __devexit_p(mxs_soc_platform_remove), +}; + +static int __init snd_mxs_pcm_init(void) +{ + return platform_driver_register(&mxs_pcm_driver); +} +module_init(snd_mxs_pcm_init); + +static void __exit snd_mxs_pcm_exit(void) +{ + platform_driver_unregister(&mxs_pcm_driver); +} +module_exit(snd_mxs_pcm_exit); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h new file mode 100644 index 0000000..f55ac4f --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MXS_PCM_H +#define _MXS_PCM_H + +#include + +struct mxs_pcm_dma_params { + int chan_irq; + int chan_num; +}; + +struct mxs_pcm_runtime_data { + int period_bytes; + int periods; + int dma; + unsigned long offset; + unsigned long size; + void *buf; + int period_time; + struct dma_async_tx_descriptor *desc; + struct dma_chan *dma_chan; + struct mxs_dma_data dma_data; + struct mxs_pcm_dma_params *dma_params; +}; + +#endif