/* * omap-abe.c -- OMAP ALSA SoC DAI driver using Audio Backend * * Copyright (C) 2010 Texas Instruments * * Contact: Liam Girdwood * Misael Lopez Cruz * Sebastien Guiriec * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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 St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "omap-pcm.h" #include "omap-abe.h" #include "omap-abe-dsp.h" #include "abe/abe_main.h" #include "abe/port_mgr.h" #define OMAP_ABE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) struct omap_abe_data { /* MODEM FE*/ struct snd_pcm_substream *modem_substream[2]; struct snd_soc_dai *modem_dai; struct abe *abe; /* BE & FE Ports */ struct omap_abe_port *port[OMAP_ABE_MAX_PORT_ID + 1]; int active_dais; int suspended_dais; }; /* * Stream DMA parameters */ static struct omap_pcm_dma_data omap_abe_dai_dma_params[7][2] = { { { .name = "Media Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_0, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, { .name = "Media Capture1", .dma_req = OMAP44XX_DMA_ABE_REQ_3, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, }, { {}, { .name = "Media Capture2", .dma_req = OMAP44XX_DMA_ABE_REQ_4, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, }, { { .name = "Voice Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_1, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, { .name = "Voice Capture", .dma_req = OMAP44XX_DMA_ABE_REQ_2, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, }, { { .name = "Tones Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_5, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, },{}, }, { { .name = "Vibra Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_6, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, },{}, }, { { .name = "MODEM Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_1, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, { .name = "MODEM Capture", .dma_req = OMAP44XX_DMA_ABE_REQ_2, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, }, }, { { .name = "Low Power Playback", .dma_req = OMAP44XX_DMA_ABE_REQ_0, .data_type = OMAP_DMA_DATA_TYPE_S32, .sync_mode = OMAP_DMA_SYNC_PACKET, },{}, },}; static int modem_get_dai(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); struct snd_soc_pcm_runtime *modem_rtd; abe_priv->modem_substream[substream->stream] = snd_soc_get_dai_substream(rtd->card, OMAP_ABE_BE_MM_EXT1, !substream->stream); if (abe_priv->modem_substream[substream->stream] == NULL) return -ENODEV; modem_rtd = abe_priv->modem_substream[substream->stream]->private_data; abe_priv->modem_substream[substream->stream]->runtime = substream->runtime; abe_priv->modem_dai = modem_rtd->cpu_dai; return 0; } int omap_abe_set_dl1_output(int output) { int gain; /* * the output itself is not important, but the DL1 gain * to use when each output is active */ switch (output) { case OMAP_ABE_DL1_HEADSET_LP: gain = GAIN_M8dB; break; case OMAP_ABE_DL1_HEADSET_HP: case OMAP_ABE_DL1_EARPIECE: gain = GAIN_M1dB; break; #if !defined(CONFIG_SND_OMAP_SOC_ABE_DL2) case OMAP_ABE_DL1_HANDSFREE: gain = GAIN_M7dB; break; #endif case OMAP_ABE_DL1_NO_PDM: gain = GAIN_0dB; break; default: return -EINVAL; } abe_write_gain(GAINS_DL1, gain, RAMP_2MS, GAIN_LEFT_OFFSET); abe_write_gain(GAINS_DL1, gain, RAMP_2MS, GAIN_RIGHT_OFFSET); return 0; } EXPORT_SYMBOL(omap_abe_set_dl1_output); static int omap_abe_dl1_enabled(struct omap_abe_data *abe_priv) { int dl1; /* DL1 path is common for PDM_DL1, BT_VX_DL and MM_EXT_DL */ dl1 = omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL1]) + omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]) + omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL]); #if !defined(CONFIG_SND_OMAP_SOC_ABE_DL2) dl1 += omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2]); #endif return dl1; } static void mute_be(struct snd_soc_pcm_runtime *be, struct snd_soc_dai *dai, int stream) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (be->dai_link->be_id) { case OMAP_ABE_DAI_PDM_DL1: case OMAP_ABE_DAI_BT_VX: case OMAP_ABE_DAI_MM_FM: /* * DL1 Mixer->SDT Mixer and DL1 gain are common for * PDM_DL1, BT_VX_DL and MM_EXT_DL, mute those gains * only if the last active BE */ if (omap_abe_dl1_enabled(abe_priv) == 1) { abe_mute_gain(GAINS_DL1, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DL1, GAIN_RIGHT_OFFSET); abe_mute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); } break; case OMAP_ABE_DAI_PDM_DL2: #if defined(CONFIG_SND_OMAP_SOC_ABE_DL2) abe_mute_gain(GAINS_DL2, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DL2, GAIN_RIGHT_OFFSET); #else if (omap_abe_dl1_enabled(abe_priv) == 1) { abe_mute_gain(GAINS_DL1, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DL1, GAIN_RIGHT_OFFSET); abe_mute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); } #endif break; case OMAP_ABE_DAI_PDM_VIB: case OMAP_ABE_DAI_MODEM: break; } } else { switch (be->dai_link->be_id) { case OMAP_ABE_DAI_PDM_UL: abe_mute_gain(GAINS_AMIC, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_AMIC, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_BT_VX: abe_mute_gain(GAINS_BTUL, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_BTUL, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_MM_FM: case OMAP_ABE_DAI_MODEM: break; case OMAP_ABE_DAI_DMIC0: abe_mute_gain(GAINS_DMIC1, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DMIC1, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_DMIC1: abe_mute_gain(GAINS_DMIC2, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DMIC2, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_DMIC2: abe_mute_gain(GAINS_DMIC3, GAIN_LEFT_OFFSET); abe_mute_gain(GAINS_DMIC3, GAIN_RIGHT_OFFSET); break; } } } static void unmute_be(struct snd_soc_pcm_runtime *be, struct snd_soc_dai *dai, int stream) { dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (be->dai_link->be_id) { case OMAP_ABE_DAI_PDM_DL1: case OMAP_ABE_DAI_BT_VX: case OMAP_ABE_DAI_MM_FM: /* * DL1 Mixer->SDT Mixer and DL1 gain are common for * PDM_DL1, BT_VX_DL and MM_EXT_DL, unmute when any * of them becomes active */ abe_unmute_gain(GAINS_DL1, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DL1, GAIN_RIGHT_OFFSET); abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); break; case OMAP_ABE_DAI_PDM_DL2: #if defined(CONFIG_SND_OMAP_SOC_ABE_DL2) abe_unmute_gain(GAINS_DL2, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DL2, GAIN_RIGHT_OFFSET); #else abe_unmute_gain(GAINS_DL1, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DL1, GAIN_RIGHT_OFFSET); abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); #endif break; case OMAP_ABE_DAI_PDM_VIB: break; case OMAP_ABE_DAI_MODEM: break; } } else { switch (be->dai_link->be_id) { case OMAP_ABE_DAI_PDM_UL: abe_unmute_gain(GAINS_AMIC, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_AMIC, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_BT_VX: abe_unmute_gain(GAINS_BTUL, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_BTUL, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_MM_FM: case OMAP_ABE_DAI_MODEM: break; case OMAP_ABE_DAI_DMIC0: abe_unmute_gain(GAINS_DMIC1, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DMIC1, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_DMIC1: abe_unmute_gain(GAINS_DMIC2, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DMIC2, GAIN_RIGHT_OFFSET); break; case OMAP_ABE_DAI_DMIC2: abe_unmute_gain(GAINS_DMIC3, GAIN_LEFT_OFFSET); abe_unmute_gain(GAINS_DMIC3, GAIN_RIGHT_OFFSET); break; } } } static void enable_be_port(struct snd_soc_pcm_runtime *be, struct snd_soc_dai *dai, int stream) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); abe_data_format_t format; dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); switch (be->dai_link->be_id) { /* McPDM is a special case, handled by McPDM driver */ case OMAP_ABE_DAI_PDM_DL1: case OMAP_ABE_DAI_PDM_DL2: case OMAP_ABE_DAI_PDM_VIB: case OMAP_ABE_DAI_PDM_UL: break; case OMAP_ABE_DAI_BT_VX: if (stream == SNDRV_PCM_STREAM_PLAYBACK) { /* port can only be configured if it's not running */ if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL])) return; /* BT_DL connection to McBSP 1 ports */ format.f = 8000; format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(BT_VX_DL_PORT, &format, MCBSP1_TX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]); } else { /* port can only be configured if it's not running */ if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL])) return; /* BT_UL connection to McBSP 1 ports */ format.f = 8000; format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(BT_VX_UL_PORT, &format, MCBSP1_RX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL]); } break; case OMAP_ABE_DAI_MM_FM: if (stream == SNDRV_PCM_STREAM_PLAYBACK) { /* port can only be configured if it's not running */ if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL])) return; /* MM_EXT connection to McBSP 2 ports */ format.f = 48000; format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(MM_EXT_OUT_PORT, &format, MCBSP2_TX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL]); } else { /* port can only be configured if it's not running */ if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL])) return; /* MM_EXT connection to McBSP 2 ports */ format.f = 48000; format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(MM_EXT_IN_PORT, &format, MCBSP2_RX); omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL]); } break; case OMAP_ABE_DAI_DMIC0: omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC0]); break; case OMAP_ABE_DAI_DMIC1: omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC1]); break; case OMAP_ABE_DAI_DMIC2: omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC2]); break; } } static void enable_fe_port(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int stream) { struct snd_soc_pcm_runtime *fe = substream->private_data; struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); switch(dai->id) { case ABE_FRONTEND_DAI_MEDIA: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_DL1]); else omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_UL1]); break; case ABE_FRONTEND_DAI_LP_MEDIA: abe_enable_data_transfer(MM_DL_PORT); break; case ABE_FRONTEND_DAI_MEDIA_CAPTURE: if (stream == SNDRV_PCM_STREAM_CAPTURE) omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_UL2]); break; case ABE_FRONTEND_DAI_MODEM: case ABE_FRONTEND_DAI_VOICE: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VX_DL]); else omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VX_UL]); break; case ABE_FRONTEND_DAI_TONES: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_TONES]); break; case ABE_FRONTEND_DAI_VIBRA: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_enable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VIB]); break; } } static void disable_be_port(struct snd_soc_pcm_runtime *be, struct snd_soc_dai *dai, int stream) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(&be->dev, "%s: %s %d\n", __func__, be->cpu_dai->name, stream); switch (be->dai_link->be_id) { /* McPDM is a special case, handled by McPDM driver */ case OMAP_ABE_DAI_PDM_DL1: case OMAP_ABE_DAI_PDM_DL2: case OMAP_ABE_DAI_PDM_VIB: case OMAP_ABE_DAI_PDM_UL: break; case OMAP_ABE_DAI_BT_VX: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_DL]); else omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_BT_VX_UL]); break; case OMAP_ABE_DAI_MM_FM: case OMAP_ABE_DAI_MODEM: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_DL]); else omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_MM_EXT_UL]); break; case OMAP_ABE_DAI_DMIC0: omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC0]); break; case OMAP_ABE_DAI_DMIC1: omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC1]); break; case OMAP_ABE_DAI_DMIC2: omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_DMIC2]); break; } } static void disable_fe_port(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int stream) { struct snd_soc_pcm_runtime *fe = substream->private_data; struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); switch(dai->id) { case ABE_FRONTEND_DAI_MEDIA: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_DL1]); else omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_UL1]); break; case ABE_FRONTEND_DAI_LP_MEDIA: abe_disable_data_transfer(MM_DL_PORT); break; case ABE_FRONTEND_DAI_MEDIA_CAPTURE: if (stream == SNDRV_PCM_STREAM_CAPTURE) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_MM_UL2]); break; case ABE_FRONTEND_DAI_MODEM: case ABE_FRONTEND_DAI_VOICE: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VX_DL]); else omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VX_UL]); break; case ABE_FRONTEND_DAI_TONES: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_TONES]); break; case ABE_FRONTEND_DAI_VIBRA: if (stream == SNDRV_PCM_STREAM_PLAYBACK) omap_abe_port_disable(abe_priv->abe, abe_priv->port[OMAP_ABE_FE_PORT_VIB]); break; } } static void mute_fe_port_capture(struct snd_soc_pcm_runtime *fe, int mute) { dev_dbg(&fe->dev, "%s: %s FE %s\n", __func__, mute ? "mute" : "unmute", fe->dai_link->name); switch (fe->cpu_dai->id) { case ABE_FRONTEND_DAI_MEDIA_CAPTURE: if (mute) { abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); } else { abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); } break; case ABE_FRONTEND_DAI_MODEM: case ABE_FRONTEND_DAI_VOICE: if (mute) { abe_mute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); } else { abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); } break; case ABE_FRONTEND_DAI_MEDIA: default: break; } } static void mute_fe_port_playback(struct snd_soc_pcm_runtime *fe, int mute) { dev_dbg(&fe->dev, "%s: %s FE %s\n", __func__, mute ? "mute" : "unmute", fe->dai_link->name); switch (fe->cpu_dai->id) { case ABE_FRONTEND_DAI_MEDIA: case ABE_FRONTEND_DAI_LP_MEDIA: if (mute) { abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); } else { abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); } break; case ABE_FRONTEND_DAI_VOICE: case ABE_FRONTEND_DAI_MODEM: if (mute) { abe_mute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); } else { abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); } break; case ABE_FRONTEND_DAI_TONES: if (mute) { abe_mute_gain(MIXDL1, MIX_DL1_INPUT_TONES); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_TONES); } else { abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_TONES); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_TONES); } break; case ABE_FRONTEND_DAI_VIBRA: default: break; } } static void mute_fe_port(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int stream) { struct snd_soc_pcm_runtime *fe = substream->private_data; dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); if (stream == SNDRV_PCM_STREAM_PLAYBACK) mute_fe_port_playback(fe, 1); else mute_fe_port_capture(fe, 1); } static void unmute_fe_port(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int stream) { struct snd_soc_pcm_runtime *fe = substream->private_data; dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); if (stream == SNDRV_PCM_STREAM_PLAYBACK) mute_fe_port_playback(fe, 0); else mute_fe_port_capture(fe, 0); } static void capture_trigger(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int cmd) { struct snd_soc_pcm_runtime *fe = substream->private_data; struct snd_soc_dsp_params *dsp_params; struct snd_pcm_substream *be_substream; int stream = substream->stream; dev_dbg(&fe->dev, "%s: %s %d\n", __func__, fe->cpu_dai->name, stream); switch (cmd) { case SNDRV_PCM_TRIGGER_START: /* mute and enable BE ports */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dsp_params->be; /* does this trigger() apply to this BE and stream ? */ if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; if ((be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; be_substream = snd_soc_dsp_get_substream(be, stream); /* mute the BE port */ mute_be(be, dai, stream); /* enable the BE port */ enable_be_port(be, dai, stream); /* DAI work must be started/stopped at least 250us after ABE */ udelay(250); /* trigger the BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); be->dsp[stream].state = SND_SOC_DSP_STATE_START; } /* does this trigger() apply to the FE ? */ if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { /* Enable Frontend sDMA */ snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); enable_fe_port(substream, dai, stream); /* unmute FE port */ unmute_fe_port(substream, dai, stream); } /* Restore ABE GAINS AMIC */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dsp_params->be; /* does this trigger() apply to this BE and stream ? */ if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; /* unmute this BE port */ unmute_be(be, dai, stream); } break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* Enable sDMA */ snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); enable_fe_port(substream, dai, stream); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* Disable sDMA */ disable_fe_port(substream, dai, stream); snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); break; case SNDRV_PCM_TRIGGER_STOP: /* does this trigger() apply to the FE ? */ if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { /* mute FE port */ mute_fe_port(substream, dai, stream); /* Disable sDMA */ disable_fe_port(substream, dai, stream); snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); } /* disable BE ports */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dsp_params->be; /* does this trigger() apply to this BE and stream ? */ if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; if (be->dsp[stream].state != SND_SOC_DSP_STATE_START) continue; /* only stop if last running user */ if (soc_dsp_fe_state_count(be, stream, SND_SOC_DSP_STATE_START) > 1) continue; be_substream = snd_soc_dsp_get_substream(be, stream); /* mute the BE port */ mute_be(be, dai, stream); /* disable the BE port */ disable_be_port(be, dai, stream); /* DAI work must be started/stopped at least 250us after ABE */ udelay(250); /* trigger BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); be->dsp[stream].state = SND_SOC_DSP_STATE_STOP; } break; default: break; } } static void playback_trigger(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, int cmd) { struct snd_soc_pcm_runtime *fe = substream->private_data; struct snd_soc_dsp_params *dsp_params; struct snd_pcm_substream *be_substream; int stream = substream->stream; dev_dbg(&fe->dev, "%s: %s %d\n", __func__, fe->cpu_dai->name, stream); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: /* mute and enable ports */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dsp_params->be; /* does this trigger() apply to the FE ? */ if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; if ((be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; be_substream = snd_soc_dsp_get_substream(be, stream); /* mute BE port */ mute_be(be, dai, stream); /* enabled BE port */ enable_be_port(be, dai, stream); /* DAI work must be started/stopped at least 250us after ABE */ udelay(250); /* trigger BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); /* unmute the BE port */ unmute_be(be, dai, stream); be->dsp[stream].state = SND_SOC_DSP_STATE_START; } /* does this trigger() apply to the FE ? */ if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { /* Enable Frontend sDMA */ snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); enable_fe_port(substream, dai, stream); } /* unmute FE port (sensitive to runtime udpates) */ unmute_fe_port(substream, dai, stream); break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* Enable Frontend sDMA */ snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); enable_fe_port(substream, dai, stream); /* unmute FE port */ unmute_fe_port(substream, dai, stream); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* mute FE port */ mute_fe_port(substream, dai, stream); /* disable Frontend sDMA */ disable_fe_port(substream, dai, stream); snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: /* does this trigger() apply to the FE ? */ if (snd_soc_dsp_is_trigger_for_fe(fe, stream)) { /* mute FE port (sensitive to runtime udpates) */ mute_fe_port(substream, dai, stream); /* disable the transfer */ disable_fe_port(substream, dai, stream); snd_soc_dsp_platform_trigger(substream, cmd, fe->platform); } /* disable BE ports */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dsp_params->be; /* does this trigger() apply to this BE and stream ? */ if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; if (be->dsp[stream].state != SND_SOC_DSP_STATE_START) continue; /* only stop if last running user */ if (soc_dsp_fe_state_count(be, stream, SND_SOC_DSP_STATE_START) > 1) continue; be_substream = snd_soc_dsp_get_substream(be, stream); /* mute the BE port */ mute_be(be, dai, stream); /* disable the BE */ disable_be_port(be, dai, stream); /* DAI work must be started/stopped at least 250us after ABE */ udelay(250); /* trigger the BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); be->dsp[stream].state = SND_SOC_DSP_STATE_STOP; } break; default: break; } } static int omap_abe_hwrule_period_step(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_interval *period_size = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); unsigned int rate = params_rate(params); /* 44.1kHz has the same iteration number as 48kHz */ rate = (rate == 44100) ? 48000 : rate; /* ABE requires chunks of 250us worth of data */ return snd_interval_step(period_size, 0, rate / 4000); } static int omap_abe_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); int ret = 0; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); abe_priv->active_dais++; abe_dsp_pm_get(); if (dai->id == ABE_FRONTEND_DAI_MODEM) { ret = modem_get_dai(substream, dai); if (ret < 0) { dev_err(dai->dev, "failed to get MODEM DAI\n"); return ret; } dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", __func__, substream->stream); ret = snd_soc_dai_startup(abe_priv->modem_substream[substream->stream], abe_priv->modem_dai); if (ret < 0) { dev_err(abe_priv->modem_dai->dev, "failed to open DAI %d\n", ret); return ret; } } else { /* * Period size must be aligned with the Audio Engine * processing loop which is 250 us long */ snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, omap_abe_hwrule_period_step, NULL, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); } return ret; } static int omap_abe_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); struct omap_pcm_dma_data *dma_data; abe_data_format_t format; abe_dma_t dma_sink; abe_dma_t dma_params; int ret; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); dma_data = &omap_abe_dai_dma_params[dai->id][substream->stream]; switch (params_channels(params)) { case 1: if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) format.samp_format = MONO_16_16; else format.samp_format = MONO_MSB; break; case 2: if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) format.samp_format = STEREO_16_16; else format.samp_format = STEREO_MSB; break; case 3: format.samp_format = THREE_MSB; break; case 4: format.samp_format = FOUR_MSB; break; case 5: format.samp_format = FIVE_MSB; break; case 6 : format.samp_format = SIX_MSB; break; case 7 : format.samp_format = SEVEN_MSB; break; case 8: format.samp_format = EIGHT_MSB; break; default: dev_err(dai->dev, "%d channels not supported", params_channels(params)); return -EINVAL; } format.f = params_rate(params); switch (dai->id) { case ABE_FRONTEND_DAI_MEDIA: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { abe_connect_cbpr_dmareq_port(MM_DL_PORT, &format, ABE_CBPR0_IDX, &dma_sink); abe_read_port_address(MM_DL_PORT, &dma_params); } else { abe_connect_cbpr_dmareq_port(MM_UL_PORT, &format, ABE_CBPR3_IDX, &dma_sink); abe_read_port_address(MM_UL_PORT, &dma_params); } break; case ABE_FRONTEND_DAI_LP_MEDIA: return 0; break; case ABE_FRONTEND_DAI_MEDIA_CAPTURE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -EINVAL; else { abe_connect_cbpr_dmareq_port(MM_UL2_PORT, &format, ABE_CBPR4_IDX, &dma_sink); abe_read_port_address(MM_UL2_PORT, &dma_params); } break; case ABE_FRONTEND_DAI_VOICE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { abe_connect_cbpr_dmareq_port(VX_DL_PORT, &format, ABE_CBPR1_IDX, &dma_sink); abe_read_port_address(VX_DL_PORT, &dma_params); } else { abe_connect_cbpr_dmareq_port(VX_UL_PORT, &format, ABE_CBPR2_IDX, &dma_sink); abe_read_port_address(VX_UL_PORT, &dma_params); } break; case ABE_FRONTEND_DAI_TONES: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { abe_connect_cbpr_dmareq_port(TONES_DL_PORT, &format, ABE_CBPR5_IDX, &dma_sink); abe_read_port_address(TONES_DL_PORT, &dma_params); } else return -EINVAL; break; case ABE_FRONTEND_DAI_VIBRA: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { abe_connect_cbpr_dmareq_port(VIB_DL_PORT, &format, ABE_CBPR6_IDX, &dma_sink); abe_read_port_address(VIB_DL_PORT, &dma_params); } else return -EINVAL; break; case ABE_FRONTEND_DAI_MODEM: /* MODEM is special case where data IO is performed by McBSP2 * directly onto VX_DL and VX_UL (instead of SDMA). */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* Vx_DL connection to McBSP 2 ports */ format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(VX_DL_PORT, &format, MCBSP2_RX); abe_read_port_address(VX_DL_PORT, &dma_params); } else { /* Vx_UL connection to McBSP 2 ports */ format.samp_format = STEREO_RSHIFTED_16; abe_connect_serial_port(VX_UL_PORT, &format, MCBSP2_TX); abe_read_port_address(VX_UL_PORT, &dma_params); } break; } /* configure frontend SDMA data */ dma_data->port_addr = (unsigned long)dma_params.data; dma_data->packet_size = dma_params.iter; if (dai->id == ABE_FRONTEND_DAI_MODEM) { /* call hw_params on McBSP with correct DMA data */ snd_soc_dai_set_dma_data(abe_priv->modem_dai, substream, dma_data); dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", __func__, substream->stream); ret = snd_soc_dai_hw_params(abe_priv->modem_substream[substream->stream], params, abe_priv->modem_dai); if (ret < 0) dev_err(abe_priv->modem_dai->dev, "MODEM hw_params failed\n"); return ret; } snd_soc_dai_set_dma_data(dai, substream, dma_data); return 0; } static int omap_abe_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); int ret = 0; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); if (dai->id == ABE_FRONTEND_DAI_MODEM) { ret = snd_soc_dai_prepare(abe_priv->modem_substream[substream->stream], abe_priv->modem_dai); dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", __func__, substream->stream); if (ret < 0) { dev_err(abe_priv->modem_dai->dev, "MODEM prepare failed\n"); return ret; } } return ret; } static int omap_abe_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); int ret = 0; dev_dbg(dai->dev, "%s: %s cmd %d\n", __func__, dai->name, cmd); if (dai->id == ABE_FRONTEND_DAI_MODEM) { dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d cmd %d\n", __func__, substream->stream, cmd); ret = snd_soc_dai_trigger(abe_priv->modem_substream[substream->stream], cmd, abe_priv->modem_dai); if (ret < 0) { dev_err(abe_priv->modem_dai->dev, "MODEM trigger failed\n"); return ret; } } return ret; } static int omap_abe_dai_bespoke_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *fe = substream->private_data; struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); int ret = 0; dev_dbg(dai->dev, "%s: %s cmd %d\n", __func__, dai->name, cmd); if ((dai->id == ABE_FRONTEND_DAI_MODEM) && snd_soc_dsp_is_trigger_for_fe(fe, substream->stream)) { dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d cmd %d\n", __func__, substream->stream, cmd); ret = snd_soc_dai_trigger(abe_priv->modem_substream[substream->stream], cmd, abe_priv->modem_dai); if (ret < 0) { dev_err(abe_priv->modem_dai->dev, "MODEM trigger failed\n"); return ret; } } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) playback_trigger(substream, dai, cmd); else capture_trigger(substream, dai, cmd); return ret; } static int omap_abe_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); int ret = 0; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); if (dai->id == ABE_FRONTEND_DAI_MODEM) { dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", __func__, substream->stream); ret = snd_soc_dai_hw_free(abe_priv->modem_substream[substream->stream], abe_priv->modem_dai); if (ret < 0) { dev_err(abe_priv->modem_dai->dev, "MODEM hw_free failed\n"); return ret; } } return ret; } static void omap_abe_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); if (dai->id == ABE_FRONTEND_DAI_MODEM) { dev_dbg(abe_priv->modem_dai->dev, "%s: MODEM stream %d\n", __func__, substream->stream); snd_soc_dai_shutdown(abe_priv->modem_substream[substream->stream], abe_priv->modem_dai); } abe_dsp_shutdown(); abe_dsp_pm_put(); abe_priv->active_dais--; } #ifdef CONFIG_PM static int omap_abe_dai_suspend(struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(dai->dev, "%s: %s active %d\n", __func__, dai->name, dai->active); if (!dai->active) return 0; if (++abe_priv->suspended_dais < abe_priv->active_dais) return 0; abe_mute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); abe_mute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_MM_DL); abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_TONES); abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); abe_mute_gain(MIXAUDUL, MIX_AUDUL_INPUT_VX_DL); abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_TONES); abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_DL); abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_MM_DL); abe_mute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_UL); abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); abe_mute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); abe_mute_gain(MIXDL1, MIX_DL1_INPUT_TONES); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_TONES); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); abe_mute_gain(MIXECHO, MIX_ECHO_DL1); abe_mute_gain(MIXECHO, MIX_ECHO_DL2); return 0; } static int omap_abe_dai_resume(struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); dev_dbg(dai->dev, "%s: %s active %d\n", __func__, dai->name, dai->active); if (!dai->active) return 0; if (abe_priv->suspended_dais-- < abe_priv->active_dais) return 0; abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_UP_MIXER); abe_unmute_gain(MIXSDT, MIX_SDT_INPUT_DL1_MIXER); abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_MM_DL); abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_TONES); abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_UPLINK); abe_unmute_gain(MIXAUDUL, MIX_AUDUL_INPUT_VX_DL); abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_TONES); abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_DL); abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_MM_DL); abe_unmute_gain(MIXVXREC, MIX_VXREC_INPUT_VX_UL); abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_TONES); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_TONES); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); abe_unmute_gain(MIXECHO, MIX_ECHO_DL1); abe_unmute_gain(MIXECHO, MIX_ECHO_DL2); return 0; } #else #define omap_abe_dai_suspend NULL #define omap_abe_dai_resume NULL #endif static int omap_abe_dai_probe(struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv; int i; abe_priv = kzalloc(sizeof(struct omap_abe_data), GFP_KERNEL); if (abe_priv == NULL) return -ENOMEM; abe_priv->abe = omap_abe_port_mgr_get(); if (!abe_priv->abe) goto err; for (i = 0; i <= OMAP_ABE_MAX_PORT_ID; i++) { abe_priv->port[i] = omap_abe_port_open(abe_priv->abe, i); if (abe_priv->port[i] == NULL) { for (--i; i >= 0; i--) omap_abe_port_close(abe_priv->abe, abe_priv->port[i]); goto err_port; } } snd_soc_dai_set_drvdata(dai, abe_priv); return 0; err_port: omap_abe_port_mgr_put(abe_priv->abe); err: kfree(abe_priv); return -ENOMEM; } static int omap_abe_dai_remove(struct snd_soc_dai *dai) { struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(dai); omap_abe_port_mgr_put(abe_priv->abe); kfree(abe_priv); return 0; } static struct snd_soc_dai_ops omap_abe_dai_ops = { .startup = omap_abe_dai_startup, .shutdown = omap_abe_dai_shutdown, .hw_params = omap_abe_dai_hw_params, .hw_free = omap_abe_dai_hw_free, .prepare = omap_abe_dai_prepare, .trigger = omap_abe_dai_trigger, .bespoke_trigger = omap_abe_dai_bespoke_trigger, }; static struct snd_soc_dai_driver omap_abe_dai[] = { { /* Multimedia Playback and Capture */ .name = "MultiMedia1", .probe = omap_abe_dai_probe, .remove = omap_abe_dai_remove, .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "MultiMedia1 Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, .formats = OMAP_ABE_FORMATS, }, .capture = { .stream_name = "MultiMedia1 Capture", .channels_min = 2, .channels_max = 6, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &omap_abe_dai_ops, }, { /* Multimedia Capture */ .name = "MultiMedia2", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .capture = { .stream_name = "MultiMedia2 Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, { /* Voice Playback and Capture */ .name = "Voice", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "Voice Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, .formats = OMAP_ABE_FORMATS, }, .capture = { .stream_name = "Voice Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, { /* Tones Playback */ .name = "Tones", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "Tones Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, { /* Vibra */ .name = "Vibra", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "Vibra Playback", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_CONTINUOUS, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, { /* MODEM Voice Playback and Capture */ .name = "MODEM", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "MODEM Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats = OMAP_ABE_FORMATS, }, .capture = { .stream_name = "MODEM Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, { /* Low Power HiFi Playback */ .name = "MultiMedia1 LP", .suspend = omap_abe_dai_suspend, .resume = omap_abe_dai_resume, .playback = { .stream_name = "MultiMedia1 LP Playback", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = OMAP_ABE_FORMATS, }, .ops = &omap_abe_dai_ops, }, }; static int __devinit omap_abe_probe(struct platform_device *pdev) { return snd_soc_register_dais(&pdev->dev, omap_abe_dai, ARRAY_SIZE(omap_abe_dai)); } static int __devexit omap_abe_remove(struct platform_device *pdev) { snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(omap_abe_dai)); return 0; } static struct platform_driver omap_abe_driver = { .driver = { .name = "omap-abe-dai", .owner = THIS_MODULE, }, .probe = omap_abe_probe, .remove = __devexit_p(omap_abe_remove), }; static int __init omap_abe_init(void) { return platform_driver_register(&omap_abe_driver); } module_init(omap_abe_init); static void __exit omap_abe_exit(void) { platform_driver_unregister(&omap_abe_driver); } module_exit(omap_abe_exit); MODULE_AUTHOR("Liam Girdwood "); MODULE_DESCRIPTION("OMAP ABE SoC Interface"); MODULE_LICENSE("GPL");