/*++ * linux/sound/soc/wmt/wmt-pdm-if.c * WonderMedia I2S audio driver for ALSA * * Copyright c 2010 WonderMedia Technologies, Inc. * * 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, see . * * WonderMedia Technologies, Inc. * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C --*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wmt-soc.h" #include "wmt-pcm-controller.h" #define PCM_IS_MASTER_MODE #define NULL_DMA ((dmach_t)(-1)) static int wmt_pcm_module_enable = 0; extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); static struct audio_stream_a wmt_pcm_controller_data[] = { { .id = "WMT PCM out", .stream_id = SNDRV_PCM_STREAM_PLAYBACK, .dmach = NULL_DMA, .dma_dev = PCM_TX_DMA_REQ, /*.dma_cfg = dma_device_cfg_table[I2S_TX_DMA_REQ],*/ }, { .id = "WMT PCM in", .stream_id = SNDRV_PCM_STREAM_CAPTURE, .dmach = NULL_DMA, .dma_dev = PCM_RX_DMA_REQ, /*.dma_cfg = dma_device_cfg_table[I2S_RX_DMA_REQ],*/ }, }; struct wmt_pcm_controller { int irq_no; int pcm_clk_src; int pcm_enable; }; static struct wmt_pcm_controller wmt_pcm_controller; static irqreturn_t wmt_pcm_controller_irq_handler(int irq, void *dev_id) { if (PCMSR_VAL & PCMSR_TXUND) { printk("-->PCMSR_TXUND\n"); } else if (PCMSR_VAL & PCMSR_RXOVR) { printk("-->PCMSR_RXOVR\n"); } PCMSR_VAL = 0x7F; //write clear all intr return IRQ_HANDLED; } static void wmt_pcm_controller_enable(void) { PCMCR_VAL |= (PCMCR_PCM_ENABLE | PCMCR_DMA_EN); } static void wmt_pcm_controller_disable(void) { PCMCR_VAL &= ~(PCMCR_PCM_ENABLE | PCMCR_DMA_EN); } static int wmt_pcm_controller_init(void) { wmt_pcm_controller.irq_no = IRQ_PCM; // Before control pcm-module, enable pcm clock first CLOCKEN(27); // set pcm_clk_source = 62464khz //auto_pll_divisor(DEV_PCM0, CLK_ENABLE, 0, 0); //auto_pll_divisor(DEV_PCM0, SET_PLLDIV, 1, 83333); wmt_pcm_controller.pcm_clk_src = auto_pll_divisor(DEV_PCM0, GET_FREQ, 0, 0); wmt_pcm_controller.pcm_clk_src /= 1000; wmt_pcm_controller.pcm_enable = 0; printk("wmt_pcm_controller_init: pcm_clk_src=%d \n\r", wmt_pcm_controller.pcm_clk_src); // Note: you should config PIN_SHARING_SEL_4BYTE_VAL if using pcm function!!! Loon mark at 2013/4/10 if (wmt_pcm_module_enable) { printk("begin to configure pcm pin\n"); /* disable GPIO and Pull Down mode */ /* Bit1:I2SDACDAT1=PCMSYNC, Bit2:I2SDACDAT2=PCMCLK, Bit3:I2SDACDAT3=PCMIN, Bit4:I2SADCMCLK=PCMOUT */ GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); GPIO_CTRL_GP11_I2S_BYTE_VAL &= ~(BIT1); /*disable pull enable*/ PULL_EN_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); PULL_EN_GP11_I2S_BYTE_VAL &= ~(BIT1); /* set to pcm mode */ // select PCMMCLK[bit0], PCMSYNC[bit17:16], PCMCLK[19:18], PCMIN[20], PCMOUT[22:21] //GPIO_PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT21); GPIO_PIN_SHARING_SEL_4BYTE_VAL |= (BIT15 | BIT16 | BIT17 | BIT18 | BIT19 |BIT20); } // set pcm control register PCMCR_VAL |= (PCMCR_TXFF_RST | PCMCR_RXFF_RST); //PCMCR_VAL |= 0x00880000; // TX/RX Fifo Threshold A PCMCR_VAL &= ~(PCMCR_BCLK_SEL); PCMCR_VAL &= ~PCMCR_SLAVE; // master mode PCMCR_VAL |= PCMCR_SYNC_MODE;//short frame sync // set pcm format register PCMDFCR_VAL = 0; PCMDFCR_VAL |= (PCMDFCR_WR_AL | PCMDFCR_TX_AL | PCMDFCR_RX_AL | PCMDFCR_RD_AL); PCMDFCR_VAL |= (PCMDFCR_TX_SZ_14 | PCMDFCR_RX_SZ_14); //PCMDFCR_VAL |= (PCMDFCR_TX_SZ_08 | PCMDFCR_RX_SZ_08); // // request irq // /*if (request_irq(wmt_pcm_controller.irq_no, &wmt_pcm_controller_irq_handler, IRQF_DISABLED, "wmt_pcm_controller", NULL)){ printk(KERN_ERR "PCM_IRQ Request Failed!\n"); } PCMCR_VAL |= (PCMCR_IRQ_EN | PCMCR_TXUND_EN | PCMCR_RXOVR_EN); */ printk("fun:%s,line:%d\n",__func__,__LINE__); return 0 ; } static int wmt_pcm_controller_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; int stream_id = substream->pstr->stream; struct audio_stream_a *s = &wmt_pcm_controller_data[0]; //dump_stack(); s[stream_id].stream = substream; runtime->private_data = s; return 0; } static void wmt_pcm_controller_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { //dump_stack(); } static int wmt_pcm_controller_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { int err = 0; int stream_id = substream->pstr->stream; //dump_stack(); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { PCMCR_VAL |= PCMCR_TXFF_RST; // reset txfifo PCMDFCR_VAL |= PCMDFCR_TXFM_EN; } else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { PCMCR_VAL |= PCMCR_RXFF_RST; // reset rxfifo PCMDFCR_VAL |= PCMDFCR_RXFM_EN; } //wmt_pcm_controller_enable(); wmt_pcm_controller.pcm_enable++; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { PCMDFCR_VAL &= ~PCMDFCR_TXFM_EN; } else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { PCMDFCR_VAL &= ~PCMDFCR_RXFM_EN; } //wmt_pcm_controller_disable(); wmt_pcm_controller.pcm_enable--; break; default: err = -EINVAL; break; } if (wmt_pcm_controller.pcm_enable) wmt_pcm_controller_enable(); else wmt_pcm_controller_disable(); return err; } static int wmt_pcm_controller_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { //dump_stack(); return 0; } static int wmt_pcm_controller_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; unsigned int channel, byte; int stream_id = substream->pstr->stream; byte = (runtime->sample_bits)/8; channel = runtime->channels; printk(KERN_INFO "wmt_pcm_controller_dai_prepare byte = %d, channels = %d\n", byte, runtime->channels); /* format setting */ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { /* little or big endian check */ switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: break; default: break; } /* channel number check */ switch (runtime->channels) { case 1: break; case 2: break; default: break; } } else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { /* little or big endian check */ switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: break; default: break; } /* channel number check */ switch (runtime->channels) { case 1: break; case 2: break; default: break; } } switch (runtime->rate) { case 16000: PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_256K); break; case 8000: PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_128K); break; default : printk(KERN_ERR "not supported fs: %d \n\r", runtime->rate); break; } return 0; } /* * This must be called before _set_clkdiv and _set_sysclk since McBSP register * cache is initialized here */ static int wmt_pcm_controller_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { return 0; } #ifdef CONFIG_PM static int wmt_pcm_controller_suspend(struct snd_soc_dai *cpu_dai) { return 0; } static int wmt_pcm_controller_resume(struct snd_soc_dai *cpu_dai) { int ret = wmt_pcm_controller_init(); if (ret) { pr_err("Failed to init pcm module: %d\n", ret); return ret; } return 0; } #else #define wmt_pcm_controller_suspend NULL #define wmt_pcm_controller_resume NULL #endif static struct snd_soc_dai_ops wmt_pcm_controller_dai_ops = { .startup = wmt_pcm_controller_dai_startup, .prepare = wmt_pcm_controller_dai_prepare, .shutdown = wmt_pcm_controller_dai_shutdown, .trigger = wmt_pcm_controller_dai_trigger, .hw_params = wmt_pcm_controller_dai_hw_params, .set_fmt = wmt_pcm_controller_dai_set_dai_fmt, }; struct snd_soc_dai_driver wmt_pcm_controller_dai = { .suspend = wmt_pcm_controller_suspend, .resume = wmt_pcm_controller_resume, .playback = { .channels_min = 1, .channels_max = 1, .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { .channels_min = 1, .channels_max = 1, .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &wmt_pcm_controller_dai_ops, }; static int wmt_pcm_controller_probe(struct platform_device *pdev) { int ret = 0; char buf[64]; int varlen = 64; ret = wmt_getsyspara("wmt.audio.pcm", buf, &varlen); if (ret == 0) { sscanf(buf, "%d", &wmt_pcm_module_enable); } ret = wmt_pcm_controller_init(); if (ret) { pr_err("Failed to init pcm module: %d\n", ret); return ret; } /* register with the ASoC layers */ ret = snd_soc_register_dai(&pdev->dev, &wmt_pcm_controller_dai); if (ret) { pr_err("Failed to register DAI: %d\n", ret); return ret; } wmt_pcm_controller_data[0].dma_cfg = dma_device_cfg_table[PCM_TX_DMA_REQ]; wmt_pcm_controller_data[1].dma_cfg = dma_device_cfg_table[PCM_RX_DMA_REQ]; spin_lock_init(&wmt_pcm_controller_data[0].dma_lock); spin_lock_init(&wmt_pcm_controller_data[1].dma_lock); return 0; } static int __devexit wmt_pcm_controller_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&pdev->dev); return 0; } static struct platform_driver wmt_pcm_controller_driver = { .probe = wmt_pcm_controller_probe, .remove = __devexit_p(wmt_pcm_controller_remove), .driver = { .name = "wmt-pcm-controller", .owner = THIS_MODULE, }, }; static int __init wmt_pcm_controller_module_init(void) { return platform_driver_register(&wmt_pcm_controller_driver); } static void __exit wmt_pcm_controller_module_exit(void) { platform_driver_unregister(&wmt_pcm_controller_driver); } module_init(wmt_pcm_controller_module_init); module_exit(wmt_pcm_controller_module_exit); MODULE_AUTHOR("WonderMedia Technologies, Inc."); MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); MODULE_LICENSE("GPL");