From: Davide Bonfanti davide.bonfanti@bticino.it
Since DM365 has the same DMA event for McBSP and Voicecodec this two peripherals cannot be used at the same time. This driver implements Voicecodec without the use of a DMA but with a copy_from_user. There's a buffer of BUF_SIZE (tested with 2KB) bytes in the driver that is filled with davinci_pcm_copy. When pcm is running, a TIMER interrupt is activated each 1.36ms in order to fill HW FIFO (dm_vc_irq). BUG: It happens sometimes that the peripheral stops working so there's a trap. This driver was tested with timer1 and timer4. The measures made using a GPIO and scope give a time for copy_from_user about 8/10us (triggered by alsa layer) while TIMER interrupt takes 1.5us.
This patch has been developed against the http://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-davinci.git git tree and tested on bmx board.
Signed-off-by: Davide Bonfanti davide.bonfanti@bticino.it Signed-off-by: Raffaele Recalcati raffaele.recalcati@bticino.it --- sound/soc/davinci/Makefile | 1 + sound/soc/davinci/davinci-pcm-copyfromuser.c | 333 ++++++++++++++++++++++++++ sound/soc/davinci/davinci-pcm.h | 1 + sound/soc/soc-core.c | 31 ++- 4 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 sound/soc/davinci/davinci-pcm-copyfromuser.c
diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index a93679d..7d6a9a1 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -1,5 +1,6 @@ # DAVINCI Platform Support snd-soc-davinci-objs := davinci-pcm.o +snd-soc-davinci-objs += davinci-pcm-copyfromuser.o snd-soc-davinci-i2s-objs := davinci-i2s.o snd-soc-davinci-mcasp-objs:= davinci-mcasp.o snd-soc-davinci-vcif-objs:= davinci-vcif.o diff --git a/sound/soc/davinci/davinci-pcm-copyfromuser.c b/sound/soc/davinci/davinci-pcm-copyfromuser.c new file mode 100644 index 0000000..df37873 --- /dev/null +++ b/sound/soc/davinci/davinci-pcm-copyfromuser.c @@ -0,0 +1,333 @@ +/* + * + * Copyright (C) 2010 Bticino S.p.a + * Author: Davide Bonfanti davide.bonfanti@bticino.it + * + * Contributors: + * Raffaele Recalcati raffaele.recalcati@bticino.it + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/clk.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/gpio.h> + +#include "davinci-pcm.h" + +/* Timer register offsets */ +#define PID12 0x00 +#define TIM12 0x10 +#define TIM34 0x14 +#define PRD12 0x18 +#define PRD34 0x1c +#define TCR 0x20 +#define TGCR 0x24 + +/* Timer register bitfields */ +#define TCR_ENAMODE_DISABLE 0x0 +#define TCR_ENAMODE_ONESHOT 0x1 +#define TCR_ENAMODE_PERIODIC 0x2 +#define TCR_ENAMODE_MASK 0x3 + +#define TGCR_TIMMODE_SHIFT 2 +#define TGCR_TIMMODE_64BIT_GP 0x0 +#define TGCR_TIMMODE_32BIT_UNCHAINED 0x1 +#define TGCR_TIMMODE_64BIT_WDOG 0x2 +#define TGCR_TIMMODE_32BIT_CHAINED 0x3 + +#define TGCR_TIM12RS_SHIFT 0 +#define TGCR_TIM34RS_SHIFT 1 +#define TGCR_RESET 0x0 +#define TGCR_UNRESET 0x1 +#define TGCR_RESET_MASK 0x3 + +#define TIMER1_BASE 0x01C21800 +#define TIMER4_BASE 0x01C23800 + +/* choose the timer to use */ +#define TIMER_BASE TIMER4_BASE +/* driver buffer dimension */ +#define BUF_SIZE 2048 + +#if (TIMER_BASE == TIMER1_BASE) +#define TINT IRQ_TINT1_TINT12 +#define TIMER "timer1" +#elif (TIMER_BASE == TIMER4_BASE) +#define TINT IRQ_PWMINT2 +#define TIMER "timer4" +#endif + +int pointer_sub; +u16 local_buffer[BUF_SIZE/2]; + +static struct snd_pcm_hardware pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = BUF_SIZE, + .period_bytes_min = 512, + .period_bytes_max = 512, + .periods_min = BUF_SIZE / 512, + .periods_max = BUF_SIZE / 512, + .fifo_size = 0, +}; + +/* + * How this driver works... + * + * Since DM365 have the same DMA event for I2S and Voicecodec this two + * peripheralscannot be used at the same time. + * This driver implements voicecodec without the use of a DMA but with + * a copy_from_user. + * There's a buffer of BUF_SIZE bytes in the driver that is filled with + * davinci_pcm_copy. + * When pcm is running, a TIMER interrupt is activated each 1.36ms in + * order to fill HW FIFO (dm_vc_irq). + * It happens that the peripheral stop working so there's a trap... + * Driver was proved with timer1 and timer4. if timer2 is used the device + * resets during power-up. Timer3 have a structure a bit different and, + * in fact, doesn't work. + * Measures using a GPIO and scope give a time for copy_from_user about + * 8/10us while interrupt takes 1.5us + */ + +static snd_pcm_uframes_t +davinci_pcm_pointer(struct snd_pcm_substream *substream) +{ + return pointer_sub; +} + +static int davinci_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct davinci_runtime_data *prtd; + struct snd_pcm_hardware *ppcm; + int ret = 0; + struct davinci_pcm_dma_params *pa; + struct davinci_pcm_dma_params *params; + struct clk *clk; + + pointer_sub = 0; + +/* + * ppcm = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + * &pcm_hardware_playback : &pcm_hardware_capture; + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ppcm = &pcm_hardware_playback; + else + return -ENODEV; + + snd_soc_set_runtime_hwparams(substream, ppcm); + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + __raw_writel(0x0, IO_ADDRESS(0x01D0C008)); + __raw_writel(0x7400, IO_ADDRESS(0x01D0C004)); + clk = clk_get(NULL, TIMER); + if (!IS_ERR(clk)) + clk_enable(clk); + return 0; +} + +static int davinci_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct clk *clk; + + clk = clk_get(NULL, TIMER); + if (!IS_ERR(clk)) + clk_disable(clk); + return 0; +} + +static int davinci_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int davinci_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int davinci_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (copy_from_user(local_buffer + hwoff, buf, + frames_to_bytes(runtime, frames))) { + printk(KERN_ERR "ERROR COPY_FROM_USER\n"); + return -EFAULT; + } + return 0; +} + +static struct snd_pcm_ops davinci_pcm_ops = { + .open = davinci_pcm_open, + .close = davinci_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = davinci_pcm_hw_params, + .hw_free = davinci_pcm_hw_free, + .pointer = davinci_pcm_pointer, + .copy = davinci_pcm_copy, +}; + +static u64 davinci_pcm_dmamask = 0xffffffff; + +static irqreturn_t dm_vc_irq(int irq, void *sbst) +{ + struct snd_pcm_substream *substream = + (struct snd_pcm_substream *) sbst; + int fifo, diff, per_size, buf_size; + static int last_ptr; + + if (substream->runtime && substream->runtime->status && + snd_pcm_running(substream)) { + fifo = __raw_readl(IO_ADDRESS(0x01D0C028)); + fifo = (fifo & 0x1f00) >> 8; + if (fifo == 15) { + /* peripheral blocked! restart */ + __raw_writel(0, IO_ADDRESS(0x01D0C004)); + __raw_writel(0x5400, IO_ADDRESS(0x01D0C004)); + } + buf_size = substream->runtime->buffer_size; + per_size = substream->runtime->period_size; + for (; fifo < 0x10; fifo++) { + __raw_writew(local_buffer[pointer_sub++], + IO_ADDRESS(0x01D0C024)); + pointer_sub %= buf_size; + do { + diff = __raw_readl(IO_ADDRESS(0x01D0C00C)); + } while (!(diff & 0x8)); + } + if (last_ptr >= pointer_sub) + diff = buf_size + pointer_sub - last_ptr; + else + diff = pointer_sub - last_ptr; + if (diff >= per_size) { + snd_pcm_period_elapsed(substream); + last_ptr += per_size; + if (last_ptr >= buf_size) + last_ptr -= buf_size; + } + } else + last_ptr = 0; + return IRQ_HANDLED; +} + +static int davinci_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int tcr, tgcr; + struct snd_dma_buffer *buf; + struct snd_pcm_substream *substream; + struct clk *clk; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &davinci_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + buf = &substream->dma_buffer; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->bytes = pcm_hardware_playback.buffer_bytes_max; + } + clk = clk_get(NULL, TIMER); + if (IS_ERR(clk)) + printk(KERN_ERR "ERROR getting timer\n"); + + /* Disabled, Internal clock source */ + __raw_writel(0, IO_ADDRESS(TIMER_BASE + TCR)); + + /* reset both timers, no pre-scaler for timer34 */ + tgcr = 0; + __raw_writel(tgcr, IO_ADDRESS(TIMER_BASE + TGCR)); + + /* Set both timers to unchained 32-bit */ + tgcr = TGCR_TIMMODE_32BIT_UNCHAINED << TGCR_TIMMODE_SHIFT; + __raw_writel(tgcr, IO_ADDRESS(TIMER_BASE + TGCR)); + + /* Unreset timers */ + tgcr |= (TGCR_UNRESET << TGCR_TIM12RS_SHIFT); + __raw_writel(tgcr, IO_ADDRESS(TIMER_BASE + TGCR)); + + /* Init both counters to zero */ + __raw_writel(0, IO_ADDRESS(TIMER_BASE + TIM12)); + tcr = __raw_readl(IO_ADDRESS(TIMER_BASE + TCR)); + + /* disable timer */ + tcr &= ~(TCR_ENAMODE_MASK << 6); + __raw_writel(tcr, IO_ADDRESS(TIMER_BASE + TCR)); + + /* reset counter to zero, set new period */ + __raw_writel(0, IO_ADDRESS(TIMER_BASE + TIM12)); + __raw_writel(0x8000, IO_ADDRESS(TIMER_BASE + PRD12)); + + /* Set enable mode */ + tcr |= TCR_ENAMODE_PERIODIC << 6; + __raw_writel(tcr, IO_ADDRESS(TIMER_BASE + TCR)); + +#if (TIMER_BASE == TIMER4_BASE) + __raw_writel(__raw_readl(IO_ADDRESS(0x01c40018)) | + 0x1000, IO_ADDRESS(0x01c40018)); +#endif + if (request_irq(TINT, dm_vc_irq, IRQF_TIMER, "VOICE-TOUT", substream)) + printk(KERN_ERR "%s ERROR requesting Interrupt\n", __func__); + return 0; +} + +struct snd_soc_platform davinci_soc_platform_copy = { + .name = "davinci-audio-copy", + .pcm_ops = &davinci_pcm_ops, + .pcm_new = davinci_pcm_new, +}; EXPORT_SYMBOL_GPL(davinci_soc_platform_copy); + +static int __init davinci_soc_copy_platform_init(void) +{ + return snd_soc_register_platform(&davinci_soc_platform_copy); +} +module_init(davinci_soc_copy_platform_init); + +static void __exit davinci_soc_copy_platform_exit(void) +{ + snd_soc_unregister_platform(&davinci_soc_platform_copy); +} +module_exit(davinci_soc_copy_platform_exit); + +MODULE_AUTHOR("bticino s.p.a."); +MODULE_DESCRIPTION("TI DAVINCI PCM copy_from_user module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 0764944..cb7c2aa 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -29,5 +29,6 @@ struct davinci_pcm_dma_params {
extern struct snd_soc_platform davinci_soc_platform; +extern struct snd_soc_platform davinci_soc_platform_copy;
#endif diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index ad7f952..696e8b3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -801,7 +801,7 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) } return 0; } - +#if 0 /* ASoC PCM operations */ static struct snd_pcm_ops soc_pcm_ops = { .open = soc_pcm_open, @@ -811,7 +811,7 @@ static struct snd_pcm_ops soc_pcm_ops = { .prepare = soc_pcm_prepare, .trigger = soc_pcm_trigger, }; - +#endif #ifdef CONFIG_PM /* powers down audio subsystem for suspend */ static int soc_suspend(struct device *dev) @@ -1306,6 +1306,7 @@ static int soc_new_pcm(struct snd_soc_device *socdev, struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; + struct snd_pcm_ops *soc_pcm_ops;
rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL); if (rtd == NULL) @@ -1335,19 +1336,25 @@ static int soc_new_pcm(struct snd_soc_device *socdev,
dai_link->pcm = pcm; pcm->private_data = rtd; - soc_pcm_ops.mmap = platform->pcm_ops->mmap; - soc_pcm_ops.pointer = platform->pcm_ops->pointer; - soc_pcm_ops.ioctl = platform->pcm_ops->ioctl; - soc_pcm_ops.copy = platform->pcm_ops->copy; - soc_pcm_ops.silence = platform->pcm_ops->silence; - soc_pcm_ops.ack = platform->pcm_ops->ack; - soc_pcm_ops.page = platform->pcm_ops->page; + soc_pcm_ops = kmalloc(sizeof(struct snd_pcm_ops), GFP_KERNEL); + soc_pcm_ops->open = soc_pcm_open; + soc_pcm_ops->close = soc_codec_close; + soc_pcm_ops->hw_params = soc_pcm_hw_params; + soc_pcm_ops->hw_free = soc_pcm_hw_free; + soc_pcm_ops->prepare = soc_pcm_prepare; + soc_pcm_ops->trigger = soc_pcm_trigger; + soc_pcm_ops->mmap = platform->pcm_ops->mmap; + soc_pcm_ops->pointer = platform->pcm_ops->pointer; + soc_pcm_ops->ioctl = platform->pcm_ops->ioctl; + soc_pcm_ops->copy = platform->pcm_ops->copy; + soc_pcm_ops->silence = platform->pcm_ops->silence; + soc_pcm_ops->ack = platform->pcm_ops->ack; + soc_pcm_ops->page = platform->pcm_ops->page;
if (playback) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops); - + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops); if (capture) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
ret = platform->pcm_new(codec->card, codec_dai, pcm); if (ret < 0) {