| /* |
| * omap-abe.c -- OMAP ALSA SoC DAI driver using Audio Backend |
| * |
| * Copyright (C) 2010 Texas Instruments |
| * |
| * Contact: Liam Girdwood <lrg@ti.com> |
| * Misael Lopez Cruz <misael.lopez@ti.com> |
| * Sebastien Guiriec <s-guiriec@ti.com> |
| * |
| * 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 |
| * |
| */ |
| |
| #define DEBUG |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/initval.h> |
| #include <sound/soc.h> |
| #include <sound/soc-dapm.h> |
| #include <sound/soc-dsp.h> |
| |
| #include <plat/dma-44xx.h> |
| #include <plat/dma.h> |
| #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; |
| 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) |
| { |
| /* DL1 path is common for PDM_DL1, BT_VX_DL and MM_EXT_DL */ |
| return 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]); |
| } |
| |
| static int omap_abe_dl2_enabled(struct omap_abe_data *abe_priv) |
| { |
| return omap_abe_port_is_enabled(abe_priv->abe, |
| abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2]); |
| } |
| |
| 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: |
| abe_mute_gain(GAINS_DL2, GAIN_LEFT_OFFSET); |
| abe_mute_gain(GAINS_DL2, GAIN_RIGHT_OFFSET); |
| 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: |
| abe_unmute_gain(GAINS_DL2, GAIN_LEFT_OFFSET); |
| abe_unmute_gain(GAINS_DL2, GAIN_RIGHT_OFFSET); |
| 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 2 ports */ |
| format.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(BT_VX_DL_PORT, &format, MCBSP2_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 2 ports */ |
| format.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(BT_VX_UL_PORT, &format, MCBSP2_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 1 ports */ |
| format.f = 48000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(MM_EXT_OUT_PORT, &format, MCBSP1_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 1 ports */ |
| format.f = 48000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(MM_EXT_IN_PORT, &format, MCBSP1_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, |
| struct snd_soc_pcm_runtime *be, int mute) |
| { |
| struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(fe->cpu_dai); |
| |
| dev_dbg(&fe->dev, "%s: %s FE %s BE %s\n", |
| __func__, mute ? "mute" : "unmute", |
| fe->dai_link->name, be->dai_link->name); |
| |
| switch (fe->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| if (omap_abe_dl1_enabled(abe_priv)) { |
| if (mute) |
| abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); |
| else |
| abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_UL2); |
| } |
| if (omap_abe_dl2_enabled(abe_priv)) { |
| if (mute) |
| abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_UL2); |
| else |
| 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, |
| struct snd_soc_pcm_runtime *be, int mute) |
| { |
| struct omap_abe_data *abe_priv = snd_soc_dai_get_drvdata(fe->cpu_dai); |
| |
| dev_dbg(&fe->dev, "%s: %s FE %s BE %s\n", |
| __func__, mute ? "mute" : "unmute", |
| fe->dai_link->name, be->dai_link->name); |
| |
| switch (fe->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| switch (be->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| if (mute) { |
| /* mute if last running DL1-related BE */ |
| if (omap_abe_dl1_enabled(abe_priv) == 1) |
| abe_mute_gain(MIXDL1, |
| MIX_DL1_INPUT_MM_DL); |
| } else { |
| abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); |
| } |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| if (mute) |
| abe_mute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); |
| else |
| abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_MM_DL); |
| break; |
| case OMAP_ABE_DAI_MODEM: |
| case OMAP_ABE_DAI_PDM_VIB: |
| default: |
| break; |
| } |
| break; |
| case ABE_FRONTEND_DAI_VOICE: |
| case ABE_FRONTEND_DAI_MODEM: |
| switch (be->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| if (mute) { |
| /* mute if last running DL1-related BE */ |
| if (omap_abe_dl1_enabled(abe_priv) == 1) |
| abe_mute_gain(MIXDL1, |
| MIX_DL1_INPUT_VX_DL); |
| } else { |
| abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_VX_DL); |
| } |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| if (mute) |
| abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); |
| else |
| abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); |
| break; |
| case OMAP_ABE_DAI_MODEM: |
| case OMAP_ABE_DAI_PDM_VIB: |
| default: |
| break; |
| } |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| switch (be->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| if (mute) { |
| /* mute if last running DL1-related BE */ |
| if (omap_abe_dl1_enabled(abe_priv) == 1) |
| abe_mute_gain(MIXDL1, |
| MIX_DL1_INPUT_TONES); |
| } else{ |
| abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_TONES); |
| } |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| if (mute) |
| abe_mute_gain(MIXDL2, MIX_DL2_INPUT_TONES); |
| else |
| abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_TONES); |
| break; |
| case OMAP_ABE_DAI_MODEM: |
| case OMAP_ABE_DAI_PDM_VIB: |
| default: |
| break; |
| } |
| 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; |
| struct snd_soc_dsp_params *dsp_params; |
| |
| dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); |
| |
| list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { |
| struct snd_soc_pcm_runtime *be = dsp_params->be; |
| |
| if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) |
| continue; |
| |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| mute_fe_port_playback(fe, be, 1); |
| else |
| mute_fe_port_capture(fe, be, 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; |
| struct snd_soc_dsp_params *dsp_params; |
| |
| dev_dbg(&fe->dev, "%s: %s %d\n", __func__, dai->name, stream); |
| |
| list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { |
| struct snd_soc_pcm_runtime *be = dsp_params->be; |
| |
| if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) |
| continue; |
| |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| mute_fe_port_playback(fe, be, 0); |
| else |
| mute_fe_port_capture(fe, be, 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: |
| |
| /* mute FE port (sensitive to runtime udpates) */ |
| mute_fe_port(substream, dai, stream); |
| |
| /* does this trigger() apply to the FE ? */ |
| if (snd_soc_dsp_is_trigger_for_fe(fe, 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_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; |
| } |
| } |
| |
| 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_RSHIFTED_16; |
| dma_data->data_type = OMAP_DMA_DATA_TYPE_S16; |
| } 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_widget mm1_widgets[] = { |
| {"MM_DL", SND_SOC_DAI_CHAN_STEREO}, |
| {"MM_UL1", SND_SOC_DAI_CHAN_MASK(8)}, |
| }; |
| |
| static struct snd_soc_dai_widget mm2_widgets[] = { |
| {"MM_UL2", SND_SOC_DAI_CHAN_STEREO}, |
| }; |
| |
| static struct snd_soc_dai_widget voice_widgets[] = { |
| {"VX_DL", SND_SOC_DAI_CHAN_STEREO}, |
| {"VX_UL", SND_SOC_DAI_CHAN_STEREO}, |
| }; |
| |
| static struct snd_soc_dai_widget tones_widgets[] = { |
| {"TONES_DL", SND_SOC_DAI_CHAN_STEREO}, |
| }; |
| |
| static struct snd_soc_dai_widget vibra_widgets[] = { |
| {"VIB_DL", SND_SOC_DAI_CHAN_STEREO}, |
| }; |
| |
| static struct snd_soc_dai_widget mm_lp_widgets[] = { |
| {"MM_DL_LP", SND_SOC_DAI_CHAN_STEREO}, |
| }; |
| |
| 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 = 8, |
| .rates = SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .ops = &omap_abe_dai_ops, |
| .widgets = mm1_widgets, |
| .num_widgets = ARRAY_SIZE(mm1_widgets), |
| }, |
| { /* 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, |
| .widgets = mm2_widgets, |
| .num_widgets = ARRAY_SIZE(mm2_widgets), |
| }, |
| { /* 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, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .capture = { |
| .stream_name = "Voice 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, |
| .widgets = voice_widgets, |
| .num_widgets = ARRAY_SIZE(voice_widgets), |
| }, |
| { /* 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, |
| .widgets = tones_widgets, |
| .num_widgets = ARRAY_SIZE(tones_widgets), |
| }, |
| { /* 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, |
| .widgets = vibra_widgets, |
| .num_widgets = ARRAY_SIZE(vibra_widgets), |
| }, |
| { /* 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, |
| .widgets = voice_widgets, |
| .num_widgets = ARRAY_SIZE(voice_widgets), |
| }, |
| { /* 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, |
| .widgets = mm_lp_widgets, |
| .num_widgets = ARRAY_SIZE(mm_lp_widgets), |
| }, |
| }; |
| |
| 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 <lrg@ti.com>"); |
| MODULE_DESCRIPTION("OMAP ABE SoC Interface"); |
| MODULE_LICENSE("GPL"); |