| /* |
| * omap-abe.c -- OMAP ALSA SoC DAI driver using Audio Backend |
| * |
| * Copyright (C) 2010 Texas Instruments |
| * |
| * Contact: Liam Girdwood <lrg@slimlogic.co.uk> |
| * |
| * 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 |
| * |
| */ |
| |
| #undef DEBUG |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/workqueue.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 <plat/control.h> |
| #include <plat/dma-44xx.h> |
| #include <plat/dma.h> |
| #include "omap-mcpdm.h" |
| #include "omap-pcm.h" |
| #include "omap-abe.h" |
| #include "omap-abe-dsp.h" |
| #include "abe/abe_main.h" |
| |
| |
| #define OMAP_ABE_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) |
| |
| /* |
| * This is the AESS frontend driver. i.e. the part that Tx and Rx PCM |
| * data via the OMAP sDMA and ALSA userspace PCMs. |
| * |
| * TODO: |
| * |
| * 1) Get DAI params from HAL and pass onto DAI drivers ? (are params static ??, i.e. can I just use a table) |
| */ |
| |
| #define NUM_ABE_FRONTENDS 7 |
| #define NUM_ABE_BACKENDS 11 |
| |
| #if 0 |
| /* Frontend DAI Playback / Capture Support Matrix */ |
| static const struct { |
| int dir[2]; |
| } fe_stream[] = { |
| {{1, 1,},}, /* playback and capture supported on PCM0 - Multimedia*/ |
| {{0, 1,},}, /* capture only supported on PCM1 - Multimedia capture */ |
| {{1, 1,},}, /* playback and capture supported on PCM2 - Voice */ |
| {{1, 0,},}, /* playback only supported on PCM3 - Tones */ |
| {{1, 0,},}, /* playback only supported on PCM4 - Vibra */ |
| }; |
| |
| /* Backend DAI Playback / Capture Support Matrix */ |
| static const struct { |
| const char *id; |
| int dir[2]; |
| } be_stream[] = { |
| {OMAP_ABE_BE_PDM_UL1, {0, 1,},}, /* capture only supported on PDM UL*/ |
| {OMAP_ABE_BE_PDM_DL1, {1, 0,},}, /* playback only supported on PDM DL1 */ |
| {OMAP_ABE_BE_PDM_DL2, {1, 0,},}, /* playback only supported on PDM DL2 */ |
| {OMAP_ABE_BE_PDM_VIB, {1, 0,},}, /* playback only supported on PDM VIB */ |
| {OMAP_ABE_BE_BT_VX, {1, 0,},}, /* playback supported on BT VX_DL */ |
| {OMAP_ABE_BE_BT_VX, {0, 1,},}, /* playback and capture supported on BT VX_UL */ |
| {OMAP_ABE_BE_MM_EXT0, {1, 0,},}, /* playback supported on MM EXT_DL */ |
| {OMAP_ABE_BE_MM_EXT0, {0, 1,},}, /* capture supported on MM EXT_UL */ |
| {OMAP_ABE_BE_DMIC0, {0, 1,},}, /* capture only supported on DMIC0 */ |
| {OMAP_ABE_BE_DMIC1, {0, 1,},}, /* capture only supported on DMIC1 */ |
| {OMAP_ABE_BE_DMIC2, {0, 1,},}, /* capture only supported on DMIC2 */ |
| }; |
| #endif |
| |
| /* |
| * Playback volumes for FE PCMs the need to be muted to stop pops. |
| */ |
| struct abe_playback_controls { |
| int dl1_vol; |
| int dl2_vol; |
| }; |
| |
| /* |
| * Capture volumes for FE PCMs the need to be muted to stop pops. |
| */ |
| struct abe_capture_controls { |
| int dmic1_vol; |
| int dmic2_vol; |
| int dmic3_vol; |
| int amic_vol; |
| }; |
| |
| /* logical -> physical DAI status */ |
| enum dai_status { |
| DAI_STOPPED = 0, |
| DAI_STARTED, |
| }; |
| |
| struct abe_frontend_dai { |
| |
| /* trigger work */ |
| struct work_struct work; |
| struct completion trigger_done; |
| int cmd; |
| struct snd_pcm_substream *substream; |
| union { |
| struct abe_playback_controls playback; |
| struct abe_capture_controls capture; |
| }; |
| }; |
| |
| struct omap_abe_data { |
| struct abe_frontend_dai frontend[NUM_ABE_FRONTENDS][2]; |
| int be_active[NUM_ABE_BACKENDS][2]; |
| |
| struct clk *clk; |
| struct workqueue_struct *workqueue; |
| |
| /* hwmod platform device */ |
| struct platform_device *pdev; |
| |
| /* MODEM FE*/ |
| struct snd_pcm_substream *modem_substream[2]; |
| struct snd_soc_dai *modem_dai; |
| |
| /* reference counting and port status - HAL should really do this */ |
| enum dai_status dmic_status[3]; |
| enum dai_status pdm_dl_status[3]; |
| enum dai_status pdm_ul_status; |
| |
| }; |
| |
| static struct omap_abe_data abe_data; |
| |
| /* frontend mutex */ |
| static DEFINE_MUTEX(fe_mutex); |
| |
| /* |
| * Stream DMA parameters |
| * FIXME: make this array[5][2] and put in priv data, otherwise we clobber it |
| * upon opening new FEs. |
| */ |
| static struct omap_pcm_dma_data omap_abe_dai_dma_params[] = { |
| { |
| .name = "Audio Playback", |
| .dma_req = OMAP44XX_DMA_ABE_REQ_0, |
| .data_type = OMAP_DMA_DATA_TYPE_S32, |
| .sync_mode = OMAP_DMA_SYNC_PACKET, |
| }, |
| { |
| .name = "Audio Capture", |
| .dma_req = OMAP44XX_DMA_ABE_REQ_2, |
| .data_type = OMAP_DMA_DATA_TYPE_S32, |
| .sync_mode = OMAP_DMA_SYNC_PACKET, |
| }, |
| }; |
| |
| /* |
| * Caller holds lock for be_active calls. |
| */ |
| static inline int be_get_active(struct snd_soc_pcm_runtime *be_rtd, int stream) |
| { |
| return abe_data.be_active[be_rtd->dai_link->be_id][stream]; |
| } |
| |
| static inline void be_inc_active(struct snd_soc_pcm_runtime *be_rtd, int stream) |
| { |
| abe_data.be_active[be_rtd->dai_link->be_id][stream]++; |
| } |
| |
| static inline void be_dec_active(struct snd_soc_pcm_runtime *be_rtd, int stream) |
| { |
| abe_data.be_active[be_rtd->dai_link->be_id][stream]--; |
| } |
| |
| /* iff the BE has one user can we start and stop the port */ |
| static inline int be_is_pending(struct snd_soc_pcm_runtime *be_rtd, int stream) |
| { |
| return abe_data.be_active[be_rtd->dai_link->be_id][stream] == 1 ? 1 : 0; |
| } |
| |
| /* |
| * consolidate our 3 PDM and DMICs. |
| */ |
| static inline int pdm_ready(struct omap_abe_data *abe) |
| { |
| int i; |
| |
| /* check the 3 DL DAIs */ |
| for (i = 0; i < 3; i++) { |
| if (abe->pdm_dl_status[i] == DAI_STARTED) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static inline int dmic_ready(struct omap_abe_data *abe) |
| { |
| int i; |
| |
| /* check the 3 DMIC DAIs */ |
| for (i = 0; i < 3; i++) { |
| if (abe->dmic_status[i] == DAI_STARTED) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int modem_get_dai(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_pcm_runtime *modem_rtd; |
| |
| abe_data.modem_substream[substream->stream] = |
| snd_soc_get_dai_substream(rtd->card, |
| OMAP_ABE_BE_MM_EXT1, substream->stream); |
| if (abe_data.modem_substream[substream->stream] == NULL) |
| return -ENODEV; |
| |
| modem_rtd = abe_data.modem_substream[substream->stream]->private_data; |
| abe_data.modem_dai = modem_rtd->cpu_dai; |
| return 0; |
| } |
| |
| /* Frontend PCM Operations */ |
| |
| static int abe_fe_startup(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| int ret = 0; |
| |
| /* TODO: complete HW pcm for backends */ |
| #if 0 |
| snd_pcm_hw_constraint_list(substream->runtime, 0, |
| SNDRV_PCM_HW_PARAM_RATE, |
| priv->sysclk_constraints); |
| #endif |
| |
| if (dai->id == ABE_FRONTEND_DAI_MODEM) { |
| |
| ret = modem_get_dai(substream); |
| if (ret < 0) { |
| dev_err(dai->dev, "failed to get MODEM DAI\n"); |
| return ret; |
| } |
| |
| ret = snd_soc_dai_startup(abe_data.modem_substream[substream->stream], |
| abe_data.modem_dai); |
| if (ret < 0) { |
| dev_err(abe_data.modem_dai->dev, "failed to open DAI %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int abe_fe_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| abe_data_format_t format; |
| abe_dma_t dma_sink; |
| abe_dma_t dma_params; |
| int dma_req, ret; |
| |
| switch (params_channels(params)) { |
| case 1: |
| format.samp_format = MONO_MSB; |
| break; |
| case 2: |
| format.samp_format = STEREO_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: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| dma_req = OMAP44XX_DMA_ABE_REQ_0; |
| abe_connect_cbpr_dmareq_port(MM_DL_PORT, &format, ABE_CBPR0_IDX, |
| &dma_sink); |
| abe_read_port_address(MM_DL_PORT, &dma_params); |
| } else { |
| dma_req = OMAP44XX_DMA_ABE_REQ_3; |
| 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_MEDIA_CAPTURE: |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return -EINVAL; |
| else { |
| dma_req = OMAP44XX_DMA_ABE_REQ_4; |
| 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) { |
| dma_req = OMAP44XX_DMA_ABE_REQ_1; |
| abe_connect_cbpr_dmareq_port(VX_DL_PORT, &format, ABE_CBPR1_IDX, |
| &dma_sink); |
| abe_read_port_address(VX_DL_PORT, &dma_params); |
| } else { |
| dma_req = OMAP44XX_DMA_ABE_REQ_2; |
| 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) { |
| dma_req = OMAP44XX_DMA_ABE_REQ_5; |
| 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) { |
| dma_req = OMAP44XX_DMA_ABE_REQ_6; |
| 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.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| dma_req = OMAP44XX_DMA_ABE_REQ_1; |
| 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.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| dma_req = OMAP44XX_DMA_ABE_REQ_2; |
| abe_connect_serial_port(VX_UL_PORT, &format, MCBSP2_TX); |
| abe_read_port_address(VX_UL_PORT, &dma_params); |
| } |
| break; |
| } |
| |
| /* configure frontend SDMA data */ |
| omap_abe_dai_dma_params[substream->stream].dma_req = dma_req; |
| omap_abe_dai_dma_params[substream->stream].port_addr = |
| (unsigned long)dma_params.data; |
| omap_abe_dai_dma_params[substream->stream].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_data.modem_dai, substream, |
| &omap_abe_dai_dma_params[substream->stream]); |
| |
| ret = snd_soc_dai_hw_params(abe_data.modem_substream[substream->stream], |
| params, abe_data.modem_dai); |
| if (ret < 0) |
| dev_err(abe_data.modem_dai->dev, "MODEM hw_params failed\n"); |
| return ret; |
| } |
| |
| snd_soc_dai_set_dma_data(dai, substream, |
| &omap_abe_dai_dma_params[substream->stream]); |
| |
| return 0; |
| } |
| |
| static int abe_fe_prepare(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| int ret = 0; |
| |
| if (dai->id == ABE_FRONTEND_DAI_MODEM) { |
| ret = snd_soc_dai_prepare(abe_data.modem_substream[substream->stream], |
| abe_data.modem_dai); |
| if (ret < 0) { |
| dev_err(abe_data.modem_dai->dev, "MODEM prepare failed\n"); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| static int abe_fe_trigger(struct snd_pcm_substream *substream, |
| int cmd, struct snd_soc_dai *dai) |
| { |
| int ret = 0; |
| //TODO: how do we sequence this with ABE ??? |
| if (dai->id == ABE_FRONTEND_DAI_MODEM) { |
| ret = snd_soc_dai_trigger(abe_data.modem_substream[substream->stream], |
| cmd, abe_data.modem_dai); |
| if (ret < 0) { |
| dev_err(abe_data.modem_dai->dev, "MODEM trigger failed\n"); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| static int abe_fe_hw_free(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| int ret = 0; |
| |
| if (dai->id == ABE_FRONTEND_DAI_MODEM) { |
| ret = snd_soc_dai_hw_free(abe_data.modem_substream[substream->stream], |
| abe_data.modem_dai); |
| if (ret < 0) { |
| dev_err(abe_data.modem_dai->dev, "MODEM hw_free failed\n"); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| static void abe_fe_shutdown(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| |
| if (dai->id == ABE_FRONTEND_DAI_MODEM) |
| snd_soc_dai_shutdown(abe_data.modem_substream[substream->stream], |
| abe_data.modem_dai); |
| |
| //TODO: Do we need to do reset this stuff i.e. :- |
| //abe_connect_cbpr_dmareq_port(VIB_DL_PORT, &format, ABE_CBPR6_IDX, |
| // &dma_sink); |
| } |
| |
| static void abe_be_dapm(struct snd_soc_pcm_runtime *rtd, |
| int id, int stream, int cmd) |
| { |
| dev_dbg(&rtd->dev, "%s: id %d stream %d cmd %d\n", |
| __func__, id, stream, cmd); |
| |
| switch (id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dapm_stream_event(rtd, "Multimedia Playback",cmd); |
| else |
| snd_soc_dapm_stream_event(rtd, "Multimedia Capture2",cmd); |
| break; |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| snd_soc_dapm_stream_event(rtd, "Multimedia Capture1",cmd); |
| break; |
| case ABE_FRONTEND_DAI_VOICE: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dapm_stream_event(rtd, "Voice Playback",cmd); |
| else |
| snd_soc_dapm_stream_event(rtd, "Voice Capture",cmd); |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dapm_stream_event(rtd, "Tones Playback",cmd); |
| break; |
| case ABE_FRONTEND_DAI_VIBRA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dapm_stream_event(rtd, "Vibra Playback",cmd); |
| break; |
| case ABE_FRONTEND_DAI_MODEM: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dapm_stream_event(rtd, "MODEM Playback",cmd); |
| else |
| snd_soc_dapm_stream_event(rtd, "MODEM Capture",cmd); |
| break; |
| } |
| } |
| |
| /* Frontend --> Backend ALSA PCM OPS */ |
| |
| static int omap_abe_dai_startup(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| int ret = 0, i; |
| |
| dev_dbg(dai->dev, "%s: frontend %s %d\n", __func__, |
| rtd->dai_link->name, dai->id); |
| |
| /* only startup backends that are either sinks or sources to this frontend DAI */ |
| for (i = 0; i < rtd->num_be; i++) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| /* update BE ref counts */ |
| be_inc_active(rtd->be_rtd[i], substream->stream); |
| |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) { |
| ret = snd_soc_pcm_open(be_substream); |
| if (ret < 0) |
| goto unwind; |
| dev_dbg(&rtd->dev, "%s: open %s:%d at %d act %d\n", __func__, |
| rtd->be_rtd[i]->dai_link->name, rtd->be_rtd[i]->dai_link->be_id, i, |
| abe_data.be_active[rtd->be_rtd[i]->dai_link->be_id][substream->stream]); |
| } |
| } |
| |
| /* start the ABE frontend */ |
| ret = abe_fe_startup(substream, dai); |
| if (ret < 0) { |
| dev_err(dai->dev,"%s: failed to start FE %d\n", __func__, ret); |
| goto unwind; |
| } |
| |
| dev_dbg(dai->dev,"%s: frontend finished %s %d\n", __func__, |
| rtd->dai_link->name, dai->id); |
| return 0; |
| |
| unwind: |
| /* disable any enabled and non active backends */ |
| for (--i; i >= 0; i--) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) { |
| |
| snd_soc_pcm_close(be_substream); |
| |
| dev_dbg(&rtd->dev, "%s: open-err-close %s:%d at %d act %d\n", __func__, |
| rtd->be_rtd[i]->dai_link->name, rtd->be_rtd[i]->dai_link->be_id, i, |
| abe_data.be_active[rtd->be_rtd[i]->dai_link->be_id][substream->stream]); |
| } |
| |
| be_dec_active(rtd->be_rtd[i], substream->stream); |
| } |
| |
| return ret; |
| } |
| |
| static void omap_abe_dai_shutdown(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| int i; |
| |
| dev_dbg(dai->dev,"%s: frontend %s \n", __func__, rtd->dai_link->name); |
| /* only shutdown backends that are either sinks or sources to this frontend DAI */ |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| /* close backend stream if inactive */ |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) { |
| snd_soc_pcm_close(rtd->be_rtd[i]->pcm->streams[substream->stream].substream); |
| |
| dev_dbg(&rtd->dev,"%s: close %s:%d at %d act %d\n", __func__, |
| rtd->be_rtd[i]->dai_link->name, rtd->be_rtd[i]->dai_link->be_id, i, |
| abe_data.be_active[rtd->be_rtd[i]->dai_link->be_id][substream->stream]); |
| } |
| |
| be_dec_active(rtd->be_rtd[i], substream->stream); |
| } |
| |
| /* now shutdown the frontend */ |
| abe_fe_shutdown(substream, dai); |
| |
| abe_be_dapm(rtd, dai->id, substream->stream, SND_SOC_DAPM_STREAM_STOP); |
| dev_dbg(dai->dev,"%s: frontend %s completed !\n", __func__, rtd->dai_link->name); |
| } |
| |
| static int omap_abe_dai_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| int i, ret; |
| |
| dev_dbg(dai->dev,"%s: frontend %s \n", __func__, rtd->dai_link->name); |
| |
| // TODO: generate our own hw params for backend DAI |
| /* only hw_params backends that are either sinks or |
| * sources to this frontend DAI */ |
| for (i = 0; i < rtd->num_be; i++) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) { |
| ret = snd_soc_pcm_hw_params( |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream, |
| params); |
| if (ret < 0) { |
| dev_err(&rtd->dev, "%s: be hw_params failed %d\n", __func__, ret); |
| return ret; |
| } |
| } |
| } |
| |
| /* call hw_params on the frontend */ |
| ret = abe_fe_hw_params(substream, params, dai); |
| if (ret < 0) |
| dev_err(dai->dev,"%s: frontend hw_params failed\n", __func__); |
| |
| return ret; |
| } |
| |
| static int omap_abe_dai_trigger(struct snd_pcm_substream *substream, |
| int cmd, struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct abe_frontend_dai *fe = &abe_data.frontend[dai->id][substream->stream]; |
| int ret; |
| |
| dev_dbg(dai->dev,"%s: frontend %s \n", __func__, rtd->dai_link->name); |
| |
| /* call prepare on the frontend. TODO: do we handle MODEM as sequence */ |
| ret = abe_fe_trigger(substream, cmd, dai); |
| if (ret < 0) { |
| dev_err(dai->dev,"%s: frontend trigger failed\n", __func__); |
| return ret; |
| } |
| |
| fe->cmd = cmd; |
| fe->substream = substream; |
| |
| /* perform the backend trigger work */ |
| queue_work(abe_data.workqueue, &fe->work); |
| |
| return 0; |
| } |
| |
| static int omap_abe_dai_prepare(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| int ret = 0, i; |
| |
| dev_dbg(dai->dev,"%s: frontend %s \n", __func__, rtd->dai_link->name); |
| /* only prepare backends that are either sinks or sources to |
| * this frontend DAI */ |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| /* prepare backend stream */ |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) |
| snd_soc_pcm_prepare(rtd->be_rtd[i]->pcm->streams[substream->stream].substream); |
| } |
| |
| /* call prepare on the frontend */ |
| ret = abe_fe_prepare(substream, dai); |
| if (ret < 0) |
| dev_err(dai->dev,"%s: frontend prepare failed\n", __func__); |
| |
| abe_be_dapm(rtd, dai->id, substream->stream, SND_SOC_DAPM_STREAM_START); |
| |
| return ret; |
| } |
| |
| static int omap_abe_dai_hw_free(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct abe_frontend_dai *fe = &abe_data.frontend[dai->id][substream->stream]; |
| int ret = 0, i; |
| |
| dev_dbg(dai->dev,"%s: frontend %s \n", __func__, rtd->dai_link->name); |
| |
| /* wait on trigger completing */ |
| wait_for_completion_timeout(&fe->trigger_done, msecs_to_jiffies(500)); |
| |
| /* call hw_free on the frontend */ |
| ret = abe_fe_hw_free(substream, dai); |
| if (ret < 0) |
| dev_err(dai->dev,"%s: frontend hw_free failed\n", __func__); |
| |
| /* only hw_params backends that are either sinks or sources |
| * to this frontend DAI */ |
| for (i = 0; i < rtd->num_be; i++) { |
| struct snd_pcm_substream *be_substream = |
| rtd->be_rtd[i]->pcm->streams[substream->stream].substream; |
| |
| if (be_substream == NULL) |
| continue; |
| |
| if (be_is_pending(rtd->be_rtd[i], substream->stream)) |
| snd_soc_pcm_hw_free(rtd->be_rtd[i]->pcm->streams[substream->stream].substream); |
| } |
| |
| return ret; |
| } |
| |
| /* ABE Frontend PCM OPS */ |
| 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, |
| .prepare = omap_abe_dai_prepare, |
| .hw_free = omap_abe_dai_hw_free, |
| .trigger = omap_abe_dai_trigger, |
| }; |
| |
| // TODO: finish this |
| static void mute_be_capture(struct snd_soc_pcm_runtime *rtd) |
| { |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d\n", __func__, be_rtd->dai_link->be_id); |
| |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_UL: |
| // abe_write_mixer(MIXAUDUL, MUTE_GAIN, RAMP_0MS, |
| // MIX_AUDUL_INPUT_UPLINK); |
| break; |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| case OMAP_ABE_DAI_MODEM: |
| case OMAP_ABE_DAI_DMIC0: |
| case OMAP_ABE_DAI_DMIC1: |
| case OMAP_ABE_DAI_DMIC2: |
| break; |
| } |
| } |
| } |
| |
| // TODO: finish this and restore correct volume levels |
| static void unmute_be_capture(struct snd_soc_pcm_runtime *rtd) |
| { |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d\n", __func__, be_rtd->dai_link->be_id); |
| |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_UL: |
| // abe_write_mixer(MIXAUDUL, GAIN_M6dB, RAMP_0MS, |
| // MIX_AUDUL_INPUT_UPLINK); |
| break; |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| case OMAP_ABE_DAI_MODEM: |
| case OMAP_ABE_DAI_DMIC0: |
| case OMAP_ABE_DAI_DMIC1: |
| case OMAP_ABE_DAI_DMIC2: |
| break; |
| } |
| } |
| } |
| |
| static void mute_fe_playback(struct abe_frontend_dai *fe, |
| struct snd_soc_pcm_runtime *be_rtd, int mixer, int *volume) |
| { |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| |
| switch (rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_read_mixer(mixer, volume, MIX_DL1_INPUT_MM_DL); |
| abe_write_mixer(mixer, MUTE_GAIN, RAMP_0MS, |
| MIX_DL1_INPUT_MM_DL); |
| } |
| break; |
| case ABE_FRONTEND_DAI_MODEM: |
| case ABE_FRONTEND_DAI_VOICE: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_read_mixer(mixer, volume, MIX_DL1_INPUT_VX_DL); |
| abe_write_mixer(mixer, MUTE_GAIN, RAMP_0MS, |
| MIX_DL1_INPUT_VX_DL); |
| } |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_read_mixer(mixer, volume, MIX_DL1_INPUT_TONES); |
| abe_write_mixer(mixer, MUTE_GAIN, RAMP_0MS, |
| MIX_DL1_INPUT_TONES); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void unmute_fe_playback(struct abe_frontend_dai *fe, |
| struct snd_soc_pcm_runtime *be_rtd, int mixer, int volume) |
| { |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| |
| switch (rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_write_mixer(mixer, volume, RAMP_5MS, |
| MIX_DL1_INPUT_MM_DL); |
| } |
| break; |
| case ABE_FRONTEND_DAI_MODEM: |
| case ABE_FRONTEND_DAI_VOICE: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_write_mixer(mixer, volume, RAMP_5MS, |
| MIX_DL1_INPUT_VX_DL); |
| } |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| if (be_is_pending(be_rtd, SNDRV_PCM_STREAM_PLAYBACK)) { |
| abe_write_mixer(mixer, volume, RAMP_5MS, |
| MIX_DL1_INPUT_TONES); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void mute_be_playback(struct abe_frontend_dai *fe) |
| { |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d\n", __func__, be_rtd->dai_link->be_id); |
| |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| mute_fe_playback(fe, be_rtd, MIXDL1, &fe->playback.dl1_vol); |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| mute_fe_playback(fe, be_rtd, MIXDL2, &fe->playback.dl2_vol); |
| break; |
| case OMAP_ABE_DAI_PDM_VIB: |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| case OMAP_ABE_DAI_MODEM: |
| break; |
| } |
| } |
| } |
| |
| static void unmute_be_playback(struct abe_frontend_dai *fe) |
| { |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d\n", __func__, be_rtd->dai_link->be_id); |
| |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| unmute_fe_playback(fe, be_rtd, MIXDL1, fe->playback.dl1_vol); |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| unmute_fe_playback(fe, be_rtd, MIXDL2, fe->playback.dl2_vol); |
| break; |
| case OMAP_ABE_DAI_PDM_VIB: |
| case OMAP_ABE_DAI_BT_VX: |
| case OMAP_ABE_DAI_MM_FM: |
| case OMAP_ABE_DAI_MODEM: |
| break; |
| } |
| } |
| } |
| |
| static inline void abe_dai_enable_data_transfer(int port) |
| { |
| pr_debug("%s : port %d\n", __func__, port); |
| abe_enable_data_transfer(port); |
| } |
| |
| static void enable_be_ports(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| struct snd_soc_pcm_runtime *be_rtd; |
| abe_data_format_t format; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d stream %d active %d\n", |
| __func__, be_rtd->dai_link->be_id, stream, |
| be_get_active(be_rtd, stream)); |
| |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) |
| abe_dai_enable_data_transfer(PDM_DL_PORT); |
| abe_data.pdm_dl_status[0] = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) |
| abe_dai_enable_data_transfer(PDM_DL_PORT); |
| abe_data.pdm_dl_status[1] = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_PDM_VIB: |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) |
| abe_dai_enable_data_transfer(PDM_DL_PORT); |
| abe_data.pdm_dl_status[2] = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_PDM_UL: |
| if (be_is_pending(be_rtd, stream)) { |
| /* switch main port to UL since DL is inactive */ |
| if (pdm_ready(&abe_data)) |
| abe_select_main_port(PDM_UL_PORT); |
| abe_dai_enable_data_transfer(PDM_UL_PORT); |
| } |
| abe_data.pdm_ul_status = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_BT_VX: |
| if (be_is_pending(be_rtd, stream)) { |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| /* 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_RX); |
| abe_dai_enable_data_transfer(BT_VX_DL_PORT); |
| } else { |
| /* 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_TX); |
| abe_dai_enable_data_transfer(BT_VX_UL_PORT); |
| } |
| } |
| break; |
| case OMAP_ABE_DAI_MM_FM: |
| if (be_is_pending(be_rtd, stream)) { |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| /* MM_EXT connection to McBSP 2 ports */ |
| format.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(MM_EXT_OUT_PORT, |
| &format, MCBSP2_RX); |
| abe_dai_enable_data_transfer(MM_EXT_OUT_PORT); |
| } else { |
| /* MM_EXT connection to McBSP 2 ports */ |
| format.f = 8000; |
| format.samp_format = STEREO_RSHIFTED_16; |
| abe_connect_serial_port(MM_EXT_IN_PORT, |
| &format, MCBSP2_TX); |
| abe_dai_enable_data_transfer(MM_EXT_IN_PORT); |
| } |
| } |
| break; |
| case OMAP_ABE_DAI_DMIC0: |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_enable_data_transfer(DMIC_PORT); |
| abe_data.dmic_status[0] = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_DMIC1: |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_enable_data_transfer(DMIC_PORT1); |
| abe_data.dmic_status[1] = DAI_STARTED; |
| break; |
| case OMAP_ABE_DAI_DMIC2: |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_enable_data_transfer(DMIC_PORT2); |
| abe_data.dmic_status[2] = DAI_STARTED; |
| break; |
| } |
| } |
| } |
| |
| static void enable_fe_ports(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| dev_dbg(&rtd->dev, "%s: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id, stream); |
| |
| switch(rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_enable_data_transfer(MM_DL_PORT); |
| else |
| abe_enable_data_transfer(MM_UL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| if (stream == SNDRV_PCM_STREAM_CAPTURE) |
| abe_enable_data_transfer(MM_UL2_PORT); |
| break; |
| case ABE_FRONTEND_DAI_MODEM: |
| case ABE_FRONTEND_DAI_VOICE: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_enable_data_transfer(VX_DL_PORT); |
| else |
| abe_enable_data_transfer(VX_UL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_enable_data_transfer(TONES_DL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_VIBRA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_enable_data_transfer(VIB_DL_PORT); |
| break; |
| } |
| } |
| |
| static inline void abe_dai_disable_data_transfer(int port) |
| { |
| pr_debug("%s : port %d\n", __func__, port); |
| abe_disable_data_transfer(port); |
| } |
| |
| static void disable_be_ports(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| |
| dev_dbg(&rtd->dev, "%s: be ID=%d stream %d active %d\n", |
| __func__, be_rtd->dai_link->be_id, stream, |
| be_get_active(be_rtd, stream)); |
| |
| // TODO: main port active logic will be moved into HAL |
| switch (be_rtd->dai_link->be_id) { |
| case OMAP_ABE_DAI_PDM_DL1: |
| abe_data.pdm_dl_status[0] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) { |
| abe_dai_disable_data_transfer(PDM_DL_PORT); |
| |
| /* switch main port to UL since DL is inactive */ |
| if (abe_data.pdm_ul_status == DAI_STARTED) |
| abe_select_main_port(PDM_UL_PORT); |
| } |
| break; |
| case OMAP_ABE_DAI_PDM_DL2: |
| abe_data.pdm_dl_status[1] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) { |
| abe_dai_disable_data_transfer(PDM_DL2_PORT); |
| |
| /* switch main port to UL since DL is inactive */ |
| if (abe_data.pdm_ul_status == DAI_STARTED) |
| abe_select_main_port(PDM_UL_PORT); |
| } |
| break; |
| case OMAP_ABE_DAI_PDM_VIB: |
| abe_data.pdm_dl_status[2] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && pdm_ready(&abe_data)) |
| abe_dai_disable_data_transfer(PDM_VIB_PORT); |
| break; |
| case OMAP_ABE_DAI_PDM_UL: |
| abe_data.pdm_ul_status = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream)) { |
| abe_dai_disable_data_transfer(PDM_UL_PORT); |
| /* switch main port to DL since UL is inactive */ |
| if (pdm_ready(&abe_data)) |
| abe_select_main_port(PDM_DL_PORT); |
| } |
| break; |
| case OMAP_ABE_DAI_BT_VX: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_dai_disable_data_transfer(BT_VX_DL_PORT); |
| else |
| abe_dai_disable_data_transfer(BT_VX_UL_PORT); |
| break; |
| case OMAP_ABE_DAI_MM_FM: |
| case OMAP_ABE_DAI_MODEM: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_dai_disable_data_transfer(MM_EXT_OUT_PORT); |
| else |
| abe_dai_disable_data_transfer(MM_EXT_IN_PORT); |
| case OMAP_ABE_DAI_DMIC0: |
| abe_data.dmic_status[0] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_disable_data_transfer(DMIC_PORT); |
| break; |
| case OMAP_ABE_DAI_DMIC1: |
| abe_data.dmic_status[1] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_disable_data_transfer(DMIC_PORT1); |
| break; |
| case OMAP_ABE_DAI_DMIC2: |
| abe_data.dmic_status[2] = DAI_STOPPED; |
| if (be_is_pending(be_rtd, stream) && dmic_ready(&abe_data)) |
| abe_dai_disable_data_transfer(DMIC_PORT2); |
| break; |
| } |
| } |
| } |
| |
| static void disable_fe_ports(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| dev_dbg(&rtd->dev, "%s: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id, stream); |
| |
| switch(rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_disable_data_transfer(MM_DL_PORT); |
| else |
| abe_disable_data_transfer(MM_UL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| if (stream == SNDRV_PCM_STREAM_CAPTURE) |
| abe_disable_data_transfer(MM_UL2_PORT); |
| break; |
| case ABE_FRONTEND_DAI_MODEM: |
| case ABE_FRONTEND_DAI_VOICE: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_disable_data_transfer(VX_DL_PORT); |
| else |
| abe_disable_data_transfer(VX_UL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_TONES: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_disable_data_transfer(TONES_DL_PORT); |
| break; |
| case ABE_FRONTEND_DAI_VIBRA: |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) |
| abe_disable_data_transfer(VIB_DL_PORT); |
| break; |
| } |
| } |
| |
| // TODO: finish this |
| static void mute_fe_port(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| dev_dbg(&rtd->dev, "%s: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id, stream); |
| |
| switch(rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| // if (stream == SNDRV_PCM_STREAM_CAPTURE) |
| break; |
| case ABE_FRONTEND_DAI_VOICE: |
| case ABE_FRONTEND_DAI_TONES: |
| case ABE_FRONTEND_DAI_VIBRA: |
| |
| break; |
| } |
| } |
| |
| // TODO: finish this |
| static void unmute_fe_port(struct snd_soc_pcm_runtime *rtd, int stream) |
| { |
| dev_dbg(&rtd->dev, "%s: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id, stream); |
| |
| switch(rtd->cpu_dai->id) { |
| case ABE_FRONTEND_DAI_MEDIA: |
| case ABE_FRONTEND_DAI_LP_MEDIA: |
| case ABE_FRONTEND_DAI_MEDIA_CAPTURE: |
| // if (stream == SNDRV_PCM_STREAM_CAPTURE) |
| break; |
| case ABE_FRONTEND_DAI_VOICE: |
| case ABE_FRONTEND_DAI_TONES: |
| case ABE_FRONTEND_DAI_VIBRA: |
| |
| break; |
| } |
| } |
| |
| static void trigger_be_ports(struct abe_frontend_dai *fe, |
| struct snd_soc_pcm_runtime *rtd) |
| { |
| struct snd_soc_pcm_runtime *be_rtd; |
| int i; |
| |
| for (i = 0; i < rtd->num_be; i++) { |
| be_rtd = rtd->be_rtd[i]; |
| dev_dbg(&rtd->dev, "%s: be ID=%d cmd %d act %d\n", |
| __func__, be_rtd->dai_link->be_id, fe->cmd, |
| abe_data.be_active[be_rtd->dai_link->be_id][fe->substream->stream]); |
| |
| if (be_is_pending(be_rtd, fe->substream->stream)) |
| snd_soc_dai_trigger(fe->substream, fe->cmd, be_rtd->cpu_dai); |
| } |
| } |
| |
| /* work for ABE capture frontend and backend ABE synchronisation */ |
| static void capture_work(struct work_struct *work) |
| { |
| struct abe_frontend_dai *fe = |
| container_of(work, struct abe_frontend_dai, work); |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| |
| dev_dbg(&rtd->dev, "%s-start: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id,fe->substream->stream); |
| |
| switch (fe->cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| /* ALL BE GAINS set to mute */ |
| mute_be_capture(rtd); |
| |
| /* Enable ALL ABE BE ports */ |
| enable_be_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| |
| /* DAI work must be started/stopped at least 250us after ABE */ |
| udelay(250); |
| |
| /* trigger ALL BE ports */ |
| trigger_be_ports(fe, rtd); |
| |
| /* Enable Frontend sDMA */ |
| enable_fe_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| |
| /* Restore ABE GAINS AMIC */ |
| unmute_be_capture(rtd); |
| break; |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| /* Enable sDMA / Enable ABE MM_UL2 */ |
| enable_fe_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| break; |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| /* Disable sDMA / Enable ABE MM_UL2 */ |
| disable_fe_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| /* Disable sDMA */ |
| disable_fe_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| |
| disable_be_ports(rtd, SNDRV_PCM_STREAM_CAPTURE); |
| |
| /* DAI work must be started/stopped at least 250us after ABE */ |
| udelay(250); |
| |
| /* trigger ALL BE ports */ |
| trigger_be_ports(fe, rtd); |
| |
| complete(&fe->trigger_done); |
| /* TODO: shutdown PDM clock */ |
| break; |
| default: |
| break; |
| } |
| |
| dev_dbg(&rtd->dev, "%s - finish: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id,fe->substream->stream); |
| } |
| |
| |
| /* work for ABE playback frontend and backend ABE synchronisation */ |
| static void playback_work(struct work_struct *work) |
| { |
| struct abe_frontend_dai *fe = |
| container_of(work, struct abe_frontend_dai, work); |
| struct snd_soc_pcm_runtime *rtd = fe->substream->private_data; |
| |
| dev_dbg(&rtd->dev, "%s-start: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id,fe->substream->stream); |
| |
| switch (fe->cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| /* mute splayback */ |
| mute_be_playback(fe); |
| |
| /* enabled BE ports */ |
| enable_be_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* DAI work must be started/stopped at least 250us after ABE */ |
| udelay(250); |
| |
| /* trigger ALL BE ports */ |
| trigger_be_ports(fe, rtd); |
| |
| /* TODO: unmute DL1 */ |
| unmute_be_playback(fe); |
| |
| /* Enable Frontend sDMA */ |
| enable_fe_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* unmute ABE_MM_DL */ |
| unmute_fe_port(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| break; |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| /* Enable Frontend sDMA */ |
| enable_fe_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* unmute ABE_MM_DL */ |
| unmute_fe_port(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| break; |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| /* Enable Frontend sDMA */ |
| disable_fe_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* mute ABE_MM_DL */ |
| mute_fe_port(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| /* disable the transfer */ |
| disable_fe_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* mute ABE_MM_DL */ |
| mute_fe_port(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* TODO :mute Phoenix */ |
| |
| /* disable our backends */ |
| disable_be_ports(rtd, SNDRV_PCM_STREAM_PLAYBACK); |
| |
| /* DAI work must be started/stopped at least 250us after ABE */ |
| udelay(250); |
| |
| /* trigger ALL BE ports */ |
| trigger_be_ports(fe, rtd); |
| complete(&fe->trigger_done); |
| break; |
| default: |
| break; |
| } |
| |
| dev_dbg(&rtd->dev, "%s - finish: fe ID=%d stream %d\n", |
| __func__, rtd->cpu_dai->id,fe->substream->stream); |
| } |
| |
| static int omap_abe_dai_probe(struct snd_soc_dai *dai) |
| { |
| snd_soc_dai_set_drvdata(dai, &abe_data); |
| |
| /* MM */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA][SNDRV_PCM_STREAM_CAPTURE].work, |
| capture_work); |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA][SNDRV_PCM_STREAM_CAPTURE].trigger_done); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| /* MM UL */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA_CAPTURE][SNDRV_PCM_STREAM_CAPTURE].work, |
| capture_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_MEDIA_CAPTURE][SNDRV_PCM_STREAM_CAPTURE].trigger_done); |
| |
| /* VX */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_VOICE][SNDRV_PCM_STREAM_CAPTURE].work, |
| capture_work); |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_VOICE][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_VOICE][SNDRV_PCM_STREAM_CAPTURE].trigger_done); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_VOICE][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| /* TONES */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_TONES][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_TONES][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| /* VIB */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_VIBRA][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_VIBRA][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| /* MODEM */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_MODEM][SNDRV_PCM_STREAM_CAPTURE].work, |
| capture_work); |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_MODEM][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_MODEM][SNDRV_PCM_STREAM_CAPTURE].trigger_done); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_MODEM][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| /* MM LP */ |
| INIT_WORK(&abe_data.frontend[ABE_FRONTEND_DAI_LP_MEDIA][SNDRV_PCM_STREAM_PLAYBACK].work, |
| playback_work); |
| init_completion(&abe_data.frontend[ABE_FRONTEND_DAI_LP_MEDIA][SNDRV_PCM_STREAM_PLAYBACK].trigger_done); |
| |
| abe_data.workqueue = create_singlethread_workqueue("omap-abe"); |
| if (abe_data.workqueue == NULL) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| static int omap_abe_dai_remove(struct snd_soc_dai *dai) |
| { |
| destroy_workqueue(abe_data.workqueue); |
| return 0; |
| } |
| |
| 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, |
| .playback = { |
| .stream_name = "MultiMedia1 Playback", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .capture = { |
| .stream_name = "MultiMedia1 Capture", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .ops = &omap_abe_dai_ops, |
| }, |
| { /* Multimedia Capture */ |
| .name = "MultiMedia2", |
| .capture = { |
| .stream_name = "MultiMedia2 Capture", |
| .channels_min = 2, |
| .channels_max = 8, |
| .rates = SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .ops = &omap_abe_dai_ops, |
| }, |
| { /* Voice Playback and Capture */ |
| .name = "Voice", |
| .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 = 2, |
| .channels_max = 2, |
| .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .ops = &omap_abe_dai_ops, |
| }, |
| { /* Tones Playback */ |
| .name = "Tones", |
| .playback = { |
| .stream_name = "Tones Playback", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .ops = &omap_abe_dai_ops, |
| }, |
| { /* Vibra */ |
| .name = "Vibra", |
| .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", |
| .playback = { |
| .stream_name = "Voice Playback", |
| .channels_min = 1, |
| .channels_max = 1, |
| .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| .capture = { |
| .stream_name = "Voice Capture", |
| .channels_min = 1, |
| .channels_max = 1, |
| .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", |
| .playback = { |
| .stream_name = "MultiMedia1 Playback", |
| .channels_min = 2, |
| .channels_max = 2, |
| .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, |
| .formats = OMAP_ABE_FORMATS, |
| }, |
| }, |
| }; |
| |
| 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@slimlogic.co.uk>"); |
| MODULE_DESCRIPTION("OMAP ABE SoC Interface"); |
| MODULE_LICENSE("GPL"); |