From 2731b2eadeaa141e6f305fa8086106608112bbaa Mon Sep 17 00:00:00 2001 From: Roshni Shah Date: Mon, 14 Mar 2011 06:49:42 -0400 Subject: Add support for the i.MX53 QSB 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 --- sound/soc/mxs/Kconfig | 51 +++ sound/soc/mxs/Makefile | 16 + sound/soc/mxs/mxs-adc.c | 477 ++++++++++++++++++++++++++++ sound/soc/mxs/mxs-adc.h | 21 ++ sound/soc/mxs/mxs-dai.c | 689 +++++++++++++++++++++++++++++++++++++++++ sound/soc/mxs/mxs-dai.h | 42 +++ sound/soc/mxs/mxs-devb-spdif.c | 90 ++++++ sound/soc/mxs/mxs-devb.c | 282 +++++++++++++++++ sound/soc/mxs/mxs-evk-adc.c | 198 ++++++++++++ sound/soc/mxs/mxs-pcm.c | 504 ++++++++++++++++++++++++++++++ sound/soc/mxs/mxs-pcm.h | 40 +++ sound/soc/mxs/mxs-spdif-dai.c | 203 ++++++++++++ sound/soc/mxs/mxs-spdif-dai.h | 21 ++ 13 files changed, 2634 insertions(+) create mode 100644 sound/soc/mxs/Kconfig create mode 100644 sound/soc/mxs/Makefile create mode 100644 sound/soc/mxs/mxs-adc.c create mode 100644 sound/soc/mxs/mxs-adc.h create mode 100644 sound/soc/mxs/mxs-dai.c create mode 100644 sound/soc/mxs/mxs-dai.h create mode 100644 sound/soc/mxs/mxs-devb-spdif.c create mode 100644 sound/soc/mxs/mxs-devb.c create mode 100644 sound/soc/mxs/mxs-evk-adc.c create mode 100644 sound/soc/mxs/mxs-pcm.c create mode 100644 sound/soc/mxs/mxs-pcm.h create mode 100644 sound/soc/mxs/mxs-spdif-dai.c create mode 100644 sound/soc/mxs/mxs-spdif-dai.h (limited to 'sound/soc/mxs') 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + +/* 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 -- cgit v1.2.3