From: fang Date: Mon, 22 Nov 2010 09:46:43 +0000 (+0800) Subject: add rk29 i2s and pcm X-Git-Tag: firefly_0821_release~11016^2~12 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e88f4776ddcd01c581e971011aeff3c6fa02d0c1;p=firefly-linux-kernel-4.4.55.git add rk29 i2s and pcm --- diff --git a/sound/soc/rk29/Kconfig b/sound/soc/rk29/Kconfig new file mode 100755 index 000000000000..8fa4d2ee6ee9 --- /dev/null +++ b/sound/soc/rk29/Kconfig @@ -0,0 +1,41 @@ +config SND_RK29_SOC + tristate "SoC Audio for the rockchip RK29 System-on-Chip" + depends on ARCH_RK29 && SND_SOC + select SND_RK29_SOC_I2S + help + Say Y or M if you want to add support for codecs attached to + the ROCKCHIP IIS interface. You will also need + to select the audio interfaces to support below. + +config SND_RK29_SOC_I2S + tristate + +config SND_RK29_SOC_WM8988 + tristate "SoC I2S Audio support for rockchip - WM8988" + depends on SND_RK29_SOC + select SND_RK29_SOC_I2S + select SND_SOC_WM8988 + help + Say Y if you want to add support for SoC audio on rockchip + with the WM8988. + +config SND_RK29_SOC_RK1000 + tristate "SoC I2S Audio support for rockchip - RK1000" + depends on SND_RK29_SOC && RK1000_CONTROL + select SND_RK29_SOC_I2S + select SND_SOC_RK1000 + help + Say Y if you want to add support for SoC audio on rockchip + with the RK1000. + +if SND_RK29_SOC_WM8988 || SND_RK29_SOC_RK1000 || SND_RK29_SOC_WM8994 +choice + prompt "Set i2s type" + + config SND_CODEC_SOC_MASTER + tristate "Codec run in Master" + + config SND_CODEC_SOC_SLAVE + tristate "Codec run in Slave" +endchoice +endif diff --git a/sound/soc/rk29/Makefile b/sound/soc/rk29/Makefile new file mode 100755 index 000000000000..762ffab5a877 --- /dev/null +++ b/sound/soc/rk29/Makefile @@ -0,0 +1,15 @@ +# ROCKCHIP Platform Support +snd-soc-rockchip-objs := rk29_pcm.o +snd-soc-rockchip-i2s-objs := rk29_i2s.o + +obj-$(CONFIG_SND_RK29_SOC) += snd-soc-rockchip.o +obj-$(CONFIG_SND_RK29_SOC_I2S) += snd-soc-rockchip-i2s.o + +# ROCKCHIP Machine Support +snd-soc-wm8988-objs := rk29_wm8988.o +snd-soc-rk1000-objs := rk29_rk1000codec.o +snd-soc-wm8994-objs := rk29_wm8994.o + +obj-$(CONFIG_SND_RK29_SOC_WM8994) += snd-soc-wm8994.o +obj-$(CONFIG_SND_RK29_SOC_WM8988) += snd-soc-wm8988.o +obj-$(CONFIG_SND_RK29_SOC_RK1000) += snd-soc-rk1000.o diff --git a/sound/soc/rk29/rk29_i2s.c b/sound/soc/rk29/rk29_i2s.c new file mode 100755 index 000000000000..bef2a8b4fdd8 --- /dev/null +++ b/sound/soc/rk29/rk29_i2s.c @@ -0,0 +1,583 @@ +/* + * rk29_i2s.c -- ALSA SoC ROCKCHIP IIS Audio Layer Platform driver + * + * Driver for rockchip iis audio + * Copyright (C) 2009 lhh + * + * + * 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 +#include +#include +#include + + +#include "rk29_pcm.h" +#include "rk29_i2s.h" + + +#if 0 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) do { } while (0) +#endif + +#define pheadi2s ((pI2S_REG)(i2s->regs)) + +#define MAX_I2S 2 + +struct rk29_i2s_info { + struct device *dev; + void __iomem *regs; + + u32 feature; + + struct clk *iis_clk; + struct clk *iis_pclk; + + unsigned char master; + + struct rockchip_pcm_dma_params *dma_playback; + struct rockchip_pcm_dma_params *dma_capture; + + u32 suspend_iismod; + u32 suspend_iiscon; + u32 suspend_iispsr; +}; + +static struct rk29_dma_client rk29_dma_client_out = { + .name = "I2S PCM Stereo Out" +}; + +static struct rk29_dma_client rk29_dma_client_in = { + .name = "I2S PCM Stereo In" +}; + +static inline struct rk29_i2s_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + +static struct rockchip_pcm_dma_params rk29_i2s_pcm_stereo_out[MAX_I2S]; +static struct rockchip_pcm_dma_params rk29_i2s_pcm_stereo_in[MAX_I2S]; +static struct rk29_i2s_info rk29_i2s[MAX_I2S]; + +struct snd_soc_dai rk29_i2s_dai[MAX_I2S]; +EXPORT_SYMBOL_GPL(rk29_i2s_dai); + +/* +static struct rockchip_pcm_dma_params rockchip_i2s_pcm_stereo_out[MAX_I2S] = { + [0] = { + .client = &rk29_dma_client_out, + .channel = DMACH_I2S_2CH_TX, ///0, //DMACH_I2S_OUT, + .dma_addr = RK29_I2S_2CH_PHYS + I2S_TXR_BUFF, + .dma_size = 4, + }, + [1] = { + .client = &rk29_dma_client_out, + .channel = DMACH_I2S_8CH_TX, ///0, //DMACH_I2S_OUT, + .dma_addr = RK29_I2S_8CH_PHYS + I2S_TXR_BUFF, + .dma_size = 4, + }, +}; + +static struct rockchip_pcm_dma_params rockchip_i2s_pcm_stereo_in[MAX_I2S] = { + [0] = { + .client = &rk29_dma_client_in, + .channel = DMACH_I2S_2CH_RX, ///1, //DMACH_I2S_IN, + .dma_addr = RK29_I2S_2CH_PHYS + I2S_RXR_BUFF, + .dma_size = 4, + }, + [1] = { + .client = &rk29_dma_client_in, + .channel = DMACH_I2S_8CH_RX, ///1, //DMACH_I2S_IN, + .dma_addr = RK29_I2S_8CH_PHYS + I2S_RXR_BUFF, + .dma_size = 4, + }, +}; +*/ + + + + +/* + *Turn on or off the transmission path. + */ +static void rockchip_snd_txctrl(struct rk29_i2s_info *i2s, int on) +{ + //struct rk29_i2s_info *i2s = &rockchip_i2s; + u32 opr,fifosts; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + opr = readl(&(pheadi2s->I2S_OPR)); + fifosts = readl(&(pheadi2s->I2S_FIFOSTS)); + fifosts = (fifosts & (~(0x0f<<16))) | TX_HALF_FULL | RX_HALF_FULL; + writel(fifosts, &(pheadi2s->I2S_FIFOSTS)); + if (on) + { + opr = (opr & (~(RESET_RX | I2S_DMA_REQ2_DISABLE | TX_START | RX_START))) | (RESET_TX | I2S_DMA_REQ1_DISABLE); + writel(opr, &(pheadi2s->I2S_OPR)); + udelay(5); + opr = (opr & (~(I2S_DMA_REQ1_DISABLE | I2S_DMA_REQ1_RX_ENABLE | RX_START))) | I2S_DMA_REQ1_ENABLE | I2S_DMA_REQ1_TX_ENABLE | TX_START; + writel(opr, &(pheadi2s->I2S_OPR)); + } + else + { + opr = (opr & (~TX_START)) | I2S_DMA_REQ1_DISABLE; + writel(opr, &(pheadi2s->I2S_OPR)); + } +} + +static void rockchip_snd_rxctrl(struct rk29_i2s_info *i2s, int on) +{ + //struct rk29_i2s_info *i2s = &rockchip_i2s; + u32 opr,fifosts; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + opr = readl(&(pheadi2s->I2S_OPR)); + fifosts = readl(&(pheadi2s->I2S_FIFOSTS)); + fifosts = (fifosts & (~(0x0f<<16))) | TX_HALF_FULL | RX_HALF_FULL; + writel(fifosts, &(pheadi2s->I2S_FIFOSTS)); + if (on) + { + opr = (opr & (~(RESET_TX | I2S_DMA_REQ1_DISABLE| TX_START | RX_START))) | (RESET_RX | I2S_DMA_REQ2_DISABLE); + writel(opr, &(pheadi2s->I2S_OPR)); + udelay(5); + opr = (opr & (~(I2S_DMA_REQ2_DISABLE | I2S_DMA_REQ2_TX_ENABLE | TX_START))) | I2S_DMA_REQ2_ENABLE | I2S_DMA_REQ2_RX_ENABLE | RX_START; + writel(opr, &(pheadi2s->I2S_OPR)); + } + else + { + opr = (opr & (~RX_START)) | I2S_DMA_REQ2_DISABLE; + writel(opr, &(pheadi2s->I2S_OPR)); + } +} + +/* + * Set Rockchip I2S DAI format + */ +static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk29_i2s_info *i2s = to_info(cpu_dai); + u32 tx_ctl,rx_ctl; + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + tx_ctl = readl(&(pheadi2s->I2S_TXCTL)); + tx_ctl &= (~MASTER_MODE); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tx_ctl |= MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tx_ctl |= SLAVE_MODE; + break; + default: + DBG("unknwon master/slave format\n"); + return -EINVAL; + } + tx_ctl &= ~IISMOD_SDF_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + tx_ctl |= RIGHT_JUSTIFIED; + break; + case SND_SOC_DAIFMT_LEFT_J: + tx_ctl |= LEFT_JUSTIFIED; + break; + case SND_SOC_DAIFMT_I2S: + tx_ctl |= I2S_MODE; + break; + default: + DBG("Unknown data format\n"); + return -EINVAL; + } + tx_ctl = tx_ctl & (~(0xff<<8)) & (~(0x03<<16)) & (~(1<<3)); + tx_ctl = tx_ctl | OVERSAMPLING_RATE_64FS | SCK_RATE4 | STEREO_MODE; + writel(tx_ctl, &(pheadi2s->I2S_TXCTL)); + rx_ctl = tx_ctl | CLEAR_RXFIFO; + writel(rx_ctl, &(pheadi2s->I2S_RXCTL)); + return 0; +} + +static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai = rtd->dai; + struct rk29_i2s_info *i2s = to_info(dai->cpu_dai); + u32 iismod; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*by Vincent Hsiung for EQ Vol Change*/ + #define HW_PARAMS_FLAG_EQVOL_ON 0x21 + #define HW_PARAMS_FLAG_EQVOL_OFF 0x22 + if ((params->flags == HW_PARAMS_FLAG_EQVOL_ON)||(params->flags == HW_PARAMS_FLAG_EQVOL_OFF)) + { + return 0; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->cpu_dai->dma_data = i2s->dma_playback->client; + else + dai->cpu_dai->dma_data = i2s->dma_capture->client; + + /* Working copies of register */ + iismod = readl(&(pheadi2s->I2S_TXCTL)); + iismod &= (~SAMPLE_DATA_MASK); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= SAMPLE_DATA_8bit; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod |= SAMPLE_DATA_16bit; + break; + } + /*stereo mode MCLK/SCK=4*/ + iismod = iismod & (~(0xff<<8)) & (~(0x03<<16)) & (~(1<<3)); + iismod = iismod | OVERSAMPLING_RATE_64FS | SCK_RATE4 | STEREO_MODE; + + writel(iismod, &(pheadi2s->I2S_TXCTL)); + iismod = iismod | CLEAR_RXFIFO; + writel(iismod, &(pheadi2s->I2S_RXCTL)); + return 0; +} + + +static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rk29_i2s_info *i2s = to_info(rtd->dai->cpu_dai); + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 1); + else + rockchip_snd_txctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 0); + else + rockchip_snd_txctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +/* + * Set Rockchip Clock source + */ +static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*add scu clk source and enable clk*/ + + return 0; +} + +/* + * Set Rockchip Clock dividers + */ +static int rockchip_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + //u32 reg; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*when i2s in master mode ,must set codec pll div*/ + switch (div_id) { + case ROCKCHIP_DIV_BCLK: + //reg = readl(&(pheadi2s->I2S_TXCTL)) & ~S3C2410_IISMOD_FS_MASK; + //writel(reg | div, &(pheadi2s->I2S_TXCTL)); + break; + case ROCKCHIP_DIV_MCLK: + //reg = readl(rockchip_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); + //writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case ROCKCHIP_DIV_PRESCALER: + //writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); + //reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + //writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); + break; + default: + return -EINVAL; + } + return 0; +} + +static int rockchip_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + + +/* + * To avoid duplicating clock code, allow machine driver to + * get the clockrate from here. + */ +u32 rockchip_i2s_get_clockrate(void) +{ + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + return 0; ///clk_get_rate(s3c24xx_i2s.iis_clk); +} +EXPORT_SYMBOL_GPL(rockchip_i2s_get_clockrate); + +#ifdef CONFIG_PM +int rockchip_i2s_suspend(struct snd_soc_dai *cpu_dai) +{ + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + //clk_disable(clk); + return 0; +} + +int rockchip_i2s_resume(struct snd_soc_dai *cpu_dai) +{ + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + //clk_enable(clk); + return 0; +} +#else +#define rockchip_i2s_suspend NULL +#define rockchip_i2s_resume NULL +#endif + +#define ROCKCHIP_I2S_RATES SNDRV_PCM_RATE_48000 + +static struct snd_soc_dai_ops rockchip_i2s_dai_ops = { + .trigger = rockchip_i2s_trigger, + .hw_params = rockchip_i2s_hw_params, + .set_fmt = rockchip_i2s_set_fmt, + .set_clkdiv = rockchip_i2s_set_clkdiv, + .set_sysclk = rockchip_i2s_set_sysclk, +}; + +static int rockchip_i2s_dai_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + return 0; +} + +static int rk29_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai, + struct rk29_i2s_info *i2s, + unsigned long base) +{ + struct device *dev = &pdev->dev; + unsigned int iismod; + struct resource *res; + + i2s->dev = dev; + + /* record our i2s structure for later use in the callbacks */ + dai->private_data = i2s; + + if (!base) { + res = platform_get_resource(pdev, + IORESOURCE_MEM, + 0); + if (!res) { + dev_err(dev, "Unable to get register resource\n"); + return -ENXIO; + } + + if (!request_mem_region(res->start, resource_size(res), + "rk29-i2s")) { + dev_err(dev, "Unable to request register region\n"); + return -EBUSY; + } + + base = res->start; + } + + i2s->regs = ioremap(base, resource_size(res)); + if (i2s->regs == NULL) { + dev_err(dev, "cannot ioremap registers\n"); + return -ENXIO; + } + + i2s->iis_pclk = clk_get(dev, "i2s"); + if (IS_ERR(i2s->iis_pclk)) { + dev_err(dev, "failed to get iis_clock\n"); + iounmap(i2s->regs); + return -ENOENT; + } + + clk_enable(i2s->iis_pclk); + + /* Mark ourselves as in TXRX mode so we can run through our cleanup + * process without warnings. */ + + rockchip_snd_txctrl(i2s, 0); + rockchip_snd_rxctrl(i2s, 0); + + return 0; +} + +static int rk29_i2s_register_dai(struct snd_soc_dai *dai) +{ + struct snd_soc_dai_ops *ops = dai->ops; + + ops->trigger = rockchip_i2s_trigger; + if (!ops->hw_params) + ops->hw_params = rockchip_i2s_hw_params; + ops->set_fmt = rockchip_i2s_set_fmt; + ops->set_clkdiv = rockchip_i2s_set_clkdiv; + ops->set_sysclk = rockchip_set_sysclk; + + dai->suspend = rockchip_i2s_suspend; + dai->resume = rockchip_i2s_resume; + + return snd_soc_register_dai(dai); +} + +static int __devinit rockchip_i2s_probe(struct platform_device *pdev) +{ + struct rk29_i2s_info *i2s; + struct snd_soc_dai *dai; + int ret; + + if(pdev->id >= MAX_I2S) { + dev_err(&pdev->dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + i2s = &rk29_i2s[pdev->id]; + dai = &rk29_i2s_dai[pdev->id]; + dai->dev = &pdev->dev; + dai->name = "rk29-i2s"; + dai->id = pdev->id; + dai->symmetric_rates = 1; + dai->playback.channels_min = 2; + dai->playback.channels_max = 2; + dai->playback.rates = ROCKCHIP_I2S_RATES; + dai->playback.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE; + dai->capture.channels_min = 2; + dai->capture.channels_max = 2; + dai->capture.rates = ROCKCHIP_I2S_RATES; + dai->capture.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE; + dai->probe = rockchip_i2s_dai_probe; + dai->ops = &rockchip_i2s_dai_ops; + + //i2s->feature |= S3C_FEATURE_CDCLKCON; + + i2s->dma_capture = &rk29_i2s_pcm_stereo_in[pdev->id]; + i2s->dma_playback = &rk29_i2s_pcm_stereo_out[pdev->id]; + + if (pdev->id == 0) { + i2s->dma_capture->channel = DMACH_I2S_2CH_RX; + i2s->dma_capture->dma_addr = RK29_I2S_2CH_PHYS + I2S_RXR_BUFF; + i2s->dma_playback->channel = DMACH_I2S_2CH_TX; + i2s->dma_playback->dma_addr = RK29_I2S_2CH_PHYS + I2S_TXR_BUFF; + } else { + i2s->dma_capture->channel = DMACH_I2S_8CH_RX; + i2s->dma_capture->dma_addr = RK29_I2S_8CH_PHYS + I2S_RXR_BUFF; + i2s->dma_playback->channel = DMACH_I2S_8CH_TX; + i2s->dma_playback->dma_addr = RK29_I2S_8CH_PHYS + I2S_TXR_BUFF; + } + + i2s->dma_capture->client = &rk29_dma_client_in; + i2s->dma_capture->dma_size = 4; + i2s->dma_playback->client = &rk29_dma_client_out; + i2s->dma_playback->dma_size = 4; + + i2s->iis_clk = clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s->iis_clk)) { + dev_err(&pdev->dev, "failed to get i2s clk\n"); + ret = PTR_ERR(i2s->iis_clk); + goto err; + } + + clk_enable(i2s->iis_clk); + + ret = rk29_i2s_probe(pdev, dai, i2s, 0); + if (ret) + goto err_clk; + + ret = rk29_i2s_register_dai(dai); + if (ret != 0) + goto err_i2sv2; + + return 0; + +err_i2sv2: + /* Not implemented for I2Sv2 core yet */ +err_clk: + clk_put(i2s->iis_clk); +err: + return ret; +} + + +static int __devexit rockchip_i2s_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&rk29_i2s_dai); + + return 0; +} + +static struct platform_driver rockchip_i2s_driver = { + .probe = rockchip_i2s_probe, + .remove = __devexit_p(rockchip_i2s_remove), + .driver = { + .name = "rk29-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init rockchip_i2s_init(void) +{ + printk(KERN_INFO + "Enter Func = %s\n", __func__); + return platform_driver_register(&rockchip_i2s_driver); +} +module_init(rockchip_i2s_init); + +static void __exit rockchip_i2s_exit(void) +{ + platform_driver_unregister(&rockchip_i2s_driver); +} +module_exit(rockchip_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("lhh lhh@rock-chips.com"); +MODULE_DESCRIPTION("ROCKCHIP IIS ASoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/rk29/rk29_i2s.h b/sound/soc/rk29/rk29_i2s.h new file mode 100755 index 000000000000..310576f59913 --- /dev/null +++ b/sound/soc/rk29/rk29_i2s.h @@ -0,0 +1,83 @@ +/* + * rockchip-iis.h - ALSA IIS interface for the Rockchip rk28 SoC + * + * Driver for rockchip iis audio + * Copyright (C) 2009 lhh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ROCKCHIP_IIS_H +#define _ROCKCHIP_IIS_H + +//I2S_OPR +#define RESET_TX (1<<17) +#define RESET_RX (1<<16) +#define I2S_DMA_REQ1_DISABLE (1<<6) +#define I2S_DMA_REQ1_ENABLE (0) +#define I2S_DMA_REQ2_DISABLE (1<<5) +#define I2S_DMA_REQ2_ENABLE (0) +#define I2S_DMA_REQ1_TX_ENABLE (0) +#define I2S_DMA_REQ1_RX_ENABLE (1<<4) +#define I2S_DMA_REQ2_TX_ENABLE (0) +#define I2S_DMA_REQ2_RX_ENABLE (1<<3) +#define TX_START (1<<1) +#define RX_START (1) + + + +//I2S_TXCTL I2S_RXCTL +#define CLEAR_RXFIFO (1<<24) +#define TRAN_DEVICES0 (0) +#define TRAN_DEVICES1 (1<<18) +#define TRAN_DEVICES2 (2<<18) +#define TRAN_DEVICES3 (3<<18) +#define OVERSAMPLING_RATE_32FS (0) +#define OVERSAMPLING_RATE_64FS (1<<16) +#define OVERSAMPLING_RATE_128FS (2<<16) +#define SCK_RATE2 (0x02<<8) +#define SCK_RATE4 (0x04<<8) +#define SCK_RATE8 (0x08<<8) +#define SAMPLE_DATA_8bit (0) +#define SAMPLE_DATA_16bit (1<<4) +#define SAMPLE_DATA_MASK (3<<4) +#define MONO_MODE (1<<3) +#define STEREO_MODE (0) +#define I2S_MODE (0) +#define LEFT_JUSTIFIED (1<<1) +#define RIGHT_JUSTIFIED (2<<1) +#define IISMOD_SDF_MASK (3<<1) +#define MASTER_MODE (1) +#define SLAVE_MODE (0) + +//I2S_FIFOSTS +#define TX_HALF_FULL (1<<18) +#define RX_HALF_FULL (1<<16) + +#define I2S_TXR_BUFF 0x04 +#define I2S_RXR_BUFF 0x08 + +/* Clock dividers */ +#define ROCKCHIP_DIV_MCLK 0 +#define ROCKCHIP_DIV_BCLK 1 +#define ROCKCHIP_DIV_PRESCALER 2 + +//I2S Registers +typedef volatile struct tagIIS_STRUCT +{ + unsigned int I2S_OPR; + unsigned int I2S_TXR; + unsigned int I2S_RXR; + unsigned int I2S_TXCTL; + unsigned int I2S_RXCTL; + unsigned int I2S_FIFOSTS; + unsigned int I2S_IER; + unsigned int I2S_ISR; +}I2S_REG,*pI2S_REG; + +extern struct snd_soc_dai rk29_i2s_dai[]; +//extern void rockchip_add_device_i2s(void); +#endif /* _ROCKCHIP_IIS_H */ + diff --git a/sound/soc/rk29/rk29_pcm.c b/sound/soc/rk29/rk29_pcm.c new file mode 100755 index 000000000000..d11be2cf844c --- /dev/null +++ b/sound/soc/rk29/rk29_pcm.c @@ -0,0 +1,682 @@ +/* + * rk29_pcm.c -- ALSA SoC ROCKCHIP PCM Audio Layer Platform driver + * + * Driver for rockchip pcm audio + * Copyright (C) 2009 lhh + * + * + * 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 "rk29_pcm.h" + +#ifdef CONFIG_ANDROID_POWER +#include +static android_suspend_lock_t audio_lock; +#endif + +#if 0 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) do { } while (0) +#endif + + +static const struct snd_pcm_hardware rockchip_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S8, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = 128*1024, + .period_bytes_min = 64, ///PAGE_SIZE, + .period_bytes_max = 2047*4,///PAGE_SIZE*2, + .periods_min = 3,///2, + .periods_max = 128, + .fifo_size = 16, +}; + + +struct rockchip_dma_buf_set { + struct rockchip_dma_buf_set *next; + struct scatterlist sg; +}; + +struct rockchip_runtime_data { + spinlock_t lock; + int state; + int transfer_first; + unsigned int dma_loaded; + unsigned int dma_limit; + unsigned int dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + struct rockchip_pcm_dma_params *params; + struct rockchip_dma_buf_set *curr; /* current dma buffer set */ + struct rockchip_dma_buf_set *next; /* next buffer set to load */ + struct rockchip_dma_buf_set *end; /* end of queue set*/ +}; + + +/* rockchip__dma_buf_enqueue + * + *queue an given buffer for dma transfer set. + *data the physical address of the buffer data + *size the size of the buffer in bytes +*/ +static int rockchip_dma_buffer_set_enqueue(struct rockchip_runtime_data *prtd, dma_addr_t data, int size) +{ + struct rockchip_dma_buf_set *sg_buf; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + sg_buf = kzalloc(sizeof(struct rockchip_dma_buf_set), GFP_ATOMIC);/* ddl@rock-chips.com:GFP_KERNEL->GFP_ATOMIC */ + + if (sg_buf == NULL) { + DBG("scatter sg buffer allocate failed,no memory!\n"); + return -ENOMEM; + } + sg_buf->next = NULL; + sg_buf->sg.dma_address = data; + sg_buf->sg.length = size/4; ////4; + if( prtd->curr == NULL) { + prtd->curr = sg_buf; + prtd->end = sg_buf; + prtd->next = NULL; + } else { + if (prtd->end == NULL) + DBG("prtd->end is NULL\n"); + prtd->end->next = sg_buf; + prtd->end = sg_buf; + } + /* if necessary, update the next buffer field */ + if (prtd->next == NULL) + prtd->next = sg_buf; + return 0; +} + +void rockchip_pcm_dma_irq(s32 ch, void *data); + + + +void audio_start_dma(struct snd_pcm_substream *substream, int mode) +{ + struct rockchip_runtime_data *prtd; + unsigned long flags; + struct rockchip_dma_buf_set *sg_buf; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + prtd = substream->runtime->private_data; + + switch (mode) { + case DMA_MODE_WRITE: + if (prtd->transfer_first == 1) { + prtd->transfer_first = 0; + } else { + sg_buf = prtd->curr; + if (sg_buf != NULL) { + prtd->curr = sg_buf->next; + prtd->next = sg_buf->next; + sg_buf->next = NULL; + kfree(sg_buf); + sg_buf = NULL; + } + } + + sg_buf = prtd->next; + DBG("Enter::%s----%d---length=%x---dma_address=%x\n",__FUNCTION__,__LINE__,sg_buf->sg.length,sg_buf->sg.dma_address); + if (sg_buf) { + spin_lock_irqsave(&prtd->lock, flags); + disable_dma(prtd->params->channel); + //set_dma_sg(prtd->params->channel, &(sg_buf->sg), 1); + set_dma_mode(prtd->params->channel, DMA_MODE_WRITE); + set_dma_handler(prtd->params->channel, rockchip_pcm_dma_irq, substream, DMA_IRQ_RIGHTNOW_MODE); + __set_dma_addr(prtd->params->channel, (void *)(sg_buf->sg.dma_address)); + set_dma_count(prtd->params->channel, sg_buf->sg.length); + enable_dma(prtd->params->channel); + spin_unlock_irqrestore(&prtd->lock, flags); + } else { + DBG("next buffer is NULL for playback\n"); + return; + } + break; + case DMA_MODE_READ: + if (prtd->transfer_first == 1) { + prtd->transfer_first = 0; + } else { + sg_buf = prtd->curr; + if (sg_buf != NULL) { + prtd->curr = sg_buf->next; + prtd->next = sg_buf->next; + sg_buf->next = NULL; + kfree(sg_buf); + sg_buf = NULL; + } + } + + sg_buf = prtd->next; + if (sg_buf) { + spin_lock_irqsave(&prtd->lock, flags); + disable_dma(prtd->params->channel); + //set_dma_sg(prtd->params->channel, &(sg_buf->sg), 1); + set_dma_mode(prtd->params->channel, DMA_MODE_READ); + set_dma_handler(prtd->params->channel, rockchip_pcm_dma_irq, substream, DMA_IRQ_RIGHTNOW_MODE); + __set_dma_addr(prtd->params->channel, (void *)(sg_buf->sg.dma_address)); + set_dma_count(prtd->params->channel, sg_buf->sg.length); + enable_dma(prtd->params->channel); + spin_unlock_irqrestore(&prtd->lock, flags); + } else { + DBG("next buffer is NULL for capture\n"); + return; + } + break; + } +} + +/* rockchip_pcm_enqueue + * + * place a dma buffer onto the queue for the dma system + * to handle. +*/ +static void rockchip_pcm_enqueue(struct snd_pcm_substream *substream) +{ + struct rockchip_runtime_data *prtd = substream->runtime->private_data; + dma_addr_t pos = prtd->dma_pos; + int ret; + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + while (prtd->dma_loaded < prtd->dma_limit) { + unsigned long len = prtd->dma_period; + + DBG("dma_loaded: %d\n", prtd->dma_loaded); + if ((pos + len) > prtd->dma_end) { + len = prtd->dma_end - pos; + } + //ret = rockchip_dma_buffer_set_enqueue(prtd, pos, len); + ret = rk29_dma_enqueue(prtd->params->channel, + substream, pos, len); + if (ret == 0) { + prtd->dma_loaded++; + pos += prtd->dma_period; + if (pos >= prtd->dma_end) + pos = prtd->dma_start; + } else + break; + } + + prtd->dma_pos = pos; +} + +void rockchip_pcm_dma_irq(s32 ch, void *data) +{ + struct snd_pcm_substream *substream = data; + struct rockchip_runtime_data *prtd; + unsigned long flags; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + prtd = substream->runtime->private_data; + if (substream) + snd_pcm_period_elapsed(substream); + spin_lock(&prtd->lock); + prtd->dma_loaded--; + if (prtd->state & ST_RUNNING) { + rockchip_pcm_enqueue(substream); + } + spin_unlock(&prtd->lock); + local_irq_save(flags); + if (prtd->state & ST_RUNNING) { + if (prtd->dma_loaded) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_start_dma(substream, DMA_MODE_WRITE); + else + audio_start_dma(substream, DMA_MODE_READ); + } + } + local_irq_restore(flags); +} + + +void rk29_audio_buffdone(struct rk29_dma_chan *ch, + void *dev_id, int size, + enum rk29_dma_buffresult result) +{ + struct snd_pcm_substream *substream = dev_id; + struct rockchip_runtime_data *prtd; + unsigned long flags; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + prtd = substream->runtime->private_data; + if (substream) + snd_pcm_period_elapsed(substream); + spin_lock(&prtd->lock); + prtd->dma_loaded--; + if (prtd->state & ST_RUNNING) { + rockchip_pcm_enqueue(substream); + } + spin_unlock(&prtd->lock); + +} + +static int rockchip_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rockchip_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; + unsigned long totbytes = params_buffer_bytes(params); + int ret = 0; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*by Vincent Hsiung for EQ Vol Change*/ + #define HW_PARAMS_FLAG_EQVOL_ON 0x21 + #define HW_PARAMS_FLAG_EQVOL_OFF 0x22 + if ((params->flags == HW_PARAMS_FLAG_EQVOL_ON)||(params->flags == HW_PARAMS_FLAG_EQVOL_OFF)) + { + return 0; + } + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!dma) + return 0; + + /* this may get called several times by oss emulation + * with different params -HW */ + if (prtd->params == NULL) { + /* prepare DMA */ + prtd->params = dma; + + DBG("params %p, client %p, channel %d\n", prtd->params, + prtd->params->client, prtd->params->channel); + + //ret = request_dma(prtd->params->channel, "i2s"); ///prtd->params->client->name); + ret = rk29_dma_request(prtd->params->channel, prtd->params->client, NULL); +/* + if(ret){ + for(prtd->params->channel=5;prtd->params->channel>0;prtd->params->channel--){ + ret = request_dma(prtd->params->channel, "i2s"); + if(!ret)break; + } + } +*/ + if (ret) { + DBG(KERN_ERR "failed to get dma channel\n"); + return ret; + } + + } + + rk29_dma_set_buffdone_fn(prtd->params->channel, rk29_audio_buffdone); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totbytes; + + spin_lock_irq(&prtd->lock); + prtd->dma_loaded = 0; + prtd->dma_limit = runtime->hw.periods_min; + 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 + totbytes; + prtd->transfer_first = 1; + prtd->curr = NULL; + prtd->next = NULL; + prtd->end = NULL; + spin_unlock_irq(&prtd->lock); + return 0; +} + +static int rockchip_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct rockchip_runtime_data *prtd = substream->runtime->private_data; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /* TODO - do we need to ensure DMA flushed */ + snd_pcm_set_runtime_buffer(substream, NULL); + + if (prtd->params) { + //free_dma(prtd->params->channel); + rk29_dma_free(prtd->params->channel, prtd->params->client); + prtd->params = NULL; + } + + return 0; +} + +static int rockchip_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct rockchip_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->params) + return 0; + + if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rk29_dma_devconfig(prtd->params->channel, + RK29_DMASRC_MEM, + prtd->params->dma_addr); + }else{ + rk29_dma_devconfig(prtd->params->channel, + RK29_DMASRC_HW, + prtd->params->dma_addr); + } + + rk29_dma_config(prtd->params->channel, + prtd->params->dma_size); + rk29_dma_ctrl(prtd->params, RK29_DMAOP_FLUSH); + + prtd->dma_loaded = 0; + prtd->dma_pos = prtd->dma_start; + + /* enqueue dma buffers */ + rockchip_pcm_enqueue(substream); + return ret; +} + +static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct rockchip_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + /**************add by qiuen for volume*****/ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai; + int vol = 0; + int streamType = 0; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + if(cmd==SNDRV_PCM_TRIGGER_VOLUME){ + vol = substream->number % 100; + streamType = (substream->number / 100) % 100; + DBG("enter:vol=%d,streamType=%d\n",vol,streamType); + pCodec_dai->ops->set_volume(streamType, vol); + } + /****************************************************/ + spin_lock(&prtd->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + DBG(" START \n"); + prtd->state |= ST_RUNNING; + rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START); + /* + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + audio_start_dma(substream, DMA_MODE_WRITE); + } else { + audio_start_dma(substream, DMA_MODE_READ); + } + */ +#ifdef CONFIG_ANDROID_POWER + android_lock_suspend(&audio_lock); + DBG("%s::start audio , lock system suspend\n" , __func__ ); +#endif + break; + case SNDRV_PCM_TRIGGER_RESUME: + DBG(" RESUME \n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + DBG(" RESTART \n"); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + DBG(" STOPS \n"); + prtd->state &= ~ST_RUNNING; + rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP); + //disable_dma(prtd->params->channel); +#ifdef CONFIG_ANDROID_POWER + android_unlock_suspend(&audio_lock ); + DBG("%s::stop audio , unlock system suspend\n" , __func__ ); +#endif + + break; + default: + ret = -EINVAL; + break; + } + + spin_unlock(&prtd->lock); + return ret; +} + + +static snd_pcm_uframes_t +rockchip_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_runtime_data *prtd = runtime->private_data; + unsigned long res; + dma_addr_t src, dst; + snd_pcm_uframes_t ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + spin_lock(&prtd->lock); + + //get_dma_position(prtd->params->channel, &src, &dst); + rk29_dma_getposition(prtd->params->channel, &src, &dst); + //dma_getposition(prtd->params->channel, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - prtd->dma_start; + else + res = src - prtd->dma_start; + + spin_unlock(&prtd->lock); + + DBG("Pointer %x %x\n",src,dst); + + ret = bytes_to_frames(runtime, res); + if (ret == runtime->buffer_size) + ret = 0; + return ret; +} + + +static int rockchip_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_runtime_data *prtd; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware); + + prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + return 0; +} + +static int rockchip_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rockchip_runtime_data *prtd = runtime->private_data; + struct rockchip_dma_buf_set *sg_buf = NULL; + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + if (!prtd) + DBG("rockchip_pcm_close called with prtd == NULL\n"); + if (prtd) + sg_buf = prtd->curr; + + while (sg_buf != NULL) { + prtd->curr = sg_buf->next; + prtd->next = sg_buf->next; + sg_buf->next = NULL; + kfree(sg_buf); + sg_buf = NULL; + sg_buf = prtd->curr; + } + kfree(prtd); + + return 0; +} + +static int rockchip_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops rockchip_pcm_ops = { + .open = rockchip_pcm_open, + .close = rockchip_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = rockchip_pcm_hw_params, + .hw_free = rockchip_pcm_hw_free, + .prepare = rockchip_pcm_prepare, + .trigger = rockchip_pcm_trigger, + .pointer = rockchip_pcm_pointer, + .mmap = rockchip_pcm_mmap, +}; + +static int rockchip_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 = rockchip_pcm_hardware.buffer_bytes_max; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + 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 void rockchip_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + 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 u64 rockchip_pcm_dmamask = DMA_BIT_MASK(32); + +static int rockchip_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret = 0; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + +#ifdef CONFIG_ANDROID_POWER + audio_lock.name = "rk-audio"; + android_init_suspend_lock(&audio_lock); +#endif + + if (!card->dev->dma_mask) + card->dev->dma_mask = &rockchip_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = rockchip_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = rockchip_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform rk29_soc_platform = { + .name = "rockchip-audio", + .pcm_ops = &rockchip_pcm_ops, + .pcm_new = rockchip_pcm_new, + .pcm_free = rockchip_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(rk29_soc_platform); + +static int __init rockchip_soc_platform_init(void) +{ + return snd_soc_register_platform(&rk29_soc_platform); +} +module_init(rockchip_soc_platform_init); + +static void __exit rockchip_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&rk29_soc_platform); +} +module_exit(rockchip_soc_platform_exit); + +/* Module information */ +MODULE_AUTHOR("lhh lhh@rock-chips.com"); +MODULE_DESCRIPTION("ROCKCHIP PCM ASoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/rk29/rk29_pcm.h b/sound/soc/rk29/rk29_pcm.h new file mode 100755 index 000000000000..8c5932409e42 --- /dev/null +++ b/sound/soc/rk29/rk29_pcm.h @@ -0,0 +1,35 @@ +/* + * rockchip-pcm.h - ALSA PCM interface for the Rockchip rk28 SoC + * + * Driver for rockchip iis audio + * Copyright (C) 2009 lhh + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ROCKCHIP_PCM_H +#define _ROCKCHIP_PCM_H + +#include + +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + +/* dma buffer */ +struct rockchip_dma_client { + char *name; +}; + +struct rockchip_pcm_dma_params { + struct rockchip_dma_client *client; /* stream identifier */ + int channel; /* Channel ID */ + dma_addr_t dma_addr; + int dma_size; /* Size of the DMA transfer */ +}; + +extern struct snd_soc_platform rk29_soc_platform; + +#endif /* _ROCKCHIP_PCM_H */ diff --git a/sound/soc/rk29/rk29_rk1000codec.c b/sound/soc/rk29/rk29_rk1000codec.c new file mode 100644 index 000000000000..790732eb3d22 --- /dev/null +++ b/sound/soc/rk29/rk29_rk1000codec.c @@ -0,0 +1,186 @@ +/* + * rk29_wm8988.c -- SoC audio for rockchip + * + * Driver for rockchip wm8988 audio + * Copyright (C) 2009 lhh + * + * 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 "../codecs/rk1000_codec.h" +#include "rk29_pcm.h" +#include "rk29_i2s.h" + +#if 0 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif + +static int rk29_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*by Vincent Hsiung for EQ Vol Change*/ + #define HW_PARAMS_FLAG_EQVOL_ON 0x21 + #define HW_PARAMS_FLAG_EQVOL_OFF 0x22 + if ((params->flags == HW_PARAMS_FLAG_EQVOL_ON)||(params->flags == HW_PARAMS_FLAG_EQVOL_OFF)) + { + ret = codec_dai->ops->hw_params(substream, params, codec_dai); //by Vincent + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + } + else + { + /* set codec DAI configuration */ + #if defined (CONFIG_SND_ROCKCHIP_SOC_MASTER) + ret = codec_dai->ops->set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + #if defined (CONFIG_SND_ROCKCHIP_SOC_SLAVE) + ret = codec_dai->ops->set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ); + #endif + if (ret < 0) + return ret; + /* set cpu DAI configuration */ + #if defined (CONFIG_SND_ROCKCHIP_SOC_MASTER) + ret = cpu_dai->ops->set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + #endif + #if defined (CONFIG_SND_ROCKCHIP_SOC_SLAVE) + ret = cpu_dai->ops->set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rk29_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out", NULL), + SND_SOC_DAPM_LINE("Line in", NULL), + SND_SOC_DAPM_MIC("Micn", NULL), + SND_SOC_DAPM_MIC("Micp", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[]= { + + {"Audio Out", NULL, "LOUT1"}, + {"Audio Out", NULL, "ROUT1"}, + {"Line in", NULL, "RINPUT1"}, + {"Line in", NULL, "LINPUT1"}, + {"Micn", NULL, "RINPUT2"}, + {"Micp", NULL, "LINPUT2"}, +}; + +/* + * Logic for a rk1000 codec as connected on a rockchip board. + */ +static int rk29_rk1000_codec_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dai *codec_dai = &codec->dai[0]; + int ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 12000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Failed to set WM8988 SYSCLK: %d\n", ret); + return ret; + } + + /* Add specific widgets */ + snd_soc_dapm_new_controls(codec, rk29_dapm_widgets, + ARRAY_SIZE(rk29_dapm_widgets)); + + /* Set up specific audio path audio_mapnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_ops rk29_ops = { + .hw_params = rk29_hw_params, +}; + +static struct snd_soc_dai_link rk29_dai = { + .name = "RK1000_CODEC", + .stream_name = "RK1000 CODEC PCM", + .cpu_dai = &rk29_i2s_dai, + .codec_dai = &rk1000_codec_dai, + .init = rk29_rk1000_codec_init, + .ops = &rk29_ops, +}; + +static struct snd_soc_card snd_soc_card_rk29 = { + .name = "RK1000_CODEC", + .platform = &rk29_soc_platform, + .dai_link = &rk29_dai, + .num_links = 1, +}; + + +static struct snd_soc_device rk29_snd_devdata = { + .card = &snd_soc_card_rk29, + .codec_dev = &soc_codec_dev_rk1000_codec, +}; + +static struct platform_device *rk29_snd_device; + +static int __init audio_card_init(void) +{ + int ret =0; + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + rk29_snd_device = platform_device_alloc("soc-audio", -1); + if (!rk29_snd_device) { + DBG("platform device allocation failed\n"); + ret = -ENOMEM; + return ret; + } + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + platform_set_drvdata(rk29_snd_device, &rk29_snd_devdata); + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + rk29_snd_devdata.dev = &rk29_snd_device->dev; + ret = platform_device_add(rk29_snd_device); + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + if (ret) { + DBG("platform device add failed\n"); + platform_device_put(rk29_snd_device); + } + return ret; +} +static void __exit audio_card_exit(void) +{ + platform_device_unregister(rk29_snd_device); +} + +module_init(audio_card_init); +module_exit(audio_card_exit); +/* Module information */ +MODULE_AUTHOR("lhh lhh@rock-chips.com"); +MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rk29/rk29_wm8988.c b/sound/soc/rk29/rk29_wm8988.c new file mode 100755 index 000000000000..68c000cc23b7 --- /dev/null +++ b/sound/soc/rk29/rk29_wm8988.c @@ -0,0 +1,185 @@ +/* + * rk2818_wm8988.c -- SoC audio for rockchip + * + * Driver for rockchip wm8988 audio + * Copyright (C) 2009 lhh + * + * 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 "../codecs/wm8988.h" +#include "rk29_pcm.h" +#include "rk29_i2s.h" + +#if 0 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif + +static int rk2818_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + /*by Vincent Hsiung for EQ Vol Change*/ + #define HW_PARAMS_FLAG_EQVOL_ON 0x21 + #define HW_PARAMS_FLAG_EQVOL_OFF 0x22 + if ((params->flags == HW_PARAMS_FLAG_EQVOL_ON)||(params->flags == HW_PARAMS_FLAG_EQVOL_OFF)) + { + ret = codec_dai->ops->hw_params(substream, params, codec_dai); //by Vincent + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + } + else + { + /* set codec DAI configuration */ + #if defined (CONFIG_SND_ROCKCHIP_SOC_MASTER) + ret = codec_dai->ops->set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + #if defined (CONFIG_SND_ROCKCHIP_SOC_SLAVE) + ret = codec_dai->ops->set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ); + #endif + if (ret < 0) + return ret; + /* set cpu DAI configuration */ + #if defined (CONFIG_SND_ROCKCHIP_SOC_MASTER) + ret = cpu_dai->ops->set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + #endif + #if defined (CONFIG_SND_ROCKCHIP_SOC_SLAVE) + ret = cpu_dai->ops->set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rk2818_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out", NULL), + SND_SOC_DAPM_LINE("Line in", NULL), + SND_SOC_DAPM_MIC("Micn", NULL), + SND_SOC_DAPM_MIC("Micp", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[]= { + + {"Audio Out", NULL, "LOUT1"}, + {"Audio Out", NULL, "ROUT1"}, + {"Line in", NULL, "RINPUT1"}, + {"Line in", NULL, "LINPUT1"}, + {"Micn", NULL, "RINPUT2"}, + {"Micp", NULL, "LINPUT2"}, +}; + +/* + * Logic for a wm8988 as connected on a rockchip board. + */ +static int rk2818_wm8988_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dai *codec_dai = &codec->dai[0]; + int ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 12000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Failed to set WM8988 SYSCLK: %d\n", ret); + return ret; + } + + /* Add specific widgets */ + snd_soc_dapm_new_controls(codec, rk2818_dapm_widgets, + ARRAY_SIZE(rk2818_dapm_widgets)); + snd_soc_dapm_nc_pin(codec, "LOUT2"); + snd_soc_dapm_nc_pin(codec, "ROUT2"); + + /* Set up specific audio path audio_mapnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_ops rk2818_ops = { + .hw_params = rk2818_hw_params, +}; + +static struct snd_soc_dai_link rk2818_dai = { + .name = "WM8988", + .stream_name = "WM8988 PCM", + .cpu_dai = &rk2818_i2s_dai, + .codec_dai = &wm8988_dai, + .init = rk2818_wm8988_init, + .ops = &rk2818_ops, +}; + +static struct snd_soc_card snd_soc_card_rk2818 = { + .name = "RK2818_WM8988", + .platform = &rk2818_soc_platform, + .dai_link = &rk2818_dai, + .num_links = 1, +}; + + +static struct snd_soc_device rk2818_snd_devdata = { + .card = &snd_soc_card_rk2818, + .codec_dev = &soc_codec_dev_wm8988, +}; + +static struct platform_device *rk2818_snd_device; + +static int __init audio_card_init(void) +{ + int ret =0; + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + rk2818_snd_device = platform_device_alloc("soc-audio", -1); + if (!rk2818_snd_device) { + DBG("platform device allocation failed\n"); + ret = -ENOMEM; + return ret; + } + platform_set_drvdata(rk2818_snd_device, &rk2818_snd_devdata); + rk2818_snd_devdata.dev = &rk2818_snd_device->dev; + ret = platform_device_add(rk2818_snd_device); + if (ret) { + DBG("platform device add failed\n"); + platform_device_put(rk2818_snd_device); + } + return ret; +} +static void __exit audio_card_exit(void) +{ + platform_device_unregister(rk2818_snd_device); +} + +module_init(audio_card_init); +module_exit(audio_card_exit); +/* Module information */ +MODULE_AUTHOR("lhh lhh@rock-chips.com"); +MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface"); +MODULE_LICENSE("GPL");