diff options
author | Roshni Shah <shah.roshni@yahoo.com> | 2011-03-14 06:49:42 -0400 |
---|---|---|
committer | Justin Waters <justin.waters@timesys.com> | 2012-03-02 16:59:46 -0500 |
commit | 2731b2eadeaa141e6f305fa8086106608112bbaa (patch) | |
tree | 2702d45bac84073cd580ccb1bd3eafb9a000d3b6 /sound/soc/mxs | |
parent | 6d23f5084c975be637f7d748db82116bf84d3872 (diff) |
Add support for the i.MX53 QSB2.6.35.3-mx53-early-201103141049
This patch seems to have originated from the 11.01.00 release
from Freescale, which is no longer available except through the
gitweb interface from Freescale.
http://opensource.freescale.com/git?p=imx/linux-2.6-imx.git;a=commit;h=27fdf7bae11978d21e8aba09bb635f49b07edd4a
Diffstat (limited to 'sound/soc/mxs')
-rw-r--r-- | sound/soc/mxs/Kconfig | 51 | ||||
-rw-r--r-- | sound/soc/mxs/Makefile | 16 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-adc.c | 477 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-adc.h | 21 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-dai.c | 689 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-dai.h | 42 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-devb-spdif.c | 90 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-devb.c | 282 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-evk-adc.c | 198 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-pcm.c | 504 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-pcm.h | 40 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-spdif-dai.c | 203 | ||||
-rw-r--r-- | sound/soc/mxs/mxs-spdif-dai.h | 21 |
13 files changed, 2634 insertions, 0 deletions
diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig new file mode 100644 index 000000000000..213feb404a6b --- /dev/null +++ b/sound/soc/mxs/Kconfig @@ -0,0 +1,51 @@ +config SND_MXS_SOC + tristate "SoC Audio for the MXS chips" + depends on ARCH_MXS && SND_SOC + select SND_PCM + help + Say Y or M if you want to add support for codecs attached to + the MXS I2S or SSP interface. + + +config SND_MXS_SOC_SPDIF_DAI + tristate + +config SND_MXS_SOC_EVK_DEVB + tristate "SoC Audio support for MXS-EVK SGTL5000" + depends on SND_MXS_SOC && ARCH_MX28 + select SND_SOC_SGTL5000 + help + Say Y if you want to add support for SoC audio on MXS EVK development + board with the sgtl5000 codec. + +config SND_MXS_SOC_EVK_ADC + tristate "SoC Audio support for MXS-EVK ADC/DAC" + depends on SND_MXS_SOC && ARCH_MX23 + select SND_SOC_MXS_ADC_CODEC + help + Say Y if you want to add support for SoC audio on MXS EVK development + board with ADC/DAC audio interface. + +config SND_MXS_SOC_DAI + tristate "MXS Digital Audio Interface SAIF" + default y + depends on SND_MXS_SOC_EVK_DEVB + help + Enable MXS Digital Audio Interface SAIF + +config SND_MXS_SOC_ADC + tristate "MXS ADC/DAC Audio Interface" + default y + depends on SND_MXS_SOC_EVK_ADC + help + Enable MXS ADC/DAC Audio Interface + +config SND_MXS_SOC_EVK_DEVB_SPDIF + tristate "SoC SPDIF support for MXS EVK Development Board" + default n + depends on SND_MXS_SOC && ARCH_MXS + select SND_MXS_SOC_SPDIF_DAI + select SND_SOC_MXS_SPDIF + help + Say Y if you want to add support for SoC audio on MXS EVK development + board with the SPDIF transmitter. diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile new file mode 100644 index 000000000000..971c622a34fb --- /dev/null +++ b/sound/soc/mxs/Makefile @@ -0,0 +1,16 @@ +# MXS platfrom support +snd-soc-mxs-objs := mxs-pcm.o +snd-soc-mxs-dai-objs := mxs-dai.o +snd-soc-mxs-spdif-dai-objs := mxs-spdif-dai.o +snd-soc-mxs-devb-objs := mxs-devb.o +snd-soc-mxs-devb-spdif-objs := mxs-devb-spdif.o +snd-soc-mxs-adc-objs := mxs-adc.o +snd-soc-mxs-evk-adc-objs := mxs-evk-adc.o + +obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o +obj-$(CONFIG_SND_MXS_SOC_DAI) += snd-soc-mxs-dai.o +obj-$(CONFIG_SND_MXS_SOC_SPDIF_DAI) += snd-soc-mxs-spdif-dai.o +obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB) += snd-soc-mxs-devb.o +obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB_SPDIF) += snd-soc-mxs-devb-spdif.o +obj-$(CONFIG_SND_MXS_SOC_ADC) += snd-soc-mxs-adc.o +obj-$(CONFIG_SND_MXS_SOC_EVK_ADC) += snd-soc-mxs-evk-adc.o diff --git a/sound/soc/mxs/mxs-adc.c b/sound/soc/mxs/mxs-adc.c new file mode 100644 index 000000000000..6733652f76bc --- /dev/null +++ b/sound/soc/mxs/mxs-adc.c @@ -0,0 +1,477 @@ +/* + * ASoC Audio Layer for Freescale MXS ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include <mach/dmaengine.h> + +#include "mxs-pcm.h" + +#define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define ADC_VOLUME_MIN 0x37 + +struct mxs_pcm_dma_params mxs_audio_in = { + .name = "mxs-audio-in", + .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_AUDIOADC, + .irq = IRQ_ADC_DMA, +}; + +struct mxs_pcm_dma_params mxs_audio_out = { + .name = "mxs-audio-out", + .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_AUDIODAC, + .irq = IRQ_DAC_DMA, +}; + +static struct delayed_work work; +static struct delayed_work adc_ramp_work; +static struct delayed_work dac_ramp_work; +static bool adc_ramp_done = 1; +static bool dac_ramp_done = 1; + +static void mxs_adc_schedule_work(struct delayed_work *work) +{ + schedule_delayed_work(work, HZ / 10); +} +static void mxs_adc_work(struct work_struct *work) +{ + /* disable irq */ + disable_irq(IRQ_HEADPHONE_SHORT); + + while (true) { + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + msleep(10); + if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL) + & BM_AUDIOOUT_ANACTRL_SHORT_LR_STS) != 0) { + /* rearm the short protection */ + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + printk(KERN_WARNING "WARNING : Headphone LR short!\r\n"); + } else { + printk(KERN_WARNING "INFO : Headphone LR no longer short!\r\n"); + break; + } + msleep(1000); + } + + /* power up the HEADPHONE and un-mute the HPVOL */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + + /* enable irq for next short detect*/ + enable_irq(IRQ_HEADPHONE_SHORT); +} + +static void mxs_adc_schedule_ramp_work(struct delayed_work *work) +{ + schedule_delayed_work(work, msecs_to_jiffies(2)); + adc_ramp_done = 0; +} + +static void mxs_adc_ramp_work(struct work_struct *work) +{ + u32 reg = 0; + u32 reg1 = 0; + u32 reg2 = 0; + u32 l, r; + u32 ll, rr; + int i; + + reg = __raw_readl(REGS_AUDIOIN_BASE + \ + HW_AUDIOIN_ADCVOLUME); + + reg1 = reg & ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg1 = reg1 & ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + /* minimize adc volume */ + reg2 = reg1 | + BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MIN) | + BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MIN); + __raw_writel(reg2, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + msleep(1); + + l = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT) >> + BP_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT) >> + BP_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + + /* fade in adc vol */ + for (i = ADC_VOLUME_MIN; (i < l) || (i < r);) { + i += 0x8; + ll = i < l ? i : l; + rr = i < r ? i : r; + reg2 = reg1 | + BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ll) | + BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(rr); + __raw_writel(reg2, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + msleep(1); + } + adc_ramp_done = 1; +} + +static void mxs_dac_schedule_ramp_work(struct delayed_work *work) +{ + schedule_delayed_work(work, msecs_to_jiffies(2)); + dac_ramp_done = 0; +} + +static void mxs_dac_ramp_work(struct work_struct *work) +{ + u32 reg = 0; + u32 reg1 = 0; + u32 l, r; + u32 ll, rr; + int i; + + /* unmute hp and speaker */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR); + + reg = __raw_readl(REGS_AUDIOOUT_BASE + \ + HW_AUDIOOUT_HPVOL); + + reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT; + reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT; + + l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >> + BP_AUDIOOUT_HPVOL_VOL_LEFT; + r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >> + BP_AUDIOOUT_HPVOL_VOL_RIGHT; + /* fade in hp vol */ + for (i = 0x7f; i > 0 ;) { + i -= 0x8; + ll = i > (int)l ? i : l; + rr = i > (int)r ? i : r; + reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll) + | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr); + __raw_writel(reg, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + msleep(1); + } + dac_ramp_done = 1; +} + +static irqreturn_t mxs_short_irq(int irq, void *dev_id) +{ + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + mxs_adc_schedule_work(&work); + return IRQ_HANDLED; +} +static irqreturn_t mxs_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ; + } else { + ctrl_reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "%s underflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + __raw_writel( + BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + else + __raw_writel( + BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "%s overflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + __raw_writel( + BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + else + __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + } else + printk(KERN_WARNING "Unknown DAC error interrupt\n"); + + return IRQ_HANDLED; +} + +static int mxs_adc_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + struct mxs_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + u32 xfer_count1 = 0; + u32 xfer_count2 = 0; + u32 cur_bar1 = 0; + u32 cur_bar2 = 0; + u32 reg; + struct mxs_dma_info dma_info; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + + if (playback) { + /* enable the fifo error interrupt */ + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + /* write a data to data reg to trigger the transfer */ + __raw_writel(0x0, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA); + mxs_dac_schedule_ramp_work(&dac_ramp_work); + } else { + mxs_dma_get_info(prtd->dma_ch, &dma_info); + cur_bar1 = dma_info.buf_addr; + xfer_count1 = dma_info.xfer_count; + + __raw_writel(BM_AUDIOIN_CTRL_RUN, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + udelay(100); + + mxs_dma_get_info(prtd->dma_ch, &dma_info); + cur_bar2 = dma_info.buf_addr; + xfer_count2 = dma_info.xfer_count; + + /* check if DMA getting stuck */ + if ((xfer_count1 == xfer_count2) && (cur_bar1 == cur_bar2)) + /* read a data from data reg to trigger the receive */ + reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_DATA); + + mxs_adc_schedule_ramp_work(&adc_ramp_work); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + + if (playback) { + if (dac_ramp_done == 0) { + cancel_delayed_work(&dac_ramp_work); + dac_ramp_done = 1; + } + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* disable the fifo error interrupt */ + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + mdelay(50); + } else { + if (adc_ramp_done == 0) { + cancel_delayed_work(&adc_ramp_work); + adc_ramp_done = 1; + } + __raw_writel(BM_AUDIOIN_CTRL_RUN, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + } + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int mxs_adc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int irq_short; + int ret; + + INIT_DELAYED_WORK(&work, mxs_adc_work); + INIT_DELAYED_WORK(&adc_ramp_work, mxs_adc_ramp_work); + INIT_DELAYED_WORK(&dac_ramp_work, mxs_dac_ramp_work); + + if (playback) { + irq = IRQ_DAC_ERROR; + snd_soc_dai_set_dma_data(dai, substream, &mxs_audio_out); + } else { + irq = IRQ_ADC_ERROR; + snd_soc_dai_set_dma_data(dai, substream, &mxs_audio_in); + } + + ret = request_irq(irq, mxs_err_irq, 0, "MXS DAC and ADC Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n", + __func__, IRQ_DAC_ERROR); + return ret; + } + + irq_short = IRQ_HEADPHONE_SHORT; + ret = request_irq(irq_short, mxs_short_irq, + IRQF_DISABLED | IRQF_SHARED, "MXS DAC and ADC HP SHORT", substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request ADC/DAC HP SHORT irq %d\n", + __func__, IRQ_DAC_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + } else { + __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + __raw_writel(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + } + + return 0; +} + +static void mxs_adc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + free_irq(IRQ_DAC_ERROR, substream); + } else { + __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + free_irq(IRQ_ADC_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int mxs_adc_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int mxs_adc_resume(struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define mxs_adc_suspend NULL +#define mxs_adc_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai_ops mxs_adc_dai_ops = { + .startup = mxs_adc_startup, + .shutdown = mxs_adc_shutdown, + .trigger = mxs_adc_trigger, +}; + +struct snd_soc_dai mxs_adc_dai = { + .name = "mxs adc/dac", + .id = 0, + .suspend = mxs_adc_suspend, + .resume = mxs_adc_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_ADC_RATES, + .formats = MXS_ADC_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_ADC_RATES, + .formats = MXS_ADC_FORMATS, + }, + .ops = &mxs_adc_dai_ops, +}; +EXPORT_SYMBOL_GPL(mxs_adc_dai); + +static int __init mxs_adc_dai_init(void) +{ + return snd_soc_register_dai(&mxs_adc_dai); +} + +static void __exit mxs_adc_dai_exit(void) +{ + snd_soc_unregister_dai(&mxs_adc_dai); +} +module_init(mxs_adc_dai_init); +module_exit(mxs_adc_dai_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("MXS ADC/DAC DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-adc.h b/sound/soc/mxs/mxs-adc.h new file mode 100644 index 000000000000..b922c14cb894 --- /dev/null +++ b/sound/soc/mxs/mxs-adc.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale MXS ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _MXS_ADC_H +#define _MXS_ADC_H +extern struct snd_soc_dai mxs_adc_dai; +#endif diff --git a/sound/soc/mxs/mxs-dai.c b/sound/soc/mxs/mxs-dai.c new file mode 100644 index 000000000000..e8c4642e660c --- /dev/null +++ b/sound/soc/mxs/mxs-dai.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include <mach/mx28.h> + +#include "mxs-pcm.h" +#include "mxs-dai.h" + +#define SAIF0_CTRL (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL) +#define SAIF1_CTRL (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL) +#define SAIF0_CTRL_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_SET) +#define SAIF1_CTRL_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_SET) +#define SAIF0_CTRL_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_CLR) +#define SAIF1_CTRL_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_CLR) + +#define SAIF0_STAT (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT) +#define SAIF1_STAT (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT) +#define SAIF0_STAT_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_SET) +#define SAIF1_STAT_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_SET) +#define SAIF0_STAT_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_CLR) +#define SAIF1_STAT_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_CLR) + +#define SAIF0_DATA (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA) +#define SAIF1_DATA (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA) +#define SAIF0_DATA_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_SET) +#define SAIF1_DATA_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_SET) +#define SAIF0_DATA_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_CLR) +#define SAIF1_DATA_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_CLR) + +#define SAIF0_VERSION (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_VERSION) +#define SAIF1_VERSION (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_VERSION) + +#define HW_SAIF_CTRL (0x00000000) +#define HW_SAIF_CTRL_SET (0x00000004) +#define HW_SAIF_CTRL_CLR (0x00000008) +#define HW_SAIF_CTRL_TOG (0x0000000c) + +#define BM_SAIF_CTRL_SFTRST 0x80000000 +#define BM_SAIF_CTRL_CLKGATE 0x40000000 +#define BP_SAIF_CTRL_BITCLK_MULT_RATE 27 +#define BM_SAIF_CTRL_BITCLK_MULT_RATE 0x38000000 +#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \ + (((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE) +#define BM_SAIF_CTRL_BITCLK_BASE_RATE 0x04000000 +#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN 0x02000000 +#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN 0x01000000 +#define BP_SAIF_CTRL_RSRVD2 21 +#define BM_SAIF_CTRL_RSRVD2 0x00E00000 +#define BF_SAIF_CTRL_RSRVD2(v) \ + (((v) << 21) & BM_SAIF_CTRL_RSRVD2) +#define BP_SAIF_CTRL_DMAWAIT_COUNT 16 +#define BM_SAIF_CTRL_DMAWAIT_COUNT 0x001F0000 +#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \ + (((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT) +#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14 +#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000 +#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \ + (((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT) +#define BM_SAIF_CTRL_LRCLK_PULSE 0x00002000 +#define BM_SAIF_CTRL_BIT_ORDER 0x00001000 +#define BM_SAIF_CTRL_DELAY 0x00000800 +#define BM_SAIF_CTRL_JUSTIFY 0x00000400 +#define BM_SAIF_CTRL_LRCLK_POLARITY 0x00000200 +#define BM_SAIF_CTRL_BITCLK_EDGE 0x00000100 +#define BP_SAIF_CTRL_WORD_LENGTH 4 +#define BM_SAIF_CTRL_WORD_LENGTH 0x000000F0 +#define BF_SAIF_CTRL_WORD_LENGTH(v) \ + (((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH) +#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE 0x00000008 +#define BM_SAIF_CTRL_SLAVE_MODE 0x00000004 +#define BM_SAIF_CTRL_READ_MODE 0x00000002 +#define BM_SAIF_CTRL_RUN 0x00000001 + +#define HW_SAIF_STAT (0x00000010) +#define HW_SAIF_STAT_SET (0x00000014) +#define HW_SAIF_STAT_CLR (0x00000018) +#define HW_SAIF_STAT_TOG (0x0000001c) + +#define BM_SAIF_STAT_PRESENT 0x80000000 +#define BP_SAIF_STAT_RSRVD2 17 +#define BM_SAIF_STAT_RSRVD2 0x7FFE0000 +#define BF_SAIF_STAT_RSRVD2(v) \ + (((v) << 17) & BM_SAIF_STAT_RSRVD2) +#define BM_SAIF_STAT_DMA_PREQ 0x00010000 +#define BP_SAIF_STAT_RSRVD1 7 +#define BM_SAIF_STAT_RSRVD1 0x0000FF80 +#define BF_SAIF_STAT_RSRVD1(v) \ + (((v) << 7) & BM_SAIF_STAT_RSRVD1) +#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040 +#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ 0x00000020 +#define BM_SAIF_STAT_FIFO_SERVICE_IRQ 0x00000010 +#define BP_SAIF_STAT_RSRVD0 1 +#define BM_SAIF_STAT_RSRVD0 0x0000000E +#define BF_SAIF_STAT_RSRVD0(v) \ + (((v) << 1) & BM_SAIF_STAT_RSRVD0) +#define BM_SAIF_STAT_BUSY 0x00000001 + +#define HW_SAIF_DATA (0x00000020) +#define HW_SAIF_DATA_SET (0x00000024) +#define HW_SAIF_DATA_CLR (0x00000028) +#define HW_SAIF_DATA_TOG (0x0000002c) + +#define BP_SAIF_DATA_PCM_RIGHT 16 +#define BM_SAIF_DATA_PCM_RIGHT 0xFFFF0000 +#define BF_SAIF_DATA_PCM_RIGHT(v) \ + (((v) << 16) & BM_SAIF_DATA_PCM_RIGHT) +#define BP_SAIF_DATA_PCM_LEFT 0 +#define BM_SAIF_DATA_PCM_LEFT 0x0000FFFF +#define BF_SAIF_DATA_PCM_LEFT(v) \ + (((v) << 0) & BM_SAIF_DATA_PCM_LEFT) + +#define HW_SAIF_VERSION (0x00000030) + +#define BP_SAIF_VERSION_MAJOR 24 +#define BM_SAIF_VERSION_MAJOR 0xFF000000 +#define BF_SAIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_SAIF_VERSION_MAJOR) +#define BP_SAIF_VERSION_MINOR 16 +#define BM_SAIF_VERSION_MINOR 0x00FF0000 +#define BF_SAIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_SAIF_VERSION_MINOR) +#define BP_SAIF_VERSION_STEP 0 +#define BM_SAIF_VERSION_STEP 0x0000FFFF +#define BF_SAIF_VERSION_STEP(v) \ + (((v) << 0) & BM_SAIF_VERSION_STEP) +/* debug */ +#define MXS_SAIF_DEBUG 0 +#if MXS_SAIF_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +#define MXS_SAIF_DUMP 0 +#if MXS_SAIF_DUMP +#define SAIF_DUMP() \ + do { \ + printk(KERN_INFO "dump @ %s\n", __func__);\ + printk(KERN_INFO "scr %x\t, %x\n", \ + __raw_readl(SAIF0_CTRL), __raw_readl(SAIF1_CTRL));\ + printk(KERN_INFO "stat %x\t, %x\n", \ + __raw_readl(SAIF0_STAT), __raw_readl(SAIF1_STAT));\ + printk(KERN_INFO "data %x\t, %x\n", \ + __raw_readl(SAIF0_DATA), __raw_readl(SAIF1_DATA));\ + printk(KERN_INFO "version %x\t, %x\n", \ + __raw_readl(SAIF0_VERSION), __raw_readl(SAIF1_VERSION)); + } while (0); +#else +#define SAIF_DUMP() +#endif + +#define SAIF0_PORT 0 +#define SAIF1_PORT 1 + +#define MXS_DAI_SAIF0 0 +#define MXS_DAI_SAIF1 1 + +static struct mxs_saif mxs_saif_en; + +static int saif_active[2] = { 0, 0 }; + +struct mxs_pcm_dma_params mxs_saif_0 = { + .name = "mxs-saif-0", + .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF0, + .irq = IRQ_SAIF0_DMA, +}; + +struct mxs_pcm_dma_params mxs_saif_1 = { + .name = "mxs-saif-1", + .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF1, + .irq = IRQ_SAIF1_DMA, +}; + +/* +* SAIF system clock configuration. +* Should only be called when port is inactive. +*/ +static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct clk *saif_clk; + u32 scr; + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + + switch (clk_id) { + case IMX_SSP_SYS_CLK: + saif_clk = saif_select->saif_mclk; + if (IS_ERR(saif_clk)) { + pr_err("%s:failed to get sys_clk\n", __func__); + return -EINVAL; + } + clk_set_rate(saif_clk, freq); + clk_enable(saif_clk); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * SAIF Clock dividers + * Should only be called when port is inactive. + */ +static int mxs_saif_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + u32 scr; + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + + if (saif_select->saif_clk == SAIF0) + scr = __raw_readl(SAIF0_CTRL); + else + scr = __raw_readl(SAIF1_CTRL); + + scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE; + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + + switch (div_id) { + case IMX_SSP_SYS_MCLK: + switch (div) { + case 32: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 64: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 128: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 256: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 512: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 48: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 96: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 192: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + case 384: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (saif_select->saif_clk == SAIF0) + __raw_writel(scr, SAIF0_CTRL); + else + __raw_writel(scr, SAIF1_CTRL); + + return 0; +} + +/* + * SAIF DAI format configuration. + * Should only be called when port is inactive. + * Note: We don't use the I2S modes but instead manually configure the + * SAIF for I2S. + */ +static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + u32 scr, stat; + u32 scr0, scr1; + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + + stat = (__raw_readl(SAIF0_STAT)) | (__raw_readl(SAIF1_STAT)); + if (stat & BM_SAIF_STAT_BUSY) + return 0; + + scr0 = __raw_readl(SAIF0_CTRL); + scr1 = __raw_readl(SAIF1_CTRL); + scr0 = scr0 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \ + & ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY; + scr1 = scr1 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \ + & ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY; + scr = 0; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data frame low 1clk before data */ + scr |= BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data frame high with data */ + scr &= ~BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + scr &= ~BM_SAIF_CTRL_JUSTIFY; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_IB_NF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_IF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_NF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + break; + case SND_SOC_DAIFMT_CBM_CFM: + if (saif_select->saif_clk == SAIF0) { + scr &= ~BM_SAIF_CTRL_SLAVE_MODE; + __raw_writel(scr | scr0, SAIF0_CTRL); + scr |= BM_SAIF_CTRL_SLAVE_MODE; + __raw_writel(scr | scr1, SAIF1_CTRL); + } else { + scr &= ~BM_SAIF_CTRL_SLAVE_MODE; + __raw_writel(scr | scr1, SAIF1_CTRL); + scr |= BM_SAIF_CTRL_SLAVE_MODE; + __raw_writel(scr | scr0, SAIF0_CTRL); + } + break; + } + + SAIF_DUMP(); + return 0; +} + +static int mxs_saif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + /* we cant really change any saif values after saif is enabled*/ + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + snd_soc_dai_set_dma_data(cpu_dai, substream, &mxs_saif_0); + else + snd_soc_dai_set_dma_data(cpu_dai, substream, &mxs_saif_1); + + if (cpu_dai->playback.active && cpu_dai->capture.active) + return 0; + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + if (saif_active[SAIF0_PORT]++) + return 0; + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_PLAYBACK))) + if (saif_active[SAIF1_PORT]++) + return 0; + SAIF_DUMP(); + return 0; +} + +/* + * Should only be called when port is inactive. + * although can be called multiple times by upper layers. + */ +static int mxs_saif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + u32 scr, stat; + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) { + scr = __raw_readl(SAIF0_CTRL); + stat = __raw_readl(SAIF0_STAT); + } else { + scr = __raw_readl(SAIF1_CTRL); + stat = __raw_readl(SAIF1_STAT); + } + /* cant change any parameters when SAIF is running */ + /* DAI data (word) size */ + scr &= ~BM_SAIF_CTRL_WORD_LENGTH; + scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(0); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(4); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(8); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + } + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* enable TX mode */ + scr &= ~BM_SAIF_CTRL_READ_MODE; + } else { + /* enable RX mode */ + scr |= BM_SAIF_CTRL_READ_MODE; + } + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + __raw_writel(scr, SAIF0_CTRL); + else + __raw_writel(scr, SAIF1_CTRL); + return 0; +} + +static int mxs_saif_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF0_CTRL_CLR); + else + __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF1_CTRL_CLR); + SAIF_DUMP(); + return 0; +} + +static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + void __iomem *reg; + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + reg = (void __iomem *)SAIF0_DATA; + else + reg = (void __iomem *)SAIF1_DATA; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + /*write a data to saif data register to trigger + the transfer*/ + __raw_writel(0, reg); + else + /*read a data from saif data register to trigger + the receive*/ + __raw_readl(reg); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + return -EINVAL; + } + SAIF_DUMP(); + return 0; +} + +static void mxs_saif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data; + /* shutdown SAIF if neither Tx or Rx is active */ + if (cpu_dai->playback.active || cpu_dai->capture.active) + return; + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_CAPTURE))) + if (--saif_active[SAIF0_PORT] > 1) + return; + + if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \ + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) || \ + ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \ + && (substream->stream == SNDRV_PCM_STREAM_PLAYBACK))) + if (--saif_active[SAIF1_PORT]) + return; +} + +#ifdef CONFIG_PM +static int mxs_saif_suspend(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + /* do we need to disable any clocks? */ + return 0; +} + +static int mxs_saif_resume(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + /* do we need to enable any clocks? */ + return 0; +} +#else +#define mxs_saif_suspend NULL +#define mxs_saif_resume NULL +#endif + +static int fifo_err_counter; + +static irqreturn_t saif0_irq(int irq, void *dev_id) +{ + if (fifo_err_counter++ % 100 == 0) + printk(KERN_ERR "saif0_irq SAIF_STAT %x SAIF_CTRL %x fifo_errs=\ + %d\n", + __raw_readl(SAIF0_STAT), + __raw_readl(SAIF0_CTRL), + fifo_err_counter); + __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \ + BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF0_STAT_CLR); + return IRQ_HANDLED; +} + +static irqreturn_t saif1_irq(int irq, void *dev_id) +{ + if (fifo_err_counter++ % 100 == 0) + printk(KERN_ERR "saif1_irq SAIF_STAT %x SAIF_CTRL %x \ + fifo_errs=%d\n", + __raw_readl(SAIF1_STAT), + __raw_readl(SAIF1_CTRL), + fifo_err_counter); + __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \ + BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF1_STAT_CLR); + return IRQ_HANDLED; +} + +static int mxs_saif_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + if (request_irq(IRQ_SAIF0, saif0_irq, 0, "saif0", dai)) { + printk(KERN_ERR "%s: failure requesting irq %s\n", + __func__, "saif0"); + return -EBUSY; + } + + if (request_irq(IRQ_SAIF1, saif1_irq, 0, "saif1", dai)) { + printk(KERN_ERR "%s: failure requesting irq %s\n", + __func__, "saif1"); + return -EBUSY; + } + return 0; +} + +static void mxs_saif_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + free_irq(IRQ_SAIF0, dai); + free_irq(IRQ_SAIF1, dai); +} + +#define MXS_SAIF_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000) + +#define MXS_SAIF_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops mxs_saif_dai_ops = { + .startup = mxs_saif_startup, + .shutdown = mxs_saif_shutdown, + .trigger = mxs_saif_trigger, + .prepare = mxs_saif_prepare, + .hw_params = mxs_saif_hw_params, + .set_sysclk = mxs_saif_set_dai_sysclk, + .set_clkdiv = mxs_saif_set_dai_clkdiv, + .set_fmt = mxs_saif_set_dai_fmt, +}; + +struct snd_soc_dai mxs_saif_dai[] = { + { + .name = "mxs-saif", + .probe = mxs_saif_probe, + .remove = mxs_saif_remove, + .suspend = mxs_saif_suspend, + .resume = mxs_saif_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .ops = &mxs_saif_dai_ops, + .private_data = &mxs_saif_en, + } +}; +EXPORT_SYMBOL_GPL(mxs_saif_dai); + +static int __init mxs_saif_init(void) +{ + return snd_soc_register_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai)); +} + +static void __exit mxs_saif_exit(void) +{ + snd_soc_unregister_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai)); +} + +module_init(mxs_saif_init); +module_exit(mxs_saif_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX28 ASoC I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-dai.h b/sound/soc/mxs/mxs-dai.h new file mode 100644 index 000000000000..2476feb832b3 --- /dev/null +++ b/sound/soc/mxs/mxs-dai.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2010 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_SAIF_H +#define _MXS_SAIF_H + +#include <mach/hardware.h> + +/* SAIF clock sources */ +#define IMX_SSP_SYS_CLK 0 +#define IMX_SSP_SYS_MCLK 1 + +#define SAIF0 0 +#define SAIF1 1 + +/*private info*/ +struct mxs_saif { + u8 saif_clk; +#define PLAYBACK_SAIF0_CAPTURE_SAIF1 0 +#define PLAYBACK_SAIF1_CAPTURE_SAIF0 1 + u16 stream_mapping; + struct clk *saif_mclk; +}; + +extern struct snd_soc_dai mxs_saif_dai[]; + +#endif diff --git a/sound/soc/mxs/mxs-devb-spdif.c b/sound/soc/mxs/mxs-devb-spdif.c new file mode 100644 index 000000000000..9ed62110dff7 --- /dev/null +++ b/sound/soc/mxs/mxs-devb-spdif.c @@ -0,0 +1,90 @@ +/* + * ASoC driver for MXS Evk development board + * + * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. + * + * based on stmp3780_devb_spdif.c + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "mxs-spdif-dai.h" +#include "../codecs/mxs_spdif.h" +#include "mxs-pcm.h" + +/* mxs devb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link mxs_devb_dai = { + .name = "MXS SPDIF", + .stream_name = "MXS SPDIF", + .cpu_dai = &mxs_spdif_dai, + .codec_dai = &mxs_spdif_codec_dai, +}; + +/* mxs devb audio machine driver */ +static struct snd_soc_card snd_soc_machine_mxs_devb = { + .name = "mxs-evk", + .platform = &mxs_soc_platform, + .dai_link = &mxs_devb_dai, + .num_links = 1, +}; + +/* mxs devb audio subsystem */ +static struct snd_soc_device mxs_devb_snd_devdata = { + .card = &snd_soc_machine_mxs_devb, + .codec_dev = &soc_spdif_codec_dev_mxs, +}; + +static struct platform_device *mxs_devb_snd_device; + +static int __init mxs_devb_init(void) +{ + int ret = 0; + + mxs_devb_snd_device = platform_device_alloc("soc-audio", 2); + if (!mxs_devb_snd_device) + return -ENOMEM; + + platform_set_drvdata(mxs_devb_snd_device, + &mxs_devb_snd_devdata); + mxs_devb_snd_devdata.dev = &mxs_devb_snd_device->dev; + mxs_devb_snd_device->dev.platform_data = + &mxs_devb_snd_devdata; + + ret = platform_device_add(mxs_devb_snd_device); + if (ret) + platform_device_put(mxs_devb_snd_device); + + return ret; +} + +static void __exit mxs_devb_exit(void) +{ + platform_device_unregister(mxs_devb_snd_device); +} + +module_init(mxs_devb_init); +module_exit(mxs_devb_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("MXS EVK development board ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-devb.c b/sound/soc/mxs/mxs-devb.c new file mode 100644 index 000000000000..6847c54a6485 --- /dev/null +++ b/sound/soc/mxs/mxs-devb.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/dma.h> +#include <mach/device.h> + +#include "mxs-dai.h" +#include "mxs-pcm.h" +#include "../codecs/sgtl5000.h" + +struct mxs_evk_priv { + int sysclk; + int hw; + struct platform_device *pdev; +}; + +static struct mxs_evk_priv card_priv; + +static int mxs_evk_audio_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_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct mxs_evk_priv *priv = &card_priv; + unsigned int rate = params_rate(params); + int ret = 0; + + u32 dai_format; + + /* only need to do this once as capture and playback are sync */ + if (priv->hw) + return 0; + priv->hw = 1; + priv->sysclk = 512 * rate; + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, (priv->sysclk)/2, 0); + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, rate, 0); + + snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSP_SYS_MCLK, 256); + /* set codec to slave mode */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + /* set cpu_dai to master mode for playback, slave mode for record */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + /* set the SAIF system clock as output */ + snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, priv->sysclk, \ + SND_SOC_CLOCK_OUT); + + return 0; +} + +static int mxs_evk_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void mxs_evk_shutdown(struct snd_pcm_substream *substream) +{ + struct mxs_evk_priv *priv = &card_priv; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + + if (cpu_dai->playback.active || cpu_dai->capture.active) + priv->hw = 1; + else + priv->hw = 0; +} + +/* + * mxs_evk SGTL5000 audio DAI opserations. + */ +static struct snd_soc_ops mxs_evk_ops = { + .startup = mxs_evk_startup, + .shutdown = mxs_evk_shutdown, + .hw_params = mxs_evk_audio_hw_params, +}; + +/* mxs_evk machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Mic Jack --> MIC_IN (with automatic bias) */ + {"MIC_IN", NULL, "Mic Jack"}, + + /* Line in Jack --> LINE_IN */ + {"LINE_IN", NULL, "Line In Jack"}, + + /* HP_OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HP_OUT"}, + + /* LINE_OUT --> Ext Speaker */ + {"Ext Spk", NULL, "LINE_OUT"}, +}; + +static const char *jack_function[] = { "off", "on"}; + +static const char *spk_function[] = { "off", "on" }; + +static const char *line_in_function[] = { "off", "on" }; + +static const struct soc_enum sgtl5000_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, line_in_function), +}; + +/* mxs_evk card dapm widgets */ +static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static int mxs_evk_sgtl5000_init(struct snd_soc_codec *codec) +{ + /* Add mxs_evk specific widgets */ + snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets, + ARRAY_SIZE(mxs_evk_dapm_widgets)); + + /* Set up mxs_evk specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* mxs_evk digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link mxs_evk_dai = { + .name = "SGTL5000", + .stream_name = "SGTL5000", + .codec_dai = &sgtl5000_dai, + .init = mxs_evk_sgtl5000_init, + .ops = &mxs_evk_ops, +}; + +static int mxs_evk_card_remove(struct platform_device *pdev) +{ + struct mxs_evk_priv *priv = &card_priv; + struct mxs_audio_platform_data *plat; + if (priv->pdev) { + plat = priv->pdev->dev.platform_data; + if (plat->finit) + plat->finit(); + } + + return 0; +} + +static struct snd_soc_card snd_soc_card_mxs_evk = { + .name = "mxs-evk", + .platform = &mxs_soc_platform, + .dai_link = &mxs_evk_dai, + .num_links = 1, + .remove = mxs_evk_card_remove, +}; + +static struct snd_soc_device mxs_evk_snd_devdata = { + .card = &snd_soc_card_mxs_evk, + .codec_dev = &soc_codec_dev_sgtl5000, +}; + +static int __devinit mxs_evk_sgtl5000_probe(struct platform_device *pdev) +{ + struct mxs_audio_platform_data *plat = pdev->dev.platform_data; + struct mxs_saif *saif_select; + int ret = -EINVAL; + if (plat->init && plat->init()) + goto err_plat_init; + mxs_evk_dai.cpu_dai = &mxs_saif_dai[0]; + saif_select = (struct mxs_saif *)mxs_evk_dai.cpu_dai->private_data; + saif_select->stream_mapping = PLAYBACK_SAIF0_CAPTURE_SAIF1; + saif_select->saif_mclk = plat->saif_mclock; + saif_select->saif_clk = SAIF0; + return 0; +err_plat_init: + if (plat->finit) + plat->finit(); + return ret; +} + +static int mxs_evk_sgtl5000_remove(struct platform_device *pdev) +{ + struct mxs_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->finit) + plat->finit(); + return 0; +} + +static struct platform_driver mxs_evk_sgtl5000_audio_driver = { + .probe = mxs_evk_sgtl5000_probe, + .remove = mxs_evk_sgtl5000_remove, + .driver = { + .name = "mxs-sgtl5000", + }, +}; + +static struct platform_device *mxs_evk_snd_device; + +static int __init mxs_evk_init(void) +{ + int ret; + + ret = platform_driver_register(&mxs_evk_sgtl5000_audio_driver); + if (ret) + return -ENOMEM; + + mxs_evk_snd_device = platform_device_alloc("soc-audio", 1); + if (!mxs_evk_snd_device) + return -ENOMEM; + + platform_set_drvdata(mxs_evk_snd_device, &mxs_evk_snd_devdata); + mxs_evk_snd_devdata.dev = &mxs_evk_snd_device->dev; + ret = platform_device_add(mxs_evk_snd_device); + + if (ret) + platform_device_put(mxs_evk_snd_device); + + return ret; +} + +static void __exit mxs_evk_exit(void) +{ + platform_driver_unregister(&mxs_evk_sgtl5000_audio_driver); + platform_device_unregister(mxs_evk_snd_device); +} + +module_init(mxs_evk_init); +module_exit(mxs_evk_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SGTL5000 Driver for MXS EVK"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-evk-adc.c b/sound/soc/mxs/mxs-evk-adc.c new file mode 100644 index 000000000000..16c1cb4fab96 --- /dev/null +++ b/sound/soc/mxs/mxs-evk-adc.c @@ -0,0 +1,198 @@ +/* + * ASoC driver for Freescale MXS EVK board + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "../codecs/mxs-adc-codec.h" +#include "mxs-adc.h" +#include "mxs-pcm.h" + +/* mxs evk machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + /* HPR/HPL OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HPR"}, + {"Headphone Jack", NULL, "HPL"}, + + /* SPEAKER OUT --> Ext Speaker */ + {"Ext Spk", NULL, "SPEAKER"}, +}; + +static int mxs_evk_jack_func; +static int mxs_evk_spk_func; + +static const char *jack_function[] = { "off", "on"}; + +static const char *spk_function[] = { "off", "on" }; + + +static const struct soc_enum mxs_evk_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static int mxs_evk_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mxs_evk_jack_func; + return 0; +} + +static int mxs_evk_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (mxs_evk_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + mxs_evk_jack_func = ucontrol->value.enumerated.item[0]; + if (mxs_evk_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int mxs_evk_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mxs_evk_spk_func; + return 0; +} + +static int mxs_evk_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (mxs_evk_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + mxs_evk_spk_func = ucontrol->value.enumerated.item[0]; + if (mxs_evk_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + + snd_soc_dapm_sync(codec); + return 1; +} +/* mxs evk card dapm widgets */ +static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new mxs_evk_controls[] = { + SOC_ENUM_EXT("HP Playback Switch", mxs_evk_enum[0], mxs_evk_get_jack, + mxs_evk_set_jack), + SOC_ENUM_EXT("Speaker Playback Switch", mxs_evk_enum[1], + mxs_evk_get_spk, mxs_evk_set_spk), +}; + +static int mxs_evk_codec_init(struct snd_soc_codec *codec) +{ + int i, ret; + /* Add mxs evk specific controls */ + snd_soc_add_controls(codec, mxs_evk_controls, + ARRAY_SIZE(mxs_evk_controls)); + + /* Add mxs evk specific widgets */ + snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets, + ARRAY_SIZE(mxs_evk_dapm_widgets)); + + /* Set up mxs evk specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + /* default on */ + mxs_evk_jack_func = 1; + mxs_evk_spk_func = 1; + + return ret; +} +/* mxs evk dac/adc audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link mxs_evk_codec_dai = { + .name = "MXS ADC/DAC", + .stream_name = "MXS ADC/DAC", + .cpu_dai = &mxs_adc_dai, + .codec_dai = &mxs_codec_dai, + .init = mxs_evk_codec_init, +}; + +/* mxs evk audio machine driver */ +static struct snd_soc_card snd_soc_card_mxs_evk = { + .name = "MXS EVK", + .platform = &mxs_soc_platform, + .dai_link = &mxs_evk_codec_dai, + .num_links = 1, +}; + +/* mxs evk audio subsystem */ +static struct snd_soc_device mxs_evk_snd_devdata = { + .card = &snd_soc_card_mxs_evk, + .codec_dev = &soc_codec_dev_mxs, +}; + +static struct platform_device *mxs_evk_snd_device; + +static int __init mxs_evk_adc_init(void) +{ + int ret = 0; + + mxs_evk_snd_device = platform_device_alloc("soc-audio", 0); + if (!mxs_evk_snd_device) + return -ENOMEM; + + platform_set_drvdata(mxs_evk_snd_device, + &mxs_evk_snd_devdata); + mxs_evk_snd_devdata.dev = &mxs_evk_snd_device->dev; + mxs_evk_snd_device->dev.platform_data = + &mxs_evk_snd_devdata; + + ret = platform_device_add(mxs_evk_snd_device); + if (ret) + platform_device_put(mxs_evk_snd_device); + + return ret; +} + +static void __exit mxs_evk_adc_exit(void) +{ + platform_device_unregister(mxs_evk_snd_device); +} + +module_init(mxs_evk_adc_init); +module_exit(mxs_evk_adc_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("MXS EVK board ADC/DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c new file mode 100644 index 000000000000..b4d7ca00a68f --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/hardware.h> +#include <mach/dmaengine.h> + +#include "mxs-pcm.h" +static const struct snd_pcm_hardware mxs_pcm_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 | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 255, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +/* + * Required to request DMA channels + */ +struct device *mxs_pcm_dev; + +static irqreturn_t mxs_pcm_dma_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = substream->runtime->private_data; + struct mxs_dma_info dma_info; + void *pdma; + unsigned long prev_appl_offset, appl_count, cont, appl_ptr_bytes; + + mxs_dma_get_info(prtd->dma_ch, &dma_info); + + if (dma_info.status) { + printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n", + __func__, prtd->params->dma_ch, prtd->params->name); + mxs_dma_ack_irq(prtd->dma_ch); + } else { + if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) && + ((prtd->format == SNDRV_PCM_FORMAT_S24_LE) + || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) { + + appl_ptr_bytes = + frames_to_bytes(runtime, + runtime->control->appl_ptr); + + appl_count = appl_ptr_bytes - prtd->appl_ptr_bytes; + prev_appl_offset = + prtd->appl_ptr_bytes % prtd->dma_totsize; + cont = prtd->dma_totsize - prev_appl_offset; + + if (appl_count > cont) { + pdma = runtime->dma_area + prev_appl_offset; + memmove(pdma + 1, pdma, cont - 1); + pdma = runtime->dma_area; + memmove(pdma + 1, pdma, appl_count - cont - 1); + } else { + pdma = runtime->dma_area + prev_appl_offset; + memmove(pdma + 1, pdma, appl_count - 1); + } + prtd->appl_ptr_bytes = appl_ptr_bytes; + } + mxs_dma_ack_irq(prtd->dma_ch); + snd_pcm_period_elapsed(substream); + } + return IRQ_HANDLED; +} + +/* + * Make a circular DMA descriptor list + */ +static int mxs_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = runtime->private_data; + dma_addr_t dma_buffer_phys; + int periods_num, playback, i; + + playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + periods_num = prtd->dma_totsize / prtd->dma_period; + + dma_buffer_phys = runtime->dma_addr; + + /* Reset DMA channel, enable interrupt */ + mxs_dma_reset(prtd->dma_ch); + + /* Set up a DMA chain to sent DMA buffer */ + for (i = 0; i < periods_num; i++) { + int ret; + /* Link with previous command */ + prtd->dma_desc_array[i]->cmd.cmd.bits.bytes = prtd->dma_period; + prtd->dma_desc_array[i]->cmd.cmd.bits.irq = 1; + prtd->dma_desc_array[i]->cmd.cmd.bits.dec_sem = 0; + prtd->dma_desc_array[i]->cmd.cmd.bits.chain = 1; + /* Set DMA direction */ + if (playback) + prtd->dma_desc_array[i]->cmd.cmd.bits.command = \ + DMA_READ; + else + prtd->dma_desc_array[i]->cmd.cmd.bits.command = \ + DMA_WRITE; + + prtd->dma_desc_array[i]->cmd.address = dma_buffer_phys; + + ret = mxs_dma_desc_append(prtd->dma_ch, \ + prtd->dma_desc_array[i]); + if (ret) { + printk(KERN_ERR "%s: Failed to append DMA descriptor\n", + __func__); + return ret; + } + /* Next data chunk */ + dma_buffer_phys += prtd->dma_period; + } + + return 0; +} + +/* + * Stop circular DMA descriptor list + * We should not stop DMA in a middle of current transaction once we receive + * stop request from ALSA core. This function finds the next DMA descriptor + * and set it up to decrement DMA channel semaphore. So the current transaction + * is the last data transfer. + */ +static void mxs_pcm_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = runtime->private_data; + struct mxs_dma_info dma_info; + int desc; + + int periods_num = prtd->dma_totsize / prtd->dma_period; + /* Freez DMA channel for a moment */ + mxs_dma_freeze(prtd->dma_ch); + mxs_dma_get_info(prtd->dma_ch, &dma_info); + + desc = (dma_info.buf_addr - runtime->dma_addr) / prtd->dma_period; + + if (desc >= periods_num) + desc = 0; + else if (desc < 0) + desc = 0; + + /* Set up the next descriptor to decrement DMA channel sempahore */ + prtd->dma_desc_array[(desc + 1)%periods_num]->cmd.cmd.bits.bytes = 0; + prtd->dma_desc_array[(desc + 1)%periods_num]->cmd.cmd.bits.pio_words = \ + 0; + prtd->dma_desc_array[(desc + 1)%periods_num]->cmd.cmd.bits.dec_sem = 1; + prtd->dma_desc_array[(desc + 1)%periods_num]->cmd.cmd.bits.irq = 0; + prtd->dma_desc_array[(desc + 1)%periods_num]->cmd.cmd.bits.command = \ + NO_DMA_XFER; + + mxs_dma_unfreeze(prtd->dma_ch); + + mxs_dma_disable(prtd->dma_ch); +} + +static int mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) && + ((prtd->format == SNDRV_PCM_FORMAT_S24_LE) + || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) { + prtd->appl_ptr_bytes = + frames_to_bytes(runtime, + runtime->control->appl_ptr); + memmove(runtime->dma_area + 1, runtime->dma_area, + prtd->appl_ptr_bytes - 1); + } + mxs_dma_enable(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_STOP: + mxs_pcm_stop(substream); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mxs_dma_unfreeze(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + mxs_dma_freeze(prtd->dma_ch); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t +mxs_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = runtime->private_data; + struct mxs_dma_info dma_info; + unsigned int offset; + dma_addr_t pos; + + mxs_dma_get_info(prtd->params->dma_ch, &dma_info); + pos = dma_info.buf_addr; + + offset = bytes_to_frames(runtime, pos - runtime->dma_addr); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int mxs_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct mxs_runtime_data *prtd = substream->runtime->private_data; + + prtd->dma_period = params_period_bytes(hw_params); + prtd->dma_totsize = params_buffer_bytes(hw_params); + prtd->format = params_format(hw_params); + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int mxs_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int mxs_pcm_dma_request(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mxs_runtime_data *prtd = runtime->private_data; + struct mxs_pcm_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + int desc_num = mxs_pcm_hardware.periods_max; + int desc; + int ret; + + if (!dma_data) + return -ENODEV; + + prtd->params = dma_data; + prtd->dma_ch = dma_data->dma_ch; + + ret = mxs_dma_request(prtd->dma_ch, mxs_pcm_dev, + prtd->params->name); + if (ret) { + printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n", + __func__, dma_data->dma_bus, dma_data->dma_ch); + return ret; + } + + /* Allocate memory for data and pio DMA descriptors */ + for (desc = 0; desc < desc_num; desc++) { + prtd->dma_desc_array[desc] = mxs_dma_alloc_desc(); + if (prtd->dma_desc_array[desc] == NULL) { + printk(KERN_ERR"%s Unable to allocate DMA command %d\n", + __func__, desc); + goto err; + } + } + + ret = request_irq(prtd->params->irq, mxs_pcm_dma_irq, 0, + "MXS PCM DMA", substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__, + prtd->params->irq); + goto err; + } + /* Enable completion interrupt */ + mxs_dma_ack_irq(prtd->dma_ch); + mxs_dma_enable_irq(prtd->dma_ch, 1); + + return 0; + +err: + while (--desc >= 0) + mxs_dma_free_desc(prtd->dma_desc_array[desc]); + mxs_dma_release(prtd->dma_ch, mxs_pcm_dev); + + return ret; +} + +static int mxs_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd; + int ret; + + /* Ensure that buffer size is a multiple of the period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &mxs_pcm_hardware); + + prtd = kzalloc(sizeof(struct mxs_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + + ret = mxs_pcm_dma_request(substream); + if (ret) { + printk(KERN_ERR "mxs_pcm: Failed to request channels\n"); + kfree(prtd); + return ret; + } + return 0; +} + +static int mxs_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxs_runtime_data *prtd = runtime->private_data; + int desc_num = mxs_pcm_hardware.periods_max; + int desc; + + static LIST_HEAD(list); + mxs_dma_disable(prtd->dma_ch); + /* Free DMA irq */ + free_irq(prtd->params->irq, substream); + mxs_dma_get_cooked(prtd->dma_ch, &list); + /* Free DMA channel*/ + mxs_dma_reset(prtd->dma_ch); + for (desc = 0; desc < desc_num; desc++) + mxs_dma_free_desc(prtd->dma_desc_array[desc]); + mxs_dma_release(prtd->dma_ch, mxs_pcm_dev); + + /* Free private runtime data */ + kfree(prtd); + return 0; +} + +static int mcs_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; + struct mxs_runtime_data *prtd = runtime->private_data; + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff); + unsigned long count = frames_to_bytes(runtime, frames); + + /* For S/PDIF 24-bit playback, fix the buffer. Code taken from + snd_pcm_lib_write_transfer() and snd_pcm_lib_read_transfer() + in sound/core/pcm_lib.c */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) && + ((prtd->format == SNDRV_PCM_FORMAT_S24_LE) + || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) { + if (copy_from_user(hwbuf + 1, buf, count - 1)) + return -EFAULT; + } else { + if (copy_from_user(hwbuf, buf, count)) + return -EFAULT; + } + } else { + if (copy_to_user(buf, hwbuf, count)) + return -EFAULT; + } + + return 0; +} + +static int mxs_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +struct snd_pcm_ops mxs_pcm_ops = { + .open = mxs_pcm_open, + .close = mxs_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mxs_pcm_hw_params, + .hw_free = mxs_pcm_hw_free, + .prepare = mxs_pcm_prepare, + .trigger = mxs_pcm_trigger, + .pointer = mxs_pcm_pointer, + .copy = mcs_pcm_copy, + .mmap = mxs_pcm_mmap, +}; + +static u64 mxs_pcm_dma_mask = DMA_BIT_MASK(32); + +static int mxs_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + size_t size = mxs_pcm_hardware.buffer_bytes_max; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &mxs_pcm_dma_mask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL, + size, size); + + return 0; +} + +static void mxs_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +/* + * We need probe/remove callbacks to setup mxs_pcm_dev + */ +static int mxs_pcm_probe(struct platform_device *pdev) +{ + mxs_pcm_dev = &pdev->dev; + return 0; +} + +static int mxs_pcm_remove(struct platform_device *pdev) +{ + mxs_pcm_dev = NULL; + return 0; +} + +struct snd_soc_platform mxs_soc_platform = { + .name = "MXS Audio", + .pcm_ops = &mxs_pcm_ops, + .probe = mxs_pcm_probe, + .remove = mxs_pcm_remove, + .pcm_new = mxs_pcm_new, + .pcm_free = mxs_pcm_free, +}; +EXPORT_SYMBOL_GPL(mxs_soc_platform); + +static int __init mxs_pcm_init(void) +{ + return snd_soc_register_platform(&mxs_soc_platform); +} + +static void __exit mxs_pcm_exit(void) +{ + snd_soc_unregister_platform(&mxs_soc_platform); +} +module_init(mxs_pcm_init); +module_exit(mxs_pcm_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS DMA Module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h new file mode 100644 index 000000000000..738e3fd82d73 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009-2010 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 + +struct mxs_pcm_dma_params { + char *name; + int dma_bus; /* DMA bus */ + int dma_ch; /* DMA channel number */ + int irq; /* DMA interrupt number */ +}; +struct mxs_runtime_data { + u32 dma_ch; + u32 dma_period; + u32 dma_totsize; + unsigned long appl_ptr_bytes; + int format; + struct mxs_pcm_dma_params *params; + struct mxs_dma_desc *dma_desc_array[255]; +}; + +extern struct snd_soc_platform mxs_soc_platform; + +#endif diff --git a/sound/soc/mxs/mxs-spdif-dai.c b/sound/soc/mxs/mxs-spdif-dai.c new file mode 100644 index 000000000000..1b45b595b90d --- /dev/null +++ b/sound/soc/mxs/mxs-spdif-dai.c @@ -0,0 +1,203 @@ +/* + * ALSA SoC SPDIF Audio Layer for MXS + * + * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif_dai.c + * Vladimir Barinov <vbarinov@embeddedalley.com> + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <mach/hardware.h> + +#include "../codecs/mxs_spdif.h" +#include "mxs-pcm.h" + +#define REGS_SPDIF_BASE IO_ADDRESS(SPDIF_PHYS_ADDR) + +struct mxs_pcm_dma_params mxs_spdif = { + .name = "mxs spdif", + .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SPDIF, + .irq = IRQ_SPDIF_DMA, +}; + +static irqreturn_t mxs_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg = 0; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = __raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL); + underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "underflow detected SPDIF\n"); + + if (playback) + __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "overflow detected SPDIF\n"); + + if (playback) + __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + } else + printk(KERN_WARNING "Unknown SPDIF error interrupt\n"); + + return IRQ_HANDLED; +} + +static int mxs_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) + __raw_writel(BM_SPDIF_CTRL_RUN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (playback) + __raw_writel(BM_SPDIF_CTRL_RUN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + + default: + ret = -EINVAL; + } + + return ret; +} + +static int mxs_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int ret; + + if (playback) { + irq = IRQ_SPDIF_ERROR; + snd_soc_dai_set_dma_data(cpu_dai, substream, &mxs_spdif); + } + + ret = request_irq(irq, mxs_err_irq, 0, "Mxs SPDIF Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n", + __func__, IRQ_SPDIF_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + } + + return 0; +} + +static void mxs_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + free_irq(IRQ_SPDIF_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int mxs_spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int mxs_spdif_resume(struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define mxs_spdif_suspend NULL +#define mxs_spdif_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai_ops mxs_spdif_dai_ops = { + .startup = mxs_spdif_startup, + .shutdown = mxs_spdif_shutdown, + .trigger = mxs_spdif_trigger, +}; + +struct snd_soc_dai mxs_spdif_dai = { + .name = "mxs-spdif", + .id = 0, + .suspend = mxs_spdif_suspend, + .resume = mxs_spdif_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SPDIF_RATES, + .formats = MXS_SPDIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SPDIF_RATES, + .formats = MXS_SPDIF_FORMATS, + }, + .ops = &mxs_spdif_dai_ops, +}; +EXPORT_SYMBOL_GPL(mxs_spdif_dai); + +static int __init mxs_spdif_dai_init(void) +{ + return snd_soc_register_dai(&mxs_spdif_dai); +} + +static void __exit mxs_spdif_dai_exit(void) +{ + snd_soc_unregister_dai(&mxs_spdif_dai); +} +module_init(mxs_spdif_dai_init); +module_exit(mxs_spdif_dai_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("MXS SPDIF DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-spdif-dai.h b/sound/soc/mxs/mxs-spdif-dai.h new file mode 100644 index 000000000000..035369af9676 --- /dev/null +++ b/sound/soc/mxs/mxs-spdif-dai.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter + * + * Author: Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_SPDIF_H +#define _STMP3XXX_SPDIF_H +extern struct snd_soc_dai mxs_spdif_dai; +#endif |