diff options
author | Leo Wang <leozwang@google.com> | 2015-12-17 18:46:29 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-12-17 18:46:29 +0000 |
commit | 35ff0e77b16f8d92173771734f0100d4e89e3207 (patch) | |
tree | 33ac65c74fdf45825e194beaec3981139ef173ee | |
parent | 2a985427851477af063db365d9bca44e08c8cb5b (diff) | |
parent | 6b44590e7aee1077f981a648ed72c860078d9e61 (diff) | |
download | freescale-35ff0e77b16f8d92173771734f0100d4e89e3207.tar.gz |
Merge "Add audio HAL based on tinyALSA" into mnc-brillo-dev
-rwxr-xr-x | peripheral/alsa/Android.mk | 52 | ||||
-rw-r--r-- | peripheral/alsa/audio_hardware.h | 235 | ||||
-rw-r--r-- | peripheral/alsa/config_cs42888.h | 62 | ||||
-rw-r--r-- | peripheral/alsa/config_hdmi.h | 49 | ||||
-rw-r--r-- | peripheral/alsa/config_nullcard.h | 49 | ||||
-rw-r--r-- | peripheral/alsa/config_sii902x.h | 49 | ||||
-rw-r--r-- | peripheral/alsa/config_spdif.h | 49 | ||||
-rw-r--r-- | peripheral/alsa/config_usbaudio.h | 50 | ||||
-rw-r--r-- | peripheral/alsa/config_wm8958.h | 512 | ||||
-rwxr-xr-x | peripheral/alsa/config_wm8960.h | 160 | ||||
-rw-r--r-- | peripheral/alsa/config_wm8962.h | 239 | ||||
-rwxr-xr-x | peripheral/alsa/control.c | 254 | ||||
-rwxr-xr-x | peripheral/alsa/control.h | 52 | ||||
-rwxr-xr-x | peripheral/alsa/tinyalsa_hal.c | 3489 |
14 files changed, 5301 insertions, 0 deletions
diff --git a/peripheral/alsa/Android.mk b/peripheral/alsa/Android.mk new file mode 100755 index 0000000..d4a9aef --- /dev/null +++ b/peripheral/alsa/Android.mk @@ -0,0 +1,52 @@ +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is the Freescale ALSA module for i.MX. + +ifeq ($(strip $(BOARD_USES_ALSA_AUDIO)),true) + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM) +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw +LOCAL_SRC_FILES := tinyalsa_hal.c +LOCAL_C_INCLUDES += \ + external/tinyalsa/include \ + system/media/audio_utils/include \ + system/media/audio_effects/include +LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioutils libdl +LOCAL_MODULE_TAGS := optional +include $(BUILD_SHARED_LIBRARY) + +endif + + +ifeq ($(findstring imx, $(soc_name)), imx) + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := audio.primary.$(soc_name) +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := tinyalsa_hal.c control.c +LOCAL_C_INCLUDES += \ + external/tinyalsa/include \ + system/media/audio_utils/include \ + system/media/audio_effects/include +LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioutils libdl +LOCAL_MODULE_TAGS := optional +include $(BUILD_SHARED_LIBRARY) + +endif diff --git a/peripheral/alsa/audio_hardware.h b/peripheral/alsa/audio_hardware.h new file mode 100644 index 0000000..d3d109f --- /dev/null +++ b/peripheral/alsa/audio_hardware.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2015 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_AUDIO_HARDWARE_H +#define ANDROID_INCLUDE_IMX_AUDIO_HARDWARE_H + +#include <stdlib.h> +#include <tinyalsa/asoundlib.h> + +#include <hardware/hardware.h> + + +#define MIN(x, y) ((x) > (y) ? (y) : (x)) + +enum tty_modes { + TTY_MODE_OFF, + TTY_MODE_VCO, + TTY_MODE_HCO, + TTY_MODE_FULL +}; + +enum pcm_type { + PCM_NORMAL = 0, + PCM_HDMI, + PCM_ESAI, + PCM_TOTAL, +}; + +enum output_type { + OUTPUT_DEEP_BUF, // deep PCM buffers output stream + OUTPUT_PRIMARY, // low latency output stream + OUTPUT_HDMI, + OUTPUT_ESAI, + OUTPUT_TOTAL +}; + +struct route_setting +{ + char *ctl_name; + int intval; + char *strval; +}; + + +struct audio_card{ + char * name; + char * driver_name; + int supported_out_devices; + int supported_in_devices; + struct route_setting *defaults; + struct route_setting *bt_output; + struct route_setting *speaker_output; + struct route_setting *hs_output; + struct route_setting *earpiece_output; + struct route_setting *vx_hs_mic_input; + struct route_setting *mm_main_mic_input; + struct route_setting *vx_main_mic_input; + struct route_setting *mm_hs_mic_input; + struct route_setting *vx_bt_mic_input; + struct route_setting *mm_bt_mic_input; + int card; + unsigned int out_rate; + int out_channels; + int out_format; + unsigned int in_rate; + int in_channels; + int in_format; +}; + +#define MAX_AUDIO_CARD_NUM 4 +#define MAX_AUDIO_CARD_SCAN 4 + +#define MAX_SUP_CHANNEL_NUM 20 +#define MAX_SUP_RATE_NUM 20 + +struct imx_audio_device { + struct audio_hw_device hw_device; + + pthread_mutex_t lock; /* see note below on mutex acquisition order */ + int mode; + int in_device; + int in_card_idx; /* the index for array card_list and mixer */ + int out_device; + struct pcm *pcm_modem_dl; + struct pcm *pcm_modem_ul; + int in_call; + float voice_volume; + struct imx_stream_in *active_input; /*1 input stream, 2 outout stream*/ + struct imx_stream_out *active_output[OUTPUT_TOTAL]; + bool mic_mute; + int tty_mode; + struct echo_reference_itfe *echo_reference; + bool bluetooth_nrec; + bool device_is_auto; + int wb_amr; + bool low_power; + struct audio_card *card_list[MAX_AUDIO_CARD_NUM]; + struct mixer *mixer[MAX_AUDIO_CARD_NUM]; + int audio_card_num; + unsigned int default_rate; /*HAL input samplerate*/ + unsigned int mm_rate; /*HAL hardware output samplerate*/ + char usb_card_name[128]; +}; + +struct imx_stream_out { + struct audio_stream_out stream; + + pthread_mutex_t lock; /* see note below on mutex acquisition order */ + struct pcm_config config[PCM_TOTAL]; + struct pcm *pcm[PCM_TOTAL]; + int writeContiFailCount[PCM_TOTAL]; + struct resampler_itfe *resampler[PCM_TOTAL]; + char *buffer; + int standby; + struct echo_reference_itfe *echo_reference; + struct imx_audio_device *dev; + int write_threshold[PCM_TOTAL]; + bool low_power; + int write_flags[PCM_TOTAL]; + int device; + size_t buffer_frames; + uint64_t written; + audio_channel_mask_t channel_mask; + audio_channel_mask_t sup_channel_masks[3]; + int sup_rates[MAX_SUP_RATE_NUM]; +}; + +#define MAX_PREPROCESSORS 3 /* maximum one AGC + one NS + one AEC per input stream */ +struct effect_info_s { + effect_handle_t effect_itfe; + size_t num_channel_configs; + channel_config_t* channel_configs; +}; + +#define NUM_IN_AUX_CNL_CONFIGS 2 +channel_config_t in_aux_cnl_configs[NUM_IN_AUX_CNL_CONFIGS] = { + { AUDIO_CHANNEL_IN_FRONT , AUDIO_CHANNEL_IN_BACK}, + { AUDIO_CHANNEL_IN_STEREO , AUDIO_CHANNEL_IN_RIGHT} +}; + +#define MAX_NUM_CHANNEL_CONFIGS 10 + +struct imx_stream_in { + struct audio_stream_in stream; + + pthread_mutex_t lock; /* see note below on mutex acquisition order */ + struct pcm_config config; + struct pcm *pcm; + int device; + struct resampler_itfe *resampler; + struct resampler_buffer_provider buf_provider; + int16_t *read_buf; + size_t read_buf_size; + size_t read_buf_frames; + int32_t *read_tmp_buf; + size_t read_tmp_buf_size; + size_t read_tmp_buf_frames; + + unsigned int requested_rate; + unsigned int requested_format; + unsigned int requested_channel; + int standby; + int source; + struct echo_reference_itfe *echo_reference; + bool need_echo_reference; + struct effect_info_s preprocessors[MAX_PREPROCESSORS]; + int num_preprocessors; + + int16_t *proc_buf_in; + int16_t *proc_buf_out; + size_t proc_buf_size; + size_t proc_buf_frames; + + int16_t *ref_buf; + size_t ref_buf_size; + size_t ref_frames_in; + int read_status; + size_t mute_500ms; + struct imx_audio_device *dev; + int last_time_of_xrun; + bool aux_channels_changed; + uint32_t main_channels; + uint32_t aux_channels; +}; +#define STRING_TO_ENUM(string) { #string, string } +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +struct string_to_enum { + const char *name; + uint32_t value; +}; + +#define SUPPORTED_DEVICE_OUT_MODULE \ + ( AUDIO_DEVICE_OUT_EARPIECE | \ + AUDIO_DEVICE_OUT_SPEAKER | \ + AUDIO_DEVICE_OUT_WIRED_HEADSET | \ + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | \ + AUDIO_DEVICE_OUT_ALL_SCO | \ + AUDIO_DEVICE_OUT_AUX_DIGITAL | \ + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | \ + AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET | \ + AUDIO_DEVICE_OUT_REMOTE_SUBMIX | \ + AUDIO_DEVICE_OUT_DEFAULT) + +#define SUPPORTED_DEVICE_IN_MODULE \ + ( AUDIO_DEVICE_IN_COMMUNICATION | \ + AUDIO_DEVICE_IN_AMBIENT | \ + AUDIO_DEVICE_IN_BUILTIN_MIC | \ + AUDIO_DEVICE_IN_ALL_SCO | \ + AUDIO_DEVICE_IN_WIRED_HEADSET | \ + AUDIO_DEVICE_IN_AUX_DIGITAL | \ + AUDIO_DEVICE_IN_VOICE_CALL | \ + AUDIO_DEVICE_IN_BACK_MIC | \ + AUDIO_DEVICE_IN_REMOTE_SUBMIX | \ + AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET | \ + AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET | \ + AUDIO_DEVICE_IN_USB_ACCESSORY | \ + AUDIO_DEVICE_IN_USB_DEVICE | \ + AUDIO_DEVICE_IN_DEFAULT ) + +#endif /* ANDROID_INCLUDE_IMX_AUDIO_HARDWARE_H */ diff --git a/peripheral/alsa/config_cs42888.h b/peripheral/alsa/config_cs42888.h new file mode 100644 index 0000000..162ccf1 --- /dev/null +++ b/peripheral/alsa/config_cs42888.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2014 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_CS42888_H +#define ANDROID_INCLUDE_IMX_CONFIG_CS42888_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card cs42888_card = { + .name = "cs42888-audio", + .driver_name = "cs42888-audio", + .supported_out_devices = (AUDIO_DEVICE_OUT_EARPIECE | + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_OUT_ALL_SCO | + AUDIO_DEVICE_OUT_DEFAULT ), + .supported_in_devices = ( + AUDIO_DEVICE_IN_COMMUNICATION | + AUDIO_DEVICE_IN_AMBIENT | + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_ALL_SCO | + AUDIO_DEVICE_IN_DEFAULT), + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_CS42888_H */ diff --git a/peripheral/alsa/config_hdmi.h b/peripheral/alsa/config_hdmi.h new file mode 100644 index 0000000..83dd242 --- /dev/null +++ b/peripheral/alsa/config_hdmi.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_HDMI_H +#define ANDROID_INCLUDE_IMX_CONFIG_HDMI_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card hdmi_card = { + .name = "imx-hdmi-soc", + .driver_name = "imx-hdmi-soc", + .supported_out_devices = AUDIO_DEVICE_OUT_AUX_DIGITAL, + .supported_in_devices = 0, + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_HDMI_H */ diff --git a/peripheral/alsa/config_nullcard.h b/peripheral/alsa/config_nullcard.h new file mode 100644 index 0000000..9cd3a0a --- /dev/null +++ b/peripheral/alsa/config_nullcard.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_NULLCARD_H +#define ANDROID_INCLUDE_IMX_CONFIG_NULLCARD_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card null_card = { + .name = "null_card", + .driver_name = "null_card", + .supported_out_devices = 0, + .supported_in_devices = 0, + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = -1, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_NULLCARD_H */ diff --git a/peripheral/alsa/config_sii902x.h b/peripheral/alsa/config_sii902x.h new file mode 100644 index 0000000..f05351b --- /dev/null +++ b/peripheral/alsa/config_sii902x.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2015 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_SII902X_H +#define ANDROID_INCLUDE_IMX_CONFIG_SII902X_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card sii902x_card = { + .name = "sii902x-audio", + .driver_name = "sii902x-audio", + .supported_out_devices = AUDIO_DEVICE_OUT_AUX_DIGITAL, + .supported_in_devices = 0, + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_SII902X_H */ diff --git a/peripheral/alsa/config_spdif.h b/peripheral/alsa/config_spdif.h new file mode 100644 index 0000000..ea75d28 --- /dev/null +++ b/peripheral/alsa/config_spdif.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_SPDIF_H +#define ANDROID_INCLUDE_IMX_CONFIG_SPDIF_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card spdif_card = { + .name = "imx-spdif", + .driver_name = "imx-spdif", + .supported_out_devices = 0, + .supported_in_devices = AUDIO_DEVICE_IN_AUX_DIGITAL, + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_SPDIF_H */ diff --git a/peripheral/alsa/config_usbaudio.h b/peripheral/alsa/config_usbaudio.h new file mode 100644 index 0000000..6419b59 --- /dev/null +++ b/peripheral/alsa/config_usbaudio.h @@ -0,0 +1,50 @@ + +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2014 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_USBAUDIO_H +#define ANDROID_INCLUDE_IMX_CONFIG_USBAUDIO_H + +#include "audio_hardware.h" + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card usbaudio_card = { + .name = "USB Device", + .driver_name = "USB-Audio", + .supported_out_devices = AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET, + .supported_in_devices = AUDIO_DEVICE_IN_USB_DEVICE|AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET, + .defaults = NULL, + .bt_output = NULL, + .speaker_output = NULL, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = NULL, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_USBAUDIO_H */ diff --git a/peripheral/alsa/config_wm8958.h b/peripheral/alsa/config_wm8958.h new file mode 100644 index 0000000..6d9533f --- /dev/null +++ b/peripheral/alsa/config_wm8958.h @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2014 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_WM8958_H +#define ANDROID_INCLUDE_IMX_CONFIG_WM8958_H + +#include "audio_hardware.h" + +#define MIXER_WM8958_AIF1DAC_MUX "AIF1DAC Mux" +#define MIXER_WM8958_AIF2DAC_MUX "AIF2DAC Mux" +#define MIXER_WM8958_AIF2ADC_MUX "AIF2ADC Mux" +#define MIXER_WM8958_AIF3ADC_MUX "AIF3ADC Mux" + +#define MIXER_WM8958_AIF1ADC1L_MIXER_AIF2_SWITCH "AIF1ADC1L Mixer AIF2 Switch" +#define MIXER_WM8958_AIF1ADC1R_MIXER_AIF2_SWITCH "AIF1ADC1R Mixer AIF2 Switch" + +#define MIXER_WM8958_DAC1L_MIXER_AIF1_1_SWITCH "DAC1L Mixer AIF1.1 Switch" +#define MIXER_WM8958_DAC1L_MIXER_AIF1_2_SWITCH "DAC1L Mixer AIF1.2 Switch" +#define MIXER_WM8958_DAC1L_MIXER_AIF2_SWITCH "DAC1L Mixer AIF2 Switch" +#define MIXER_WM8958_DAC1L_MIXER_LEFT_SIDETONE_SWITCH "DAC1L Mixer Left Sidetone Switch" +#define MIXER_WM8958_DAC1L_MIXER_RIGHT_SIDETONE_SWITCH "DAC1L Mixer Right Sidetone Switch" + +#define MIXER_WM8958_DAC1R_MIXER_AIF1_1_SWITCH "DAC1R Mixer AIF1.1 Switch" +#define MIXER_WM8958_DAC1R_MIXER_AIF1_2_SWITCH "DAC1R Mixer AIF1.2 Switch" +#define MIXER_WM8958_DAC1R_MIXER_AIF2_SWITCH "DAC1R Mixer AIF2 Switch" +#define MIXER_WM8958_DAC1R_MIXER_LEFT_SIDETONE_SWITCH "DAC1R Mixer Left Sidetone Switch" +#define MIXER_WM8958_DAC1R_MIXER_RIGHT_SIDETONE_SWITCH "DAC1R Mixer Right Sidetone Switch" + +#define MIXER_WM8958_SPEAKER_SWITCH "Speaker Switch" +#define MIXER_WM8958_SPEAKER_VOLUME "Speaker Volume" +#define MIXER_WM8958_EARPIECE_SWITCH "Earpiece Switch" +#define MIXER_WM8958_EARPIECE_VOLUME "Earpiece Volume" +#define MIXER_WM8958_HEADPHONE_SWITCH "Headphone Switch" +#define MIXER_WM8958_HEADPHONE_VOLUME "Headphone Volume" + +#define MIXER_WM8958_LEFT_HEADPHONE_MUX "Left Headphone Mux" +#define MIXER_WM8958_RIGHT_HEADPHONE_MUX "Right Headphone Mux" + +#define MIXER_WM8958_DAC1_SWITCH "DAC1 Switch" +#define MIXER_WM8958_DAC2_SWITCH "DAC2 Switch" + +#define MIXER_WM8958_SPKL_DAC1_SWITCH "SPKL DAC1 Switch" +#define MIXER_WM8958_SPKL_DAC1_VOLUME "SPKL DAC1 Volume" +#define MIXER_WM8958_SPKR_DAC1_SWITCH "SPKR DAC1 Switch" +#define MIXER_WM8958_SPKR_DAC1_VOLUME "SPKR DAC1 Volume" + +#define MIXER_WM8958_SPKL_BOOST_SPKL_SWITCH "SPKL Boost SPKL Switch" +#define MIXER_WM8958_SPKR_BOOST_SPKR_SWITCH "SPKR Boost SPKR Switch" +#define MIXER_WM8958_SPEAKER_MIXER_VOLUME "Speaker Mixer Volume" + +#define MIXER_WM8958_IN1R_SWITCH "IN1R Switch" +#define MIXER_WM8958_IN1R_VOLUME "IN1R Volume" +#define MIXER_WM8958_IN1L_SWITCH "IN1L Switch" +#define MIXER_WM8958_IN1L_VOLUME "IN1L Volume" + +#define MIXER_WM8958_MIXINL_IN1L_VOLUME "MIXINL IN1L Volume" +#define MIXER_WM8958_MIXINL_IN1L_SWITCH "MIXINL IN1L Switch" +#define MIXER_WM8958_MIXINR_IN1R_VOLUME "MIXINR IN1R Volume" +#define MIXER_WM8958_MIXINR_IN1R_SWITCH "MIXINR IN1R Switch" + +#define MIXER_WM8958_IN1L_PGA_IN1LN_SWITCH "IN1L PGA IN1LN Switch" +#define MIXER_WM8958_IN1L_PGA_IN1LP_SWITCH "IN1L PGA IN1LP Switch" +#define MIXER_WM8958_IN1R_PGA_IN1RN_SWITCH "IN1R PGA IN1RN Switch" +#define MIXER_WM8958_IN1R_PGA_IN1RP_SWITCH "IN1R PGA IN1RP Switch" + +#define MIXER_WM8958_ADCL_MUX "ADCL Mux" +#define MIXER_WM8958_ADCR_MUX "ADCR Mux" + +#define MIXER_WM8958_ADC_OSR "ADC OSR" + +#define MIXER_WM8958_DAC2_LEFT_SIDETONE_VOLUME "DAC2 Left Sidetone Volume" +#define MIXER_WM8958_DAC2_RIGHT_SIDETONE_VOLUME "DAC2 Right Sidetone Volume" + +#define MIXER_WM8958_AIF2DAC2L_MIXER_LEFT_SIDETONE_SWITCH "AIF2DAC2L Mixer Left Sidetone Switch" +#define MIXER_WM8958_AIF2DAC2R_MIXER_RIGHT_SIDETONE_SWITCH "AIF2DAC2R Mixer Right Sidetone Switch" + +#define MIXER_WM8958_AIF1ADC1L_MIXER_ADCDMIC_SWITCH "AIF1ADC1L Mixer ADC/DMIC Switch" +#define MIXER_WM8958_AIF1ADC1R_MIXER_ADCDMIC_SWITCH "AIF1ADC1R Mixer ADC/DMIC Switch" + +#define MIXER_WM8958_AIF1ADC1L_MIXER_AIF2_SWITCH "AIF1ADC1L Mixer AIF2 Switch" +#define MIXER_WM8958_AIF1ADC1R_MIXER_AIF2_SWITCH "AIF1ADC1R Mixer AIF2 Switch" + +#define MIXER_WM8958_LEFT_SIDETONE "Left Sidetone" +#define MIXER_WM8958_RIGHT_SIDETONE "Right Sidetone" + + + +/* These are values that never change */ +static struct route_setting defaults_wm8958[] = { + /* general */ + { + .ctl_name = MIXER_WM8958_AIF1DAC_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_AIF2DAC_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1L_MIXER_AIF1_1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_DAC1L_MIXER_AIF1_2_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1L_MIXER_AIF2_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1L_MIXER_LEFT_SIDETONE_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1L_MIXER_RIGHT_SIDETONE_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1R_MIXER_AIF1_1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_DAC1R_MIXER_AIF1_2_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1R_MIXER_AIF2_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1R_MIXER_LEFT_SIDETONE_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1R_MIXER_RIGHT_SIDETONE_SWITCH, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC1_SWITCH, + .intval = 1, + }, + /* headset */ + + /* bt */ + { + .ctl_name = NULL, + }, +}; + +static struct route_setting bt_output_wm8958[] = { + { + .ctl_name = MIXER_WM8958_AIF2ADC_MUX, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_AIF3ADC_MUX, + .intval = 2, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting speaker_output_wm8958[] = { + { + .ctl_name = MIXER_WM8958_SPKL_DAC1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPKL_DAC1_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPKR_DAC1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPKR_DAC1_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPKL_BOOST_SPKL_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPKR_BOOST_SPKR_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPEAKER_MIXER_VOLUME, + .intval = 3, + }, + { + .ctl_name = MIXER_WM8958_SPEAKER_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_SPEAKER_VOLUME, + .intval = 60, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting hs_output_wm8958[] = { + { + .ctl_name = MIXER_WM8958_LEFT_HEADPHONE_MUX, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_RIGHT_HEADPHONE_MUX, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_HEADPHONE_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_HEADPHONE_VOLUME, + .intval = 57, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting earpiece_output_wm8958[] = { + { + .ctl_name = MIXER_WM8958_EARPIECE_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_EARPIECE_VOLUME, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting vx_hs_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_IN1L_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_VOLUME, + .intval = 27, + }, + { + .ctl_name = MIXER_WM8958_MIXINL_IN1L_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_MIXINL_IN1L_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_PGA_IN1LN_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_PGA_IN1LP_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_ADCL_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_ADC_OSR, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC2_LEFT_SIDETONE_VOLUME, + .intval = 12, + }, + { + .ctl_name = MIXER_WM8958_DAC2_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_AIF2ADC_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_AIF2DAC2L_MIXER_LEFT_SIDETONE_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting mm_main_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_IN1L_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_VOLUME, + .intval = 27, + }, + { + .ctl_name = MIXER_WM8958_MIXINL_IN1L_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_MIXINL_IN1L_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_PGA_IN1LN_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1L_PGA_IN1LP_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_ADCL_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_AIF1ADC1L_MIXER_ADCDMIC_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting vx_main_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_IN1R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_VOLUME, + .intval = 27, + }, + { + .ctl_name = MIXER_WM8958_MIXINR_IN1R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_MIXINR_IN1R_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_PGA_IN1RN_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_PGA_IN1RP_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_ADCR_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_ADC_OSR, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_DAC2_RIGHT_SIDETONE_VOLUME, + .intval = 12, + }, + { + .ctl_name = MIXER_WM8958_DAC2_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_AIF2ADC_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_AIF2DAC2R_MIXER_RIGHT_SIDETONE_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + +/*hs_mic exchanged with main mic for sabresd, because the the main is no implemented*/ +static struct route_setting mm_hs_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_IN1R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_VOLUME, + .intval = 27, + }, + { + .ctl_name = MIXER_WM8958_MIXINR_IN1R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_MIXINR_IN1R_VOLUME, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_PGA_IN1RN_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_IN1R_PGA_IN1RP_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_ADCR_MUX, + .intval = 0, + }, + { + .ctl_name = MIXER_WM8958_AIF1ADC1L_MIXER_ADCDMIC_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting vx_bt_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_AIF1ADC1L_MIXER_AIF2_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_AIF1ADC1R_MIXER_AIF2_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting mm_bt_mic_input_wm8958[] = { + { + .ctl_name = MIXER_WM8958_AIF1ADC1L_MIXER_AIF2_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8958_AIF1ADC1R_MIXER_AIF2_SWITCH, + .intval = 1, + }, + { + .ctl_name = NULL, + }, +}; + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card wm8958_card = { + .name = "wm8958-audio", + .driver_name = "wm8958-audio", + .supported_out_devices = (AUDIO_DEVICE_OUT_EARPIECE | + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_OUT_ALL_SCO | + AUDIO_DEVICE_OUT_DEFAULT), + .supported_in_devices = ( + AUDIO_DEVICE_IN_COMMUNICATION | + AUDIO_DEVICE_IN_AMBIENT | + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_ALL_SCO | + AUDIO_DEVICE_IN_DEFAULT), + .defaults = defaults_wm8958, + .bt_output = bt_output_wm8958, + .speaker_output = speaker_output_wm8958, + .hs_output = hs_output_wm8958, + .earpiece_output = earpiece_output_wm8958, + .vx_hs_mic_input = vx_hs_mic_input_wm8958, + .mm_main_mic_input = mm_main_mic_input_wm8958, + .vx_main_mic_input = vx_main_mic_input_wm8958, + .mm_hs_mic_input = mm_hs_mic_input_wm8958, + .vx_bt_mic_input = vx_bt_mic_input_wm8958, + .mm_bt_mic_input = mm_bt_mic_input_wm8958, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_WM8958_H */ diff --git a/peripheral/alsa/config_wm8960.h b/peripheral/alsa/config_wm8960.h new file mode 100755 index 0000000..a250cc0 --- /dev/null +++ b/peripheral/alsa/config_wm8960.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2015 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_WM8960_H +#define ANDROID_INCLUDE_IMX_CONFIG_WM8960_H + +#include "audio_hardware.h" + + +#define MIXER_WM8960_SPEAKER_VOLUME "Speaker Playback Volume" +#define MIXER_WM8960_HEADPHONE_VOLUME "Headphone Playback Volume" +#define MIXER_WM8960_PLAYBACK_VOLUME "Playback Volume" + +#define MIXER_WM8960_LEFT_OUTPUT_SWITCH "Left Output Mixer PCM Playback Switch" +#define MIXER_WM8960_RIGHT_OUTPUT_SWITCH "Right Output Mixer PCM Playback Switch" + +#define MIXER_WM8960_CAPTURE_SWITCH "Capture Switch" +#define MIXER_WM8960_CAPTURE_VOLUME "Capture Volume" + +#define MIXER_WM8960_ALC_FUNCTION "ALC Function" +#define MIXER_WM8960_LEFT_INPUT_SWITCH "Left Input Mixer Boost Switch" +#define MIXER_WM8960_ADC_PCM_CAPTURE_VOLUME "ADC PCM Capture Volume" + +#ifdef BRILLO +#define MIXER_WM8960_LEFT_INPUT1_SWITCH "Left Boost Mixer LINPUT1 Switch" +#define MIXER_WM8960_LEFT_INPUT2_SWITCH "Left Boost Mixer LINPUT2 Switch" +#define MIXER_WM8960_LEFT_INPUT3_SWITCH "Left Boost Mixer LINPUT3 Switch" +#define MIXER_WM8960_RIGHT_INPUT_SWITCH "Right Input Mixer Boost Switch" +#define MIXER_WM8960_RIGHT_INPUT1_SWITCH "Right Boost Mixer LINPUT1 Switch" +#define MIXER_WM8960_RIGHT_INPUT2_SWITCH "Right Boost Mixer LINPUT2 Switch" +#endif + +static struct route_setting speaker_output_wm8960[] = { + { + .ctl_name = MIXER_WM8960_LEFT_OUTPUT_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_RIGHT_OUTPUT_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_PLAYBACK_VOLUME, + .intval = 230, + }, + { + .ctl_name = MIXER_WM8960_SPEAKER_VOLUME, + .intval = 120, + }, + { + .ctl_name = MIXER_WM8960_HEADPHONE_VOLUME, + .intval = 120, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting mm_main_mic_input_wm8960[] = { + { + .ctl_name = MIXER_WM8960_ALC_FUNCTION, + .intval = 3, + }, + { + .ctl_name = MIXER_WM8960_LEFT_INPUT_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_ADC_PCM_CAPTURE_VOLUME, + .intval = 230, + }, + { + .ctl_name = MIXER_WM8960_CAPTURE_VOLUME, + .intval = 60, + }, +#ifdef BRILLO + { + .ctl_name = MIXER_WM8960_LEFT_INPUT1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_LEFT_INPUT2_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_LEFT_INPUT3_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_RIGHT_INPUT_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_RIGHT_INPUT1_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8960_RIGHT_INPUT2_SWITCH, + .intval = 1, + }, +#endif + { + .ctl_name = NULL, + }, +}; + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card wm8960_card = { + .name = "wm8960-audio", + .driver_name = "wm8960-audio", + .supported_out_devices = (AUDIO_DEVICE_OUT_EARPIECE | + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_OUT_ALL_SCO | + AUDIO_DEVICE_OUT_DEFAULT ), + .supported_in_devices = ( + AUDIO_DEVICE_IN_COMMUNICATION | + AUDIO_DEVICE_IN_AMBIENT | + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_ALL_SCO | + AUDIO_DEVICE_IN_DEFAULT), + .defaults = NULL, + .bt_output = NULL, + .speaker_output = speaker_output_wm8960, + .hs_output = NULL, + .earpiece_output = NULL, + .vx_hs_mic_input = NULL, + .mm_main_mic_input = mm_main_mic_input_wm8960, + .vx_main_mic_input = NULL, + .mm_hs_mic_input = NULL, + .vx_bt_mic_input = NULL, + .mm_bt_mic_input = NULL, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_WM8960_H */ diff --git a/peripheral/alsa/config_wm8962.h b/peripheral/alsa/config_wm8962.h new file mode 100644 index 0000000..582be53 --- /dev/null +++ b/peripheral/alsa/config_wm8962.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2014 Freescale Semiconductor, Inc. */ + +#ifndef ANDROID_INCLUDE_IMX_CONFIG_WM8962_H +#define ANDROID_INCLUDE_IMX_CONFIG_WM8962_H + +#include "audio_hardware.h" + + +#define MIXER_WM8962_SPEAKER_VOLUME "Speaker Volume" +#define MIXER_WM8962_SPEAKER_SWITCH "Speaker Switch" +#define MIXER_WM8962_HEADPHONE_VOLUME "Headphone Volume" +#define MIXER_WM8962_HEADPHONE_SWITCH "Headphone Switch" + +#define MIXER_WM8962_CAPTURE_SWITCH "Capture Switch" +#define MIXER_WM8962_CAPTURE_VOLUME "Capture Volume" + +#define MIXER_WM8962_INPGAR_IN3R_SWITCH "INPGAR IN3R Switch" +#define MIXER_WM8962_MIXINR_IN3R_SWITCH "MIXINR IN3R Switch" +#define MIXER_WM8962_MIXINR_IN3R_VOLUME "MIXINR IN3R Volume" + +#define MIXER_WM8962_MIXINR_PGA_SWITCH "MIXINR PGA Switch" +#define MIXER_WM8962_MIXINR_PGA_VOLUME "MIXINR PGA Volume" + +#define MIXER_WM8962_DIGITAL_CAPTURE_VOLUME "Digital Capture Volume" + +#define MIXER_WM8962_DIGITAL_PLAYBACK_VOLUME "Digital Playback Volume" + + +/* These are values that never change */ +static struct route_setting defaults_wm8962[] = { + /* general */ + { + .ctl_name = MIXER_WM8962_DIGITAL_PLAYBACK_VOLUME, + .intval = 96, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting bt_output_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + +static struct route_setting speaker_output_wm8962[] = { + { + .ctl_name = MIXER_WM8962_SPEAKER_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_SPEAKER_VOLUME, + .intval = 121, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting hs_output_wm8962[] = { + { + .ctl_name = MIXER_WM8962_HEADPHONE_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_HEADPHONE_VOLUME, + .intval = 121, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting earpiece_output_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + +static struct route_setting vx_hs_mic_input_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting mm_main_mic_input_wm8962[] = { + { + .ctl_name = MIXER_WM8962_CAPTURE_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_CAPTURE_VOLUME, + .intval = 63, + }, + { + .ctl_name = MIXER_WM8962_DIGITAL_CAPTURE_VOLUME, + .intval = 127, + },/* + { + .ctl_name = MIXER_WM8962_INPGAR_IN3R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_PGA_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_PGA_VOLUME, + .intval = 7, + },*/ + { + .ctl_name = MIXER_WM8962_MIXINR_IN3R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_IN3R_VOLUME, + .intval = 7, + }, + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting vx_main_mic_input_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + +/*hs_mic exchanged with main mic for sabresd, because the the main is no implemented*/ +static struct route_setting mm_hs_mic_input_wm8962[] = { + { + .ctl_name = MIXER_WM8962_CAPTURE_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_CAPTURE_VOLUME, + .intval = 63, + }, + { + .ctl_name = MIXER_WM8962_DIGITAL_CAPTURE_VOLUME, + .intval = 127, + },/* + { + .ctl_name = MIXER_WM8962_INPGAR_IN3R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_PGA_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_PGA_VOLUME, + .intval = 7, + },*/ + { + .ctl_name = MIXER_WM8962_MIXINR_IN3R_SWITCH, + .intval = 1, + }, + { + .ctl_name = MIXER_WM8962_MIXINR_IN3R_VOLUME, + .intval = 7, + }, + { + .ctl_name = NULL, + }, +}; + +static struct route_setting vx_bt_mic_input_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + + +static struct route_setting mm_bt_mic_input_wm8962[] = { + { + .ctl_name = NULL, + }, +}; + +/* ALSA cards for IMX, these must be defined according different board / kernel config*/ +static struct audio_card wm8962_card = { + .name = "wm8962-audio", + .driver_name = "wm8962-audio", + .supported_out_devices = (AUDIO_DEVICE_OUT_EARPIECE | + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_OUT_ALL_SCO | + AUDIO_DEVICE_OUT_DEFAULT ), + .supported_in_devices = ( + AUDIO_DEVICE_IN_COMMUNICATION | + AUDIO_DEVICE_IN_AMBIENT | + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_ALL_SCO | + AUDIO_DEVICE_IN_DEFAULT), + .defaults = defaults_wm8962, + .bt_output = bt_output_wm8962, + .speaker_output = speaker_output_wm8962, + .hs_output = hs_output_wm8962, + .earpiece_output = earpiece_output_wm8962, + .vx_hs_mic_input = vx_hs_mic_input_wm8962, + .mm_main_mic_input = mm_main_mic_input_wm8962, + .vx_main_mic_input = vx_main_mic_input_wm8962, + .mm_hs_mic_input = mm_hs_mic_input_wm8962, + .vx_bt_mic_input = vx_bt_mic_input_wm8962, + .mm_bt_mic_input = mm_bt_mic_input_wm8962, + .card = 0, + .out_rate = 0, + .out_channels = 0, + .out_format = 0, + .in_rate = 0, + .in_channels = 0, + .in_format = 0, +}; + +#endif /* ANDROID_INCLUDE_IMX_CONFIG_WM8962_H */ diff --git a/peripheral/alsa/control.c b/peripheral/alsa/control.c new file mode 100755 index 0000000..69fe7c7 --- /dev/null +++ b/peripheral/alsa/control.c @@ -0,0 +1,254 @@ +/* +** Copyright 2011, The Android Open Source Project +** Copyright (C) 2012-2015 Freescale Semiconductor, Inc. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <linux/ioctl.h> +#include <sound/asound.h> +#include <tinyalsa/asoundlib.h> +#include "control.h" + +struct control *control_open(unsigned int card) +{ + struct snd_ctl_card_info tmp; + struct control *control = NULL; + unsigned int n, m; + int fd; + char fn[256]; + int device = -1; + struct ctl_pcm_info *current = NULL; + + snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); + fd = open(fn, O_RDWR); + if (fd < 0) + return 0; + + control = calloc(1, sizeof(*control)); + if (!control) + goto fail; + + control->count_p = 0; + control->count_c = 0; + control->fd = fd; + control->card_info = calloc(1, sizeof(struct snd_ctl_card_info)); + if (!control->card_info) + goto fail; + + if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, control->card_info) < 0) + goto fail; + + control->pcm_info_p = calloc(1, sizeof(struct ctl_pcm_info)); + if (!control->pcm_info_p) + goto fail; + + current = control->pcm_info_p; + device = -1; + while(1) + { + if (ioctl(fd, SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE, &device) < 0) + break; + if(device < 0) + break; + + control->count_p += 1; + current->info = calloc(1, sizeof(struct snd_pcm_info)); + if (!current->info) + goto fail; + + current->info->device = device; + current->info->subdevice = 0; + current->info->stream = SNDRV_PCM_STREAM_PLAYBACK; + + if (ioctl(fd, SNDRV_CTL_IOCTL_PCM_INFO, current->info) < 0) + break; + + current->next = calloc(1, sizeof(struct ctl_pcm_info)); + if (!current->next) + goto fail; + current = current->next; + } + + control->pcm_info_c = calloc(1, sizeof(struct ctl_pcm_info)); + if (!control->pcm_info_c) + goto fail; + + current = control->pcm_info_c; + device = -1; + while(1) + { + if (ioctl(fd, SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE, &device) < 0) + break; + if(device < 0) + break; + + control->count_c += 1; + current->info = calloc(1, sizeof(struct snd_pcm_info)); + if (!current->info) + goto fail; + + current->info->device = device; + current->info->subdevice = 0; + current->info->stream = SNDRV_PCM_STREAM_CAPTURE; + + if (ioctl(fd, SNDRV_CTL_IOCTL_PCM_INFO, current->info) < 0) + break; + + current->next = calloc(1, sizeof(struct ctl_pcm_info)); + if (!current->next) + goto fail; + current = current->next; + } + return control; + +fail: + if (control) + control_close(control); + else if (fd >= 0) + close(fd); + return 0; +} + +void control_close(struct control *control) +{ + unsigned int n,m; + struct ctl_pcm_info *current = NULL; + struct ctl_pcm_info *p = NULL; + + if (!control) + return; + + if (control->fd >= 0) + close(control->fd); + + if (control->card_info) + free(control->card_info); + + current = control->pcm_info_p; + while(!current) + { + if(!current->info) + free(current->info); + p = current; + current = current->next; + free(p); + } + + current = control->pcm_info_c; + while(!current) + { + if(!current->info) + free(current->info); + p = current; + current = current->next; + free(p); + } + free(control); +} + +const char *control_card_info_get_id(struct control *control) +{ + if (!control) + return ""; + + return (const char *)control->card_info->id; +} + +const char *control_card_info_get_driver(struct control *control) +{ + if (!control) + return ""; + + return (const char *)control->card_info->driver; +} + +const char *control_card_info_get_name(struct control *control) +{ + if (!control) + return ""; + + return (const char *)control->card_info->name; +} + + +int control_pcm_next_device(struct control *control, int *device, int stream) +{ + struct ctl_pcm_info *current = NULL; + if (!control) + return -EINVAL; + + if(stream == SNDRV_PCM_STREAM_PLAYBACK) current = control->pcm_info_p; + else current = control->pcm_info_c; + + while(!current->info) + if((int)current->info->device > *device) + { + *device = current->info->device; + return 0; + } + return -1; +} + + +const char *control_pcm_info_get_id(struct control *control, unsigned int device, int stream) +{ + struct ctl_pcm_info *current = NULL; + if (!control) + return ""; + + if(stream == SNDRV_PCM_STREAM_PLAYBACK) current = control->pcm_info_p; + else current = control->pcm_info_c; + + while(!current->info) + if(current->info->device == device) + return (const char *)current->info->id; + + return ""; +} + +const char *control_pcm_info_get_name(struct control *control, unsigned int device, int stream) +{ + struct ctl_pcm_info *current = NULL; + if (!control) + return ""; + + if(stream == SNDRV_PCM_STREAM_PLAYBACK) current = control->pcm_info_p; + else current = control->pcm_info_c; + + while(!current->info) + if(current->info->device == device) + return (const char *)current->info->name; + + return ""; +} + + diff --git a/peripheral/alsa/control.h b/peripheral/alsa/control.h new file mode 100755 index 0000000..916f06a --- /dev/null +++ b/peripheral/alsa/control.h @@ -0,0 +1,52 @@ +/* +** Copyright 2015, The Android Open Source Project +** Copyright (C) 2015 Freescale Semiconductor, Inc. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +** DAMAGE. +*/ + +#ifndef _CONTROL_H +#define _CONTROL_H + +struct ctl_pcm_info { + struct snd_pcm_info *info; + struct ctl_pcm_info *next; +}; + +struct control { + int fd; + struct snd_ctl_card_info *card_info; + struct ctl_pcm_info *pcm_info_p; + unsigned int count_p; + struct ctl_pcm_info *pcm_info_c; + unsigned int count_c; +}; + +struct control *control_open(unsigned int card); +void control_close(struct control *control); +const char *control_card_info_get_id(struct control *control); +const char *control_card_info_get_driver(struct control *control); +const char *control_card_info_get_name(struct control *control); + +#endif //_CONTROL_H diff --git a/peripheral/alsa/tinyalsa_hal.c b/peripheral/alsa/tinyalsa_hal.c new file mode 100755 index 0000000..6ab494e --- /dev/null +++ b/peripheral/alsa/tinyalsa_hal.c @@ -0,0 +1,3489 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Copyright (C) 2012-2015 Freescale Semiconductor, Inc. */ + +#define LOG_TAG "audio_hw_primary" +//#define LOG_NDEBUG 0 + +#include <errno.h> +#include <pthread.h> +#include <stdint.h> +#include <sys/time.h> +#include <stdlib.h> + +#include <cutils/log.h> +#include <cutils/str_parms.h> +#include <cutils/properties.h> + +#include <hardware/hardware.h> +#include <system/audio.h> +#include <hardware/audio.h> + +#include <tinyalsa/asoundlib.h> +#include <audio_utils/resampler.h> +#include <audio_utils/echo_reference.h> +#include <hardware/audio_effect.h> +#include <audio_effects/effect_aec.h> + +#include "audio_hardware.h" +#include "config_wm8962.h" +#include "config_wm8958.h" +#include "config_hdmi.h" +#include "config_usbaudio.h" +#include "config_nullcard.h" +#include "config_spdif.h" +#include "config_cs42888.h" +#include "config_wm8960.h" +#include "config_sii902x.h" + +#ifdef BRILLO +#define PCM_HW_PARAM_ACCESS 0 +#define PCM_HW_PARAM_FORMAT 1 +#define PCM_HW_PARAM_SUBFORMAT 2 +#define PCM_HW_PARAM_FIRST_MASK PCM_HW_PARAM_ACCESS +#define PCM_HW_PARAM_LAST_MASK PCM_HW_PARAM_SUBFORMAT +#define PCM_HW_PARAM_SAMPLE_BITS 8 +#define PCM_HW_PARAM_FRAME_BITS 9 +#define PCM_HW_PARAM_CHANNELS 10 +#define PCM_HW_PARAM_RATE 11 +#define PCM_HW_PARAM_PERIOD_TIME 12 +#define PCM_HW_PARAM_PERIOD_SIZE 13 +#define PCM_HW_PARAM_PERIOD_BYTES 14 +#define PCM_HW_PARAM_PERIODS 15 +#define PCM_HW_PARAM_BUFFER_TIME 16 +#define PCM_HW_PARAM_BUFFER_SIZE 17 +#define PCM_HW_PARAM_BUFFER_BYTES 18 +#define PCM_HW_PARAM_TICK_TIME 19 +#define PCM_HW_PARAM_FIRST_INTERVAL PCM_HW_PARAM_SAMPLE_BITS +#define PCM_HW_PARAM_LAST_INTERVAL PCM_HW_PARAM_TICK_TIME +#define PCM_HW_PARAMS_NORESAMPLE (1<<0) +#endif + + +/* ALSA ports for IMX */ +#define PORT_MM 0 +#define PORT_MM2_UL 0 +#define PORT_SPDIF 6 /*not used*/ +#define PORT_HDMI 0 + +/*align the definition in kernel for hdmi audio*/ +#define HDMI_PERIOD_SIZE 192 +#define PLAYBACK_HDMI_PERIOD_COUNT 8 + +#define ESAI_PERIOD_SIZE 192 +#define PLAYBACK_ESAI_PERIOD_COUNT 8 + +/* number of frames per short period (low latency) */ +/* align other card with hdmi, same latency*/ +#define SHORT_PERIOD_SIZE 384 +/* number of short periods in a long period (low power) */ +#define LONG_PERIOD_MULTIPLIER 2 +/* number of frames per long period (low power) */ +#define LONG_PERIOD_SIZE 192 +/* number of periods for low power playback */ +#define PLAYBACK_LONG_PERIOD_COUNT 8 +/* number of periods for capture */ +#define CAPTURE_PERIOD_SIZE 1024 +/* number of periods for capture */ +#define CAPTURE_PERIOD_COUNT 4 +/* minimum sleep time in out_write() when write threshold is not reached */ +#define MIN_WRITE_SLEEP_US 5000 + +#define DEFAULT_OUT_SAMPLING_RATE 44100 + +/* sampling rate when using MM low power port */ +#define MM_LOW_POWER_SAMPLING_RATE 44100 +/* sampling rate when using MM full power port */ +#define MM_FULL_POWER_SAMPLING_RATE 44100 + +#define MM_USB_AUDIO_IN_RATE 16000 + +/* product-specific defines */ +#define PRODUCT_DEVICE_PROPERTY "ro.product.device" +#define PRODUCT_NAME_PROPERTY "ro.product.name" +#define PRODUCT_DEVICE_IMX "imx" +#define PRODUCT_DEVICE_AUTO "sabreauto" +#define SUPPORT_CARD_NUM 9 + +/*"null_card" must be in the end of this array*/ +struct audio_card *audio_card_list[SUPPORT_CARD_NUM] = { + &wm8958_card, + &wm8962_card, + &hdmi_card, + &usbaudio_card, + &spdif_card, + &cs42888_card, + &wm8960_card, + &sii902x_card, + &null_card, +}; + +struct pcm_config pcm_config_mm_out = { + .channels = 2, + .rate = MM_FULL_POWER_SAMPLING_RATE, + .period_size = LONG_PERIOD_SIZE, + .period_count = PLAYBACK_LONG_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .avail_min = 0, +}; + +struct pcm_config pcm_config_hdmi_multi = { + .channels = 8, /* changed when the stream is opened */ + .rate = MM_FULL_POWER_SAMPLING_RATE, /* changed when the stream is opened */ + .period_size = HDMI_PERIOD_SIZE, + .period_count = PLAYBACK_HDMI_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .avail_min = 0, +}; +struct pcm_config pcm_config_esai_multi = { + .channels = 8, /* changed when the stream is opened */ + .rate = MM_FULL_POWER_SAMPLING_RATE, /* changed when the stream is opened */ + .period_size = ESAI_PERIOD_SIZE, + .period_count = PLAYBACK_ESAI_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .avail_min = 0, +}; + +struct pcm_config pcm_config_mm_in = { + .channels = 2, + .rate = MM_FULL_POWER_SAMPLING_RATE, + .period_size = CAPTURE_PERIOD_SIZE, + .period_count = CAPTURE_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .avail_min = 0, +}; + +const struct string_to_enum out_channels_name_to_enum_table[] = { + STRING_TO_ENUM(AUDIO_CHANNEL_OUT_STEREO), + STRING_TO_ENUM(AUDIO_CHANNEL_OUT_5POINT1), + STRING_TO_ENUM(AUDIO_CHANNEL_OUT_7POINT1), +}; + + +/** + * NOTE: when multiple mutexes have to be acquired, always respect the following order: + * hw device > in stream > out stream + */ +static void select_output_device(struct imx_audio_device *adev); +static void select_input_device(struct imx_audio_device *adev); +static int adev_set_voice_volume(struct audio_hw_device *dev, float volume); +static int do_input_standby(struct imx_stream_in *in); +static int do_output_standby(struct imx_stream_out *out); +static int scan_available_device(struct imx_audio_device *adev, bool rescanusb, bool queryInput, bool queryOutput); +static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer); +static void release_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer); +static int adev_get_rate_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag); +static int adev_get_channels_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag); +static int adev_get_format_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag); +static void in_update_aux_channels(struct imx_stream_in *in, effect_handle_t effect); +static int pcm_read_wrapper(struct pcm *pcm, const void * buffer, size_t bytes); + +/* Returns true on devices that are sabreauto, false otherwise */ +static int is_device_auto(void) +{ + char property[PROPERTY_VALUE_MAX]; + + property_get(PRODUCT_DEVICE_PROPERTY, property, ""); + + /* return true if the property matches the given value */ + return strstr(property, PRODUCT_DEVICE_AUTO) != NULL; +} + +static int convert_record_data(void *src, void *dst, unsigned int frames, bool bit_24b_2_16b, bool mono2stereo, bool stereo2mono) +{ + unsigned int i; + short *dst_t = (short *)dst; + if (bit_24b_2_16b && mono2stereo && !stereo2mono) { + int data; + int *src_t = (int *)src; + for(i = 0; i < frames; i++) + { + data = *src_t++; + *dst_t++ = (short)(data >> 8); + *dst_t++ = (short)(data >> 8); + } + } + + if (bit_24b_2_16b && !mono2stereo && stereo2mono) { + int data1=0, data2=0; + int *src_t = (int *)src; + for(i = 0; i < frames; i++) + { + data1 = *src_t++; + data2 = *src_t++; + *dst_t++ = (short)(((data1 << 8) >> 17) + ((data2 << 8) >> 17)); + } + } + + if (bit_24b_2_16b && !mono2stereo && !stereo2mono) { + int data1, data2; + int *src_t = (int *)src; + for(i = 0; i < frames; i++) + { + data1 = *src_t++; + data2 = *src_t++; + *dst_t++ = (short)(data1 >> 8); + *dst_t++ = (short)(data2 >> 8); + } + } + + if (!bit_24b_2_16b && mono2stereo && !stereo2mono ) { + short data; + short *src_t = (short *)src; + for(i = 0; i < frames; i++) + { + data = *src_t++; + *dst_t++ = data; + *dst_t++ = data; + } + } + + if (!bit_24b_2_16b && !mono2stereo && stereo2mono) { + short data1, data2; + short *src_t = (short *)src; + for(i = 0; i < frames; i++) + { + data1 = *src_t++; + data2 = *src_t++; + *dst_t++ = (data1 >> 1) + (data2 >> 1); + } + } + + return 0; +} + +/* The enable flag when 0 makes the assumption that enums are disabled by + * "Off" and integers/booleans by 0 */ +static int set_route_by_array(struct mixer *mixer, struct route_setting *route, + int enable) +{ + struct mixer_ctl *ctl = NULL; + unsigned int i, j; + + if(!mixer) return 0; + if(!route) return 0; + /* Go through the route array and set each value */ + i = 0; + while (route[i].ctl_name) { + ctl = mixer_get_ctl_by_name(mixer, route[i].ctl_name); + if (!ctl) + return -EINVAL; + + if (route[i].strval) { + if (enable) + mixer_ctl_set_enum_by_string(ctl, route[i].strval); + else + mixer_ctl_set_enum_by_string(ctl, "Off"); + } else { + /* This ensures multiple (i.e. stereo) values are set jointly */ + for (j = 0; j < mixer_ctl_get_num_values(ctl); j++) { + if (enable) + mixer_ctl_set_value(ctl, j, route[i].intval); + else + mixer_ctl_set_value(ctl, j, 0); + } + } + i++; + } + + return 0; +} + + + +static void force_all_standby(struct imx_audio_device *adev) +{ + struct imx_stream_in *in; + struct imx_stream_out *out; + int i; + + for(i = 0; i < OUTPUT_TOTAL; i++) + if (adev->active_output[i]) { + out = adev->active_output[i]; + pthread_mutex_lock(&out->lock); + do_output_standby(out); + pthread_mutex_unlock(&out->lock); + } + + if (adev->active_input) { + in = adev->active_input; + pthread_mutex_lock(&in->lock); + do_input_standby(in); + pthread_mutex_unlock(&in->lock); + } +} + +static void select_mode(struct imx_audio_device *adev) +{ + if (adev->mode == AUDIO_MODE_IN_CALL) { + ALOGW("Entering IN_CALL state, in_call=%d", adev->in_call); + if (!adev->in_call) { + force_all_standby(adev); + /* force earpiece route for in call state if speaker is the + only currently selected route. This prevents having to tear + down the modem PCMs to change route from speaker to earpiece + after the ringtone is played, but doesn't cause a route + change if a headset or bt device is already connected. If + speaker is not the only thing active, just remove it from + the route. We'll assume it'll never be used initally during + a call. This works because we're sure that the audio policy + manager will update the output device after the audio mode + change, even if the device selection did not change. */ + if (adev->out_device == AUDIO_DEVICE_OUT_SPEAKER) { + adev->out_device = AUDIO_DEVICE_OUT_EARPIECE; + adev->in_device = AUDIO_DEVICE_IN_BUILTIN_MIC & ~AUDIO_DEVICE_BIT_IN; + } else + adev->out_device &= ~AUDIO_DEVICE_OUT_SPEAKER; + select_output_device(adev); + + adev_set_voice_volume(&adev->hw_device, adev->voice_volume); + adev->in_call = 1; + } + } else { + ALOGW("Leaving IN_CALL state, in_call=%d, mode=%d", + adev->in_call, adev->mode); + if (adev->in_call) { + adev->in_call = 0; + force_all_standby(adev); + select_output_device(adev); + select_input_device(adev); + } + } +} + +static void select_output_device(struct imx_audio_device *adev) +{ + int headset_on; + int headphone_on; + int speaker_on; + int earpiece_on; + int bt_on; + bool tty_volume = false; + unsigned int channel; + int i; + + headset_on = adev->out_device & AUDIO_DEVICE_OUT_WIRED_HEADSET; + headphone_on = adev->out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE; + speaker_on = adev->out_device & AUDIO_DEVICE_OUT_SPEAKER; + earpiece_on = adev->out_device & AUDIO_DEVICE_OUT_EARPIECE; + bt_on = adev->out_device & AUDIO_DEVICE_OUT_ALL_SCO; + + /* force rx path according to TTY mode when in call */ + if (adev->mode == AUDIO_MODE_IN_CALL && !bt_on) { + switch(adev->tty_mode) { + case TTY_MODE_FULL: + case TTY_MODE_VCO: + /* rx path to headphones */ + headphone_on = 1; + headset_on = 0; + speaker_on = 0; + earpiece_on = 0; + tty_volume = true; + break; + case TTY_MODE_HCO: + /* rx path to device speaker */ + headphone_on = 0; + headset_on = 0; + speaker_on = 1; + earpiece_on = 0; + break; + case TTY_MODE_OFF: + default: + /* force speaker on when in call and HDMI is selected as voice DL audio + * cannot be routed to HDMI by ABE */ + if (adev->out_device & AUDIO_DEVICE_OUT_AUX_DIGITAL) + speaker_on = 1; + break; + } + } + /*if mode = AUDIO_MODE_IN_CALL*/ + ALOGV("headphone %d ,headset %d ,speaker %d, earpiece %d, \n", headphone_on, headset_on, speaker_on, earpiece_on); + /* select output stage */ + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->bt_output, bt_on); + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->hs_output, headset_on | headphone_on); + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->speaker_output, speaker_on); + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->earpiece_output, earpiece_on); + + /* Special case: select input path if in a call, otherwise + in_set_parameters is used to update the input route + todo: use sub mic for handsfree case */ + if (adev->mode == AUDIO_MODE_IN_CALL) { + if (bt_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->vx_bt_mic_input, bt_on); + else { + /* force tx path according to TTY mode when in call */ + switch(adev->tty_mode) { + case TTY_MODE_FULL: + case TTY_MODE_HCO: + /* tx path from headset mic */ + headphone_on = 0; + headset_on = 1; + speaker_on = 0; + earpiece_on = 0; + break; + case TTY_MODE_VCO: + /* tx path from device sub mic */ + headphone_on = 0; + headset_on = 0; + speaker_on = 1; + earpiece_on = 0; + break; + case TTY_MODE_OFF: + default: + break; + } + + if (headset_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->vx_hs_mic_input, 1); + else if (headphone_on || earpiece_on || speaker_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->vx_main_mic_input, 1); + else + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->vx_main_mic_input, 0); + } + } +} + +static void select_input_device(struct imx_audio_device *adev) +{ + int i; + int headset_on = 0; + int main_mic_on = 0; + int sub_mic_on = 0; + int bt_on = adev->in_device & AUDIO_DEVICE_IN_ALL_SCO; + + if (!bt_on) { + if ((adev->mode != AUDIO_MODE_IN_CALL) && (adev->active_input != 0)) { + /* sub mic is used for camcorder or VoIP on speaker phone */ + sub_mic_on = (adev->active_input->source == AUDIO_SOURCE_CAMCORDER) || + ((adev->out_device & AUDIO_DEVICE_OUT_SPEAKER) && + (adev->active_input->source == AUDIO_SOURCE_VOICE_COMMUNICATION)); + } + + headset_on = adev->in_device & AUDIO_DEVICE_IN_WIRED_HEADSET; + main_mic_on = adev->in_device & AUDIO_DEVICE_IN_BUILTIN_MIC; + } + + /* TODO: check how capture is possible during voice calls or if + * both use cases are mutually exclusive. + */ + if (bt_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->mm_bt_mic_input, 1); + else { + /* Select front end */ + if (headset_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->mm_hs_mic_input, 1); + else if (main_mic_on || sub_mic_on) + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->mm_main_mic_input, 1); + else + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->mm_main_mic_input, 0); + } +} + +static int get_card_for_device(struct imx_audio_device *adev, int device, unsigned int flag) +{ + int i; + int card = -1; + + if (flag == PCM_OUT ) { + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) { + if(adev->card_list[i]->supported_out_devices & device) { + card = adev->card_list[i]->card; + break; + } + } + } else { + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) { + if(adev->card_list[i]->supported_in_devices & device) { + card = adev->card_list[i]->card; + break; + } + } + } + return card; +} +/* must be called with hw device and output stream mutexes locked */ +static int start_output_stream_primary(struct imx_stream_out *out) +{ + struct imx_audio_device *adev = out->dev; + unsigned int card = -1; + unsigned int port = 0; + int i; + int pcm_device; + bool success = false; + + ALOGI("start_output_stream... %d, device %d",(int)out, out->device); + + if (adev->mode != AUDIO_MODE_IN_CALL) { + /* FIXME: only works if only one output can be active at a time */ + select_output_device(adev); + } + + pcm_device = out->device & (AUDIO_DEVICE_OUT_ALL & ~AUDIO_DEVICE_OUT_AUX_DIGITAL); + if (pcm_device && (adev->active_output[OUTPUT_ESAI] == NULL || adev->active_output[OUTPUT_ESAI]->standby)) { + out->write_flags[PCM_NORMAL] = PCM_OUT | PCM_MMAP | PCM_MONOTONIC; + out->write_threshold[PCM_NORMAL] = PLAYBACK_LONG_PERIOD_COUNT * LONG_PERIOD_SIZE; + out->config[PCM_NORMAL] = pcm_config_mm_out; + + if (out->device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) { + int rate = 0; + int channels = 0; + + channels = adev_get_channels_for_device(adev, out->device, PCM_OUT); + if (channels == 2){ + out->config[PCM_NORMAL].channels = 2; + } else if (channels == 1) { + ALOGW("start_output_stream_primary, channels is 1 !!!"); + out->config[PCM_NORMAL].channels = 1; + } else { + ALOGE("can not get channels for out_device %d ", out->device); + return -EINVAL; + } + + rate = adev_get_rate_for_device(adev, out->device, PCM_OUT); + if( rate == 0) { + ALOGW("can not get rate for out_device %d ", out->device); + return -EINVAL; + } + out->config[PCM_NORMAL].rate = rate; + + + if(out->resampler[PCM_NORMAL]) { + int ret; + release_resampler(out->resampler[PCM_NORMAL]); + + //channels = 1 ? + ret = create_resampler(adev->default_rate, + rate, + channels, + RESAMPLER_QUALITY_DEFAULT, + NULL, + &out->resampler[PCM_NORMAL]); + if (ret != 0) { + ALOGE("can not create_resampler, inRate %d, outRate %d, channels %d, device 0x%x", + adev->default_rate, rate, channels, out->device); + return ret; + } + } + } + + card = get_card_for_device(adev, pcm_device, PCM_OUT); + out->pcm[PCM_NORMAL] = pcm_open(card, port,out->write_flags[PCM_NORMAL], &out->config[PCM_NORMAL]); + ALOGW("card %d, port %d device 0x%x", card, port, out->device); + ALOGW("rate %d, channel %d period_size 0x%x", out->config[PCM_NORMAL].rate, out->config[PCM_NORMAL].channels, out->config[PCM_NORMAL].period_size); + success = true; + } + + pcm_device = out->device & AUDIO_DEVICE_OUT_AUX_DIGITAL; + if(pcm_device && (adev->active_output[OUTPUT_HDMI] == NULL || adev->active_output[OUTPUT_HDMI]->standby)) { + out->write_flags[PCM_HDMI] = PCM_OUT | PCM_MONOTONIC; + out->write_threshold[PCM_HDMI] = HDMI_PERIOD_SIZE * PLAYBACK_HDMI_PERIOD_COUNT; + out->config[PCM_HDMI] = pcm_config_mm_out; + card = get_card_for_device(adev, pcm_device, PCM_OUT); + out->pcm[PCM_HDMI] = pcm_open(card, port,out->write_flags[PCM_HDMI], &out->config[PCM_HDMI]); + ALOGW("card %d, port %d device 0x%x", card, port, out->device); + ALOGW("rate %d, channel %d period_size 0x%x", out->config[PCM_HDMI].rate, out->config[PCM_HDMI].channels, out->config[PCM_HDMI].period_size); + success = true; + } + /* default to low power: will be corrected in out_write if necessary before first write to + * tinyalsa. + */ + out->low_power = 0; + /* Close any PCMs that could not be opened properly and return an error */ + for (i = 0; i < PCM_TOTAL; i++) { + if (out->pcm[i] && !pcm_is_ready(out->pcm[i])) { + ALOGE("cannot open pcm_out driver %d: %s", i, pcm_get_error(out->pcm[i])); + pcm_close(out->pcm[i]); + out->pcm[i] = NULL; + success = false; + } + } + + if (success) { + out->buffer_frames = pcm_config_mm_out.period_size * 2; + if (out->buffer == NULL) + out->buffer = malloc(out->buffer_frames * audio_stream_frame_size(&out->stream.common)); + + if (adev->echo_reference != NULL) + out->echo_reference = adev->echo_reference; + + for(i = 0; i < PCM_TOTAL; i++) { + if (out->resampler[i]) + out->resampler[i]->reset(out->resampler[i]); + } + + return 0; + } + + return -ENOMEM; +} + +static int start_output_stream_hdmi(struct imx_stream_out *out) +{ + struct imx_audio_device *adev = out->dev; + unsigned int card = -1; + unsigned int port = 0; + int i = 0; + + ALOGI("start_output_stream_hdmi, out %d, device 0x%x", (int)out, out->device); + /* force standby on low latency output stream to close HDMI driver in case it was in use */ + if (adev->active_output[OUTPUT_PRIMARY] != NULL && + !adev->active_output[OUTPUT_PRIMARY]->standby) { + struct imx_stream_out *p_out = adev->active_output[OUTPUT_PRIMARY]; + pthread_mutex_lock(&p_out->lock); + do_output_standby(p_out); + pthread_mutex_unlock(&p_out->lock); + } + + card = get_card_for_device(adev, out->device & AUDIO_DEVICE_OUT_AUX_DIGITAL, PCM_OUT); + ALOGW("card %d, port %d device 0x%x", card, port, out->device); + ALOGW("rate %d, channel %d period_size 0x%x", out->config[PCM_HDMI].rate, out->config[PCM_HDMI].channels, out->config[PCM_HDMI].period_size); + + out->pcm[PCM_HDMI] = pcm_open(card, port, PCM_OUT | PCM_MONOTONIC, &out->config[PCM_HDMI]); + + if (out->pcm[PCM_HDMI] && !pcm_is_ready(out->pcm[PCM_HDMI])) { + ALOGE("cannot open pcm_out driver: %s", pcm_get_error(out->pcm[PCM_HDMI])); + pcm_close(out->pcm[PCM_HDMI]); + out->pcm[PCM_HDMI] = NULL; + return -ENOMEM; + } + return 0; +} + +static int start_output_stream_esai(struct imx_stream_out *out) +{ + struct imx_audio_device *adev = out->dev; + unsigned int card = -1; + unsigned int port = 0; + int i = 0; + + /* force standby on low latency output stream to close HDMI driver in case it was in use */ + if (adev->active_output[OUTPUT_PRIMARY] != NULL && + !adev->active_output[OUTPUT_PRIMARY]->standby) { + struct imx_stream_out *p_out = adev->active_output[OUTPUT_PRIMARY]; + pthread_mutex_lock(&p_out->lock); + do_output_standby(p_out); + pthread_mutex_unlock(&p_out->lock); + } + + card = get_card_for_device(adev, out->device & AUDIO_DEVICE_OUT_SPEAKER, PCM_OUT); + ALOGW("card %d, port %d device 0x%x", card, port, out->device); + ALOGW("rate %d, channel %d period_size 0x%x", out->config[PCM_ESAI].rate, out->config[PCM_ESAI].channels, out->config[PCM_ESAI].period_size); + + out->pcm[PCM_ESAI] = pcm_open(card, port, PCM_OUT | PCM_MONOTONIC, &out->config[PCM_ESAI]); + + if (out->pcm[PCM_ESAI] && !pcm_is_ready(out->pcm[PCM_ESAI])) { + ALOGE("cannot open pcm_out driver: %s", pcm_get_error(out->pcm[PCM_ESAI])); + pcm_close(out->pcm[PCM_ESAI]); + out->pcm[PCM_ESAI] = NULL; + return -ENOMEM; + } + return 0; +} + +static int check_input_parameters(uint32_t sample_rate, int format, int channel_count) +{ + if (format != AUDIO_FORMAT_PCM_16_BIT) + return -EINVAL; + + if ((channel_count < 1) || (channel_count > 2)) + return -EINVAL; + + switch(sample_rate) { + case 8000: + case 11025: + case 16000: + case 22050: + case 24000: + case 32000: + case 44100: + case 48000: + break; + default: + return -EINVAL; + } + + return 0; +} + +static size_t get_input_buffer_size(uint32_t sample_rate, int format, int channel_count) +{ + size_t size; + size_t device_rate; + + if (check_input_parameters(sample_rate, format, channel_count) != 0) + return 0; + + /* take resampling into account and return the closest majoring + multiple of 16 frames, as audioflinger expects audio buffers to + be a multiple of 16 frames */ + size = (pcm_config_mm_in.period_size * sample_rate) / pcm_config_mm_in.rate; + size = ((size + 15) / 16) * 16; + + ALOGW("get_input_buffer_size size = %d, channel_count = %d",size,channel_count); + return size * channel_count * sizeof(short); +} + +static void add_echo_reference(struct imx_stream_out *out, + struct echo_reference_itfe *reference) +{ + pthread_mutex_lock(&out->lock); + out->echo_reference = reference; + pthread_mutex_unlock(&out->lock); +} + +static void remove_echo_reference(struct imx_stream_out *out, + struct echo_reference_itfe *reference) +{ + pthread_mutex_lock(&out->lock); + if (out->echo_reference == reference) { + /* stop writing to echo reference */ + reference->write(reference, NULL); + out->echo_reference = NULL; + } + pthread_mutex_unlock(&out->lock); +} + +static void put_echo_reference(struct imx_audio_device *adev, + struct echo_reference_itfe *reference) +{ + if (adev->echo_reference != NULL && + reference == adev->echo_reference) { + + if (adev->active_output[OUTPUT_PRIMARY] != NULL && + !adev->active_output[OUTPUT_PRIMARY]->standby ) + remove_echo_reference(adev->active_output[OUTPUT_PRIMARY], reference); + release_echo_reference(reference); + adev->echo_reference = NULL; + } +} + +static struct echo_reference_itfe *get_echo_reference(struct imx_audio_device *adev, + audio_format_t format, + uint32_t channel_count, + uint32_t sampling_rate) +{ + put_echo_reference(adev, adev->echo_reference); + /*only for mixer output, only one output*/ + if (adev->active_output[OUTPUT_PRIMARY] != NULL && + !adev->active_output[OUTPUT_PRIMARY]->standby){ + struct audio_stream *stream = &adev->active_output[OUTPUT_PRIMARY]->stream.common; + uint32_t wr_channel_count = popcount(stream->get_channels(stream)); + uint32_t wr_sampling_rate = stream->get_sample_rate(stream); + + int status = create_echo_reference(AUDIO_FORMAT_PCM_16_BIT, + channel_count, + sampling_rate, + AUDIO_FORMAT_PCM_16_BIT, + wr_channel_count, + wr_sampling_rate, + &adev->echo_reference); + if (status == 0) + add_echo_reference(adev->active_output[OUTPUT_PRIMARY], adev->echo_reference); + } + + return adev->echo_reference; +} + +static int get_playback_delay(struct imx_stream_out *out, + size_t frames, + struct echo_reference_buffer *buffer) +{ + size_t kernel_frames; + int status; + int primary_pcm = 0; + struct imx_audio_device *adev = out->dev; + + /* Find the first active PCM to act as primary */ + while ((primary_pcm < PCM_TOTAL) && !out->pcm[primary_pcm]) + primary_pcm++; + + status = pcm_get_htimestamp(out->pcm[primary_pcm], &kernel_frames, &buffer->time_stamp); + if (status < 0) { + buffer->time_stamp.tv_sec = 0; + buffer->time_stamp.tv_nsec = 0; + buffer->delay_ns = 0; + ALOGV("get_playback_delay(): pcm_get_htimestamp error," + "setting playbackTimestamp to 0"); + return status; + } + + kernel_frames = pcm_get_buffer_size(out->pcm[primary_pcm]) - kernel_frames; + + /* adjust render time stamp with delay added by current driver buffer. + * Add the duration of current frame as we want the render time of the last + * sample being written. */ + buffer->delay_ns = (long)(((int64_t)(kernel_frames + frames)* 1000000000)/ + adev->mm_rate); + + return 0; +} + +static uint32_t out_get_sample_rate(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + return pcm_config_mm_out.rate; +} + +static uint32_t out_get_sample_rate_hdmi(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + return out->config[PCM_HDMI].rate; +} + +static uint32_t out_get_sample_rate_esai(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + return out->config[PCM_ESAI].rate; +} + +static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + ALOGW("out_set_sample_rate %d", rate); + return 0; +} + +static size_t out_get_buffer_size_primary(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + + /* take resampling into account and return the closest majoring + multiple of 16 frames, as audioflinger expects audio buffers to + be a multiple of 16 frames */ + size_t size = (pcm_config_mm_out.period_size * adev->default_rate) / pcm_config_mm_out.rate; + size = ((size + 15) / 16) * 16; + return size * audio_stream_frame_size((struct audio_stream *)stream); +} + +static size_t out_get_buffer_size_hdmi(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + /* take resampling into account and return the closest majoring + multiple of 16 frames, as audioflinger expects audio buffers to + be a multiple of 16 frames */ + size_t size = pcm_config_hdmi_multi.period_size; + size = ((size + 15) / 16) * 16; + return size * audio_stream_frame_size((struct audio_stream *)stream); +} + +static size_t out_get_buffer_size_esai(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + /* take resampling into account and return the closest majoring + multiple of 16 frames, as audioflinger expects audio buffers to + be a multiple of 16 frames */ + size_t size = pcm_config_esai_multi.period_size; + size = ((size + 15) / 16) * 16; + return size * audio_stream_frame_size((struct audio_stream *)stream); +} + +static uint32_t out_get_channels(const struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + return out->channel_mask; +} + +static audio_format_t out_get_format(const struct audio_stream *stream) +{ + return AUDIO_FORMAT_PCM_16_BIT; +} + +static int out_set_format(struct audio_stream *stream, audio_format_t format) +{ + ALOGW("out_set_format %d", format); + return 0; +} + +/* must be called with hw device and output stream mutexes locked */ +static int do_output_standby(struct imx_stream_out *out) +{ + struct imx_audio_device *adev = out->dev; + int i; + + if (!out->standby) { + + for (i = 0; i < PCM_TOTAL; i++) { + if (out->pcm[i]) { + pcm_close(out->pcm[i]); + out->pcm[i] = NULL; + } + + out->writeContiFailCount[i] = 0; + } + + ALOGW("do_out_standby... %d",(int)out); + + /* if in call, don't turn off the output stage. This will + be done when the call is ended */ + if (adev->mode != AUDIO_MODE_IN_CALL) { + /* FIXME: only works if only one output can be active at a time */ + } + + /* stop writing to echo reference */ + if (out->echo_reference != NULL) { + out->echo_reference->write(out->echo_reference, NULL); + out->echo_reference = NULL; + } + + out->standby = 1; + } + return 0; +} + +static int out_standby(struct audio_stream *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + int status; + + pthread_mutex_lock(&out->dev->lock); + pthread_mutex_lock(&out->lock); + status = do_output_standby(out); + pthread_mutex_unlock(&out->lock); + pthread_mutex_unlock(&out->dev->lock); + return status; +} + +static int out_dump(const struct audio_stream *stream, int fd) +{ + return 0; +} + +static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + struct imx_stream_in *in; + struct str_parms *parms; + char *str; + char value[32]; + int ret, val = 0; + bool force_input_standby = false; + bool out_is_active = false; + int i; + + parms = str_parms_create_str(kvpairs); + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); + if (ret >= 0) { + val = atoi(value); + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&out->lock); + + if (adev->out_device != val) { + if (out == adev->active_output[OUTPUT_PRIMARY] && !out->standby) { + /* a change in output device may change the microphone selection */ + if (adev->active_input && + adev->active_input->source == AUDIO_SOURCE_VOICE_COMMUNICATION) { + force_input_standby = true; + } + /* force standby if moving to/from HDMI */ + if (((val & AUDIO_DEVICE_OUT_AUX_DIGITAL) ^ + (adev->out_device & AUDIO_DEVICE_OUT_AUX_DIGITAL)) || + ((val & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) ^ + (adev->out_device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET)) || + (adev->out_device & (AUDIO_DEVICE_OUT_AUX_DIGITAL | + AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET)) || + ((val & AUDIO_DEVICE_OUT_SPEAKER) ^ + (adev->out_device & AUDIO_DEVICE_OUT_SPEAKER)) || + (adev->mode == AUDIO_MODE_IN_CALL)) { + ALOGI("out_set_parameters, old 0x%x, new 0x%x do_output_standby", adev->out_device, val); + do_output_standby(out); + } + } + if ((out != adev->active_output[OUTPUT_HDMI]) && val) { + adev->out_device = val; + out->device = val; + if(out->device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) { + scan_available_device(adev, false, false, true); + } + + select_output_device(adev); + } + } + pthread_mutex_unlock(&out->lock); + if (force_input_standby) { + in = adev->active_input; + pthread_mutex_lock(&in->lock); + do_input_standby(in); + pthread_mutex_unlock(&in->lock); + } + pthread_mutex_unlock(&adev->lock); + + ret = 0; + } + + ALOGW("out_set_parameters %s, ret %d, out %d",kvpairs, ret, (int)out); + str_parms_destroy(parms); + + return ret; +} + +static char * out_get_parameters(const struct audio_stream *stream, const char *keys) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + struct str_parms *query = str_parms_create_str(keys); + char *str = NULL; + char value[256]; + struct str_parms *reply = str_parms_create(); + size_t i, j; + int ret; + bool first = true; + bool checked = false; + char temp[10]; + + ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_SUP_CHANNELS, value, sizeof(value)); + if (ret >= 0) { + value[0] = '\0'; + i = 0; + while (out->sup_channel_masks[i] != 0) { + for (j = 0; j < ARRAY_SIZE(out_channels_name_to_enum_table); j++) { + if (out_channels_name_to_enum_table[j].value == out->sup_channel_masks[i]) { + if (!first) { + strcat(value, "|"); + } + strcat(value, out_channels_name_to_enum_table[j].name); + first = false; + break; + } + } + i++; + } + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_CHANNELS, value); + str = strdup(str_parms_to_str(reply)); + checked = true; + } + + ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES, value, sizeof(value)); + if (ret >= 0) { + value[0] = '\0'; + i = 0; + while (out->sup_rates[i] != 0) { + if (!first) { + strcat(value, "|"); + } + sprintf(temp, "%d", out->sup_rates[i]); + strcat(value, temp); + first = false; + i++; + } + str_parms_add_str(reply, AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES, value); + str = strdup(str_parms_to_str(reply)); + checked = true; + } + + if (!checked) { + str = strdup(keys); + } + + ALOGW("out get parameters query %s, reply %s",str_parms_to_str(query), str_parms_to_str(reply)); + str_parms_destroy(query); + str_parms_destroy(reply); + return str; +} + +static uint32_t out_get_latency_primary(const struct audio_stream_out *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + return (pcm_config_mm_out.period_size * pcm_config_mm_out.period_count * 1000) / pcm_config_mm_out.rate; +} + +static uint32_t out_get_latency_hdmi(const struct audio_stream_out *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + return (pcm_config_hdmi_multi.period_size * pcm_config_hdmi_multi.period_count * 1000) / pcm_config_hdmi_multi.rate; +} + +static uint32_t out_get_latency_esai(const struct audio_stream_out *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + + return (pcm_config_esai_multi.period_size * pcm_config_esai_multi.period_count * 1000) / pcm_config_esai_multi.rate; +} + +static int out_set_volume(struct audio_stream_out *stream, float left, + float right) +{ + return -ENOSYS; +} + +static int pcm_read_convert(struct imx_stream_in *in, struct pcm *pcm, void *data, unsigned int count) +{ + bool bit_24b_2_16b = false; + bool mono2stereo = false; + bool stereo2mono = false; + size_t frames_rq = count / audio_stream_frame_size(&in->stream.common); + + if (in->config.format == PCM_FORMAT_S24_LE && in->requested_format == PCM_FORMAT_S16_LE) bit_24b_2_16b = true; + if (in->config.channels == 2 && in->requested_channel == 1) stereo2mono = true; + if (in->config.channels == 1 && in->requested_channel == 2) mono2stereo = true; + + if (bit_24b_2_16b || mono2stereo || stereo2mono) { + size_t size_in_bytes_tmp = pcm_frames_to_bytes(in->pcm, frames_rq); + if (in->read_tmp_buf_size < in->config.period_size) { + in->read_tmp_buf_size = in->config.period_size; + in->read_tmp_buf = (int32_t *) realloc(in->read_tmp_buf, size_in_bytes_tmp); + ALOG_ASSERT((in->read_tmp_buf != NULL), + "get_next_buffer() failed to reallocate read_tmp_buf"); + ALOGV("get_next_buffer(): read_tmp_buf %p extended to %d bytes", + in->read_tmp_buf, size_in_bytes_tmp); + } + + in->read_status = pcm_read_wrapper(pcm, (void*)in->read_tmp_buf, size_in_bytes_tmp); + + if (in->read_status != 0) { + ALOGE("get_next_buffer() pcm_read_wrapper error %d", in->read_status); + return in->read_status; + } + convert_record_data((void *)in->read_tmp_buf, (void *)data, frames_rq, bit_24b_2_16b, mono2stereo, stereo2mono); + } + else { + in->read_status = pcm_read_wrapper(pcm, (void*)data, count); + } + + return in->read_status; +} + +static int pcm_read_wrapper(struct pcm *pcm, const void * buffer, size_t bytes) +{ + int ret = 0; + ret = pcm_read(pcm, (void *)buffer, bytes); + + if(ret !=0) { + ALOGV("ret %d, pcm read %d error %s.", ret, bytes, pcm_get_error(pcm)); + + switch(pcm_state(pcm)) { + case PCM_STATE_SETUP: + case PCM_STATE_XRUN: + ret = pcm_prepare(pcm); + if(ret != 0) return ret; + break; + default: + return ret; + } + + ret = pcm_read(pcm, (void *)buffer, bytes); + } + + return ret; +} + +static int pcm_write_wrapper(struct pcm *pcm, const void * buffer, size_t bytes, int flags) +{ + int ret = 0; + if(flags & PCM_MMAP) + ret = pcm_mmap_write(pcm, (void *)buffer, bytes); + else + ret = pcm_write(pcm, (void *)buffer, bytes); + + if(ret !=0) { + ALOGW("ret %d, pcm write %d error %s", ret, bytes, pcm_get_error(pcm)); + + switch(pcm_state(pcm)) { + case PCM_STATE_SETUP: + case PCM_STATE_XRUN: + ret = pcm_prepare(pcm); + if(ret != 0) return ret; + break; + default: + return ret; + } + + if(flags & PCM_MMAP) + ret = pcm_mmap_write(pcm, (void *)buffer, bytes); + else + ret = pcm_write(pcm, (void *)buffer, bytes); + } + + return ret; +} + +static ssize_t out_write_primary(struct audio_stream_out *stream, const void* buffer, + size_t bytes) +{ + int ret; + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + size_t frame_size = audio_stream_frame_size(&out->stream.common); + size_t in_frames = bytes / frame_size; + size_t out_frames = in_frames; + bool force_input_standby = false; + struct imx_stream_in *in; + int i; + /* acquiring hw device mutex systematically is useful if a low priority thread is waiting + * on the output stream mutex - e.g. executing select_mode() while holding the hw device + * mutex + */ + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&out->lock); + if (out->standby) { + ret = start_output_stream_primary(out); + if (ret != 0) { + pthread_mutex_unlock(&adev->lock); + goto exit; + } + out->standby = 0; + /* a change in output device may change the microphone selection */ + if (adev->active_input && + adev->active_input->source == AUDIO_SOURCE_VOICE_COMMUNICATION) + force_input_standby = true; + } + pthread_mutex_unlock(&adev->lock); + + /* only use resampler if required */ + for (i = 0; i < PCM_TOTAL; i++) { + /* only use resampler if required */ + if (out->pcm[i] && out->resampler[i] && (out->config[i].rate != adev->default_rate)) { + out_frames = out->buffer_frames; + out->resampler[i]->resample_from_input(out->resampler[i], + (int16_t *)buffer, + &in_frames, + (int16_t *)out->buffer, + &out_frames); + break; + } + } + + if (out->echo_reference != NULL) { + struct echo_reference_buffer b; + b.raw = (void *)buffer; + b.frame_count = in_frames; + + get_playback_delay(out, out_frames, &b); + out->echo_reference->write(out->echo_reference, &b); + } + /* do not allow more than out->write_threshold frames in kernel pcm driver buffer */ + /* Write to all active PCMs */ + for (i = 0; i < PCM_TOTAL; i++) { + if (out->pcm[i]) { + if (out->config[i].rate == adev->default_rate) { + /* PCM uses native sample rate */ + ret = pcm_write_wrapper(out->pcm[i], (void *)buffer, bytes, out->write_flags[i]); + } else { + /* PCM needs resampler */ + ret = pcm_write_wrapper(out->pcm[i], (void *)out->buffer, out_frames * frame_size, out->write_flags[i]); + } + + if (ret) { + out->writeContiFailCount[i]++; + break; + } else { + out->writeContiFailCount[i] = 0; + } + } + } + + //If continue fail, probably th fd is invalid. + for (i = 0; i < PCM_TOTAL; i++) { + if(out->writeContiFailCount[i] > 100) { + ALOGW("pcm_write_wrapper continues failed for pcm %d, standby", i); + do_output_standby(out); + break; + } + } + +exit: + out->written += bytes / frame_size; + pthread_mutex_unlock(&out->lock); + + if (ret != 0) { + ALOGV("write error, sleep few ms"); + usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / + out_get_sample_rate(&stream->common)); + } + + if (force_input_standby) { + pthread_mutex_lock(&adev->lock); + if (adev->active_input) { + in = adev->active_input; + pthread_mutex_lock(&in->lock); + do_input_standby(in); + pthread_mutex_unlock(&in->lock); + } + pthread_mutex_unlock(&adev->lock); + } + return bytes; +} + +static ssize_t out_write_hdmi(struct audio_stream_out *stream, const void* buffer, + size_t bytes) +{ + int ret; + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + size_t frame_size = audio_stream_frame_size(&out->stream.common); + size_t in_frames = bytes / frame_size; + + /* acquiring hw device mutex systematically is useful if a low priority thread is waiting + * on the output stream mutex - e.g. executing select_mode() while holding the hw device + * mutex + */ + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&out->lock); + if (out->standby) { + ret = start_output_stream_hdmi(out); + if (ret != 0) { + pthread_mutex_unlock(&adev->lock); + goto exit; + } + out->standby = 0; + } + pthread_mutex_unlock(&adev->lock); + + /* do not allow more than out->write_threshold frames in kernel pcm driver buffer */ + + ret = pcm_write_wrapper(out->pcm[PCM_HDMI], (void *)buffer, bytes, out->write_flags[PCM_HDMI]); + +exit: + out->written += bytes / frame_size; + pthread_mutex_unlock(&out->lock); + + if (ret != 0) { + ALOGV("write error, sleep few ms"); + usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / + out_get_sample_rate(&stream->common)); + } + + return bytes; +} + +/******************************************************************************************************** +For esai, it will use the first channels/2 for Left channels, use second half channels for Right channels. +So we need to transform channel map in HAL for multichannel. +when input is "FL, FR, C, LFE, BL, BR, SL, SR", output is "FL,BL,C,FR,BR,LFE". +when input is "FL, FR, C, LFE, BL, BR, SL, SR", output is "FL,BL,C,SL,FR,BR,LFE,SR" +*********************************************************************************************************/ +static void convert_output_for_esai(const void* buffer, size_t bytes, int channels) +{ + short *data_src = (short *)buffer; + short *data_dst = (short *)buffer; + short dataFL,dataFR,dataC,dataLFE,dataBL,dataBR,dataSL,dataSR; + int i; + + if (channels == 6) { + for (i = 0; i < (int)bytes/(2*channels); i++ ) { + dataFL = *data_src++; + dataFR = *data_src++; + dataC = *data_src++; + dataLFE = *data_src++; + dataBL = *data_src++; + dataBR = *data_src++; + *data_dst++ = dataFL; + *data_dst++ = dataBL; + *data_dst++ = dataC; + *data_dst++ = dataFR; + *data_dst++ = dataBR; + *data_dst++ = dataLFE; + } + } + else if (channels == 8) { + for (i = 0; i < (int)bytes/(2*channels); i++ ) { + dataFL = *data_src++; + dataFR = *data_src++; + dataC = *data_src++; + dataLFE = *data_src++; + dataBL = *data_src++; + dataBR = *data_src++; + dataSL = *data_src++; + dataSR = *data_src++; + *data_dst++ = dataFL; + *data_dst++ = dataBL; + *data_dst++ = dataC; + *data_dst++ = dataSL; + *data_dst++ = dataFR; + *data_dst++ = dataBR; + *data_dst++ = dataLFE; + *data_dst++ = dataSR; + } + } +} + +static ssize_t out_write_esai(struct audio_stream_out *stream, const void* buffer, + size_t bytes) +{ + int ret; + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + size_t frame_size = audio_stream_frame_size(&out->stream.common); + size_t in_frames = bytes / frame_size; + + /* acquiring hw device mutex systematically is useful if a low priority thread is waiting + * on the output stream mutex - e.g. executing select_mode() while holding the hw device + * mutex + */ + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&out->lock); + if (out->standby) { + ret = start_output_stream_esai(out); + if (ret != 0) { + pthread_mutex_unlock(&adev->lock); + goto exit; + } + out->standby = 0; + } + pthread_mutex_unlock(&adev->lock); + + /* do not allow more than out->write_threshold frames in kernel pcm driver buffer */ + + convert_output_for_esai(buffer, bytes, out->config[PCM_ESAI].channels); + ret = pcm_write_wrapper(out->pcm[PCM_ESAI], (void *)buffer, bytes, out->write_flags[PCM_ESAI]); + +exit: + out->written += bytes / frame_size; + pthread_mutex_unlock(&out->lock); + + if (ret != 0) { + ALOGV("write error, sleep few ms"); + usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / + out_get_sample_rate(&stream->common)); + } + + return bytes; +} + +static int out_get_render_position(const struct audio_stream_out *stream, + uint32_t *dsp_frames) +{ + ALOGW("get render position...."); + return -EINVAL; +} + +static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) +{ + return 0; +} + +static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) +{ + return 0; +} + +static int out_get_presentation_position(const struct audio_stream_out *stream, + uint64_t *frames, struct timespec *timestamp) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *adev = out->dev; + int ret = -1; + int i; + + pthread_mutex_lock(&out->lock); + + for (i = 0; i < PCM_TOTAL; i++) + if (out->pcm[i]) { + size_t avail; + if (pcm_get_htimestamp(out->pcm[i], &avail, timestamp) == 0) { + size_t kernel_buffer_size = out->config[i].period_size * out->config[i].period_count; + /*Actually we have no case for adev->default_rate != out->config[i].rate */ + int64_t signed_frames = out->written - (kernel_buffer_size - avail) * adev->default_rate / out->config[i].rate; + + if (signed_frames >= 0) { + *frames = signed_frames; + ret = 0; + } + break; + } + } + + pthread_mutex_unlock(&out->lock); + + return ret; +} + +/** audio_stream_in implementation **/ +#define MID_RATE_0_8000 4000 +#define MID_RATE_8000_11025 ((8000+11025)/2) +#define MID_RATE_11025_16000 ((11025+16000)/2) +#define MID_RATE_16000_22050 ((16000+22050)/2) +#define MID_RATE_22050_32000 ((22050+32000)/2) +#define MID_RATE_32000_44100 ((32000+44100)/2) +#define MID_RATE_44100_48000 ((44100+48000)/2) +#define MID_RATE_48000_64000 ((48000+64000)/2) +#define MID_RATE_64000_88200 ((64000+88200)/2) +#define MID_RATE_88200_96000 ((88200+96000)/2) +#define MID_RATE_96000_176400 ((96000+176400)/2) +#define MID_RATE_176400_196000 ((176400+196000)/2) + +static int spdif_in_rate_check(struct imx_stream_in *in) +{ + struct imx_audio_device *adev = in->dev; + int i = adev->in_card_idx; + int ret = 0; + + if(!strcmp(adev->card_list[i]->driver_name, "imx-spdif")) { + struct mixer_ctl *ctl; + unsigned int rate = 0; + struct mixer *mixer_spdif = adev->mixer[i]; + ctl = mixer_get_ctl_by_name(mixer_spdif, "RX Sample Rate"); + if (ctl) { + rate = mixer_ctl_get_value(ctl, 0); + } + + if (rate <= MID_RATE_0_8000) + rate = 0; + else if (MID_RATE_0_8000 < rate && rate <= MID_RATE_8000_11025) + rate = 8000; + else if (MID_RATE_8000_11025 < rate && rate <= MID_RATE_11025_16000) + rate = 11025; + else if (MID_RATE_11025_16000 < rate && rate <= MID_RATE_16000_22050) + rate = 16000; + else if (MID_RATE_16000_22050 < rate && rate <= MID_RATE_22050_32000) + rate = 22050; + else if (MID_RATE_22050_32000 < rate && rate <= MID_RATE_32000_44100) + rate = 32000; + else if (MID_RATE_32000_44100 < rate && rate <= MID_RATE_44100_48000) + rate = 44100; + else if (MID_RATE_44100_48000 < rate && rate <= MID_RATE_48000_64000) + rate = 48000; + else if (MID_RATE_48000_64000 < rate && rate <= MID_RATE_64000_88200) + rate = 64000; + else if (MID_RATE_64000_88200 < rate && rate <= MID_RATE_88200_96000) + rate = 88200; + else if (MID_RATE_88200_96000 < rate && rate <= MID_RATE_96000_176400) + rate = 96000; + else if (MID_RATE_96000_176400 < rate && rate <= MID_RATE_176400_196000) + rate = 176400; + else + rate = 196000; + + if (rate > 0 && rate != in->config.rate) { + in->config.rate = rate; + ALOGW("spdif input rate changed to %d", rate); + + if (in->resampler) { + release_resampler(in->resampler); + } + + if (in->requested_rate != in->config.rate) { + in->buf_provider.get_next_buffer = get_next_buffer; + in->buf_provider.release_buffer = release_buffer; + + ret = create_resampler(in->config.rate, + in->requested_rate, + in->requested_channel, + RESAMPLER_QUALITY_DEFAULT, + &in->buf_provider, + &in->resampler); + } + + /* if no supported sample rate is available, use the resampler */ + if (in->resampler) { + in->resampler->reset(in->resampler); + } + } + } + + return 0; +} + +/* must be called with hw device and input stream mutexes locked */ +static int start_input_stream(struct imx_stream_in *in) +{ + int ret = 0; + int i; + struct imx_audio_device *adev = in->dev; + unsigned int card = -1; + unsigned int port = 0; + struct mixer *mixer; + int rate = 0, channels = 0, format = 0; + + ALOGW("start_input_stream...."); + + adev->active_input = in; + if (adev->mode != AUDIO_MODE_IN_CALL) { + adev->in_device = in->device; + select_input_device(adev); + } + + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) { + if(adev->in_device & adev->card_list[i]->supported_in_devices) { + card = adev->card_list[i]->card; + adev->in_card_idx = i; + port = 0; + break; + } + if(i == MAX_AUDIO_CARD_NUM-1) { + ALOGE("can not find supported device for %d",in->device); + return -EINVAL; + } + } + + /*Error handler for usb mic plug in/plug out when recording. */ + memcpy(&in->config, &pcm_config_mm_in, sizeof(pcm_config_mm_in)); + + in->config.stop_threshold = in->config.period_size * in->config.period_count; + + if ((in->device & AUDIO_DEVICE_IN_USB_DEVICE) || (in->device & AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET)) { + channels = adev_get_channels_for_device(adev, in->device, PCM_IN); + if (channels == 2){ + in->config.channels = 2; + } else if (channels == 1) { + in->config.channels = 1; + } else { + ALOGW("can not get channels for in_device %d ", in->device); + return -EINVAL; + } + + rate = adev_get_rate_for_device(adev, in->device, PCM_IN); + if( rate == 0) { + ALOGW("can not get rate for in_device %d ", in->device); + return -EINVAL; + } + in->config.rate = rate; + } else if (in->device & AUDIO_DEVICE_IN_AUX_DIGITAL) { + format = adev_get_format_for_device(adev, in->device, PCM_IN); + in->config.format = format; + } + + ALOGW("card %d, port %d device 0x%x", card, port, in->device); + ALOGW("rate %d, channel %d format %d, period_size 0x%x", in->config.rate, in->config.channels, + in->config.format, in->config.period_size); + + if (in->need_echo_reference && in->echo_reference == NULL) + in->echo_reference = get_echo_reference(adev, + AUDIO_FORMAT_PCM_16_BIT, + in->requested_channel, + in->requested_rate); + + /* this assumes routing is done previously */ + in->pcm = pcm_open(card, port, PCM_IN, &in->config); + if (!pcm_is_ready(in->pcm)) { + ALOGE("cannot open pcm_in driver: %s", pcm_get_error(in->pcm)); + pcm_close(in->pcm); + /*workaround for some usb camera (V-UBM46). the issue is that: + open camerarecorder,recording, suspend, resumed, recording. sometimes the audio input + will be failed to open. + Analysis: when resume, the usb will do some initialize in kernel, but if the user start + recording quickly, there will be some confliction which will cause the input open failed + and reopen also failed. + But if open and close the mixer here then the input will be opened successfully. + */ + if(!strcmp(adev->card_list[i]->driver_name, "USB-Audio")) { + mixer = mixer_open(card); + mixer_close(mixer); + } + adev->active_input = NULL; + return -ENOMEM; + } + + in->read_buf_frames = 0; + in->read_buf_size = 0; + in->proc_buf_frames = 0; + in->proc_buf_size = 0; + + if (in->resampler) { + release_resampler(in->resampler); + } + if (in->requested_rate != in->config.rate) { + in->buf_provider.get_next_buffer = get_next_buffer; + in->buf_provider.release_buffer = release_buffer; + + ret = create_resampler(in->config.rate, + in->requested_rate, + in->requested_channel, + RESAMPLER_QUALITY_DEFAULT, + &in->buf_provider, + &in->resampler); + } + + /* if no supported sample rate is available, use the resampler */ + if (in->resampler) { + in->resampler->reset(in->resampler); + } + return 0; +} + +static uint32_t in_get_sample_rate(const struct audio_stream *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + + return in->requested_rate; +} + +static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) +{ + return 0; +} + +static size_t in_get_buffer_size(const struct audio_stream *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + + return get_input_buffer_size(in->requested_rate, + AUDIO_FORMAT_PCM_16_BIT, + in->requested_channel); +} + +static uint32_t in_get_channels(const struct audio_stream *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + + if (in->requested_channel == 1) { + return AUDIO_CHANNEL_IN_MONO; + } else { + return AUDIO_CHANNEL_IN_STEREO; + } +} + +static audio_format_t in_get_format(const struct audio_stream *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + switch(in->requested_format) { + case PCM_FORMAT_S16_LE: + return AUDIO_FORMAT_PCM_16_BIT; + case PCM_FORMAT_S32_LE: + return AUDIO_FORMAT_PCM_32_BIT; + case PCM_FORMAT_S24_LE: + return AUDIO_FORMAT_PCM_8_24_BIT; + default: + return AUDIO_FORMAT_PCM_16_BIT; + } + +} + +static int in_set_format(struct audio_stream *stream, audio_format_t format) +{ + return 0; +} + +/* must be called with hw device and input stream mutexes locked */ +static int do_input_standby(struct imx_stream_in *in) +{ + struct imx_audio_device *adev = in->dev; + + if (!in->standby) { + ALOGW("do_in_standby.."); + pcm_close(in->pcm); + in->pcm = NULL; + in->last_time_of_xrun = 0; + + adev->active_input = 0; + if (adev->mode != AUDIO_MODE_IN_CALL) { + adev->in_device = AUDIO_DEVICE_NONE; + select_input_device(adev); + } + + if (in->echo_reference != NULL) { + /* stop reading from echo reference */ + in->echo_reference->read(in->echo_reference, NULL); + put_echo_reference(adev, in->echo_reference); + in->echo_reference = NULL; + } + + in->standby = 1; + } + return 0; +} + +static int in_standby(struct audio_stream *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + int status; + + pthread_mutex_lock(&in->dev->lock); + pthread_mutex_lock(&in->lock); + status = do_input_standby(in); + pthread_mutex_unlock(&in->lock); + pthread_mutex_unlock(&in->dev->lock); + return status; +} + +static int in_dump(const struct audio_stream *stream, int fd) +{ + return 0; +} + +static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + struct imx_audio_device *adev = in->dev; + struct str_parms *parms; + char *str; + char value[32]; + int ret, val = 0; + bool do_standby = false; + + parms = str_parms_create_str(kvpairs); + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_INPUT_SOURCE, value, sizeof(value)); + + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&in->lock); + if (ret >= 0) { + val = atoi(value); + /* no audio source uses val == 0 */ + if ((in->source != val) && (val != 0)) { + in->source = val; + do_standby = true; + } + } + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); + if (ret >= 0) { + val = atoi(value) & ~AUDIO_DEVICE_BIT_IN; + if ((in->device != val) && (val != 0)) { + in->device = val; + do_standby = true; + if(in->device & AUDIO_DEVICE_IN_USB_DEVICE) + scan_available_device(adev, false, true, false); + in_update_aux_channels(in, NULL); + } + } + + if (do_standby) + do_input_standby(in); + + pthread_mutex_unlock(&in->lock); + pthread_mutex_unlock(&adev->lock); + + ALOGW("in_set_parameters %s, ret %d", kvpairs, ret); + str_parms_destroy(parms); + return ret; +} + +static char * in_get_parameters(const struct audio_stream *stream, + const char *keys) +{ + return strdup(""); +} + +static int in_set_gain(struct audio_stream_in *stream, float gain) +{ + return 0; +} + +static void get_capture_delay(struct imx_stream_in *in, + size_t frames, + struct echo_reference_buffer *buffer) +{ + + /* read frames available in kernel driver buffer */ + size_t kernel_frames; + struct timespec tstamp; + long buf_delay; + long rsmp_delay; + long kernel_delay; + long delay_ns; + + if (pcm_get_htimestamp(in->pcm, &kernel_frames, &tstamp) < 0) { + buffer->time_stamp.tv_sec = 0; + buffer->time_stamp.tv_nsec = 0; + buffer->delay_ns = 0; + ALOGW("read get_capture_delay(): pcm_htimestamp error"); + return; + } + + /* read frames available in audio HAL input buffer + * add number of frames being read as we want the capture time of first sample + * in current buffer */ + buf_delay = (long)(((int64_t)(in->read_buf_frames) * 1000000000) / in->config.rate + + ((int64_t)(in->proc_buf_frames) * 1000000000) / + in->requested_rate); + + /* add delay introduced by resampler */ + rsmp_delay = 0; + if (in->resampler) { + rsmp_delay = in->resampler->delay_ns(in->resampler); + } + + kernel_delay = (long)(((int64_t)kernel_frames * 1000000000) / in->config.rate); + + delay_ns = kernel_delay + buf_delay + rsmp_delay; + + buffer->time_stamp = tstamp; + buffer->delay_ns = delay_ns; + ALOGV("get_capture_delay time_stamp = [%ld].[%ld], delay_ns: [%d]," + " kernel_delay:[%ld], buf_delay:[%ld], rsmp_delay:[%ld], kernel_frames:[%d], " + "in->read_buf_frames:[%d], in->proc_buf_frames:[%d], frames:[%d]", + buffer->time_stamp.tv_sec , buffer->time_stamp.tv_nsec, buffer->delay_ns, + kernel_delay, buf_delay, rsmp_delay, kernel_frames, + in->read_buf_frames, in->proc_buf_frames, frames); + +} + +static int32_t update_echo_reference(struct imx_stream_in *in, size_t frames) +{ + struct echo_reference_buffer b; + b.delay_ns = 0; + + ALOGV("update_echo_reference, frames = [%d], in->ref_frames_in = [%d], " + "b.frame_count = [%d]", + frames, in->ref_frames_in, frames - in->ref_frames_in); + if (in->ref_frames_in < frames) { + if (in->ref_buf_size < frames) { + in->ref_buf_size = frames; + in->ref_buf = (int16_t *)realloc(in->ref_buf, + in->ref_buf_size * + in->requested_channel * sizeof(int16_t)); + } + + b.frame_count = frames - in->ref_frames_in; + b.raw = (void *)(in->ref_buf + in->ref_frames_in * in->requested_channel); + + get_capture_delay(in, frames, &b); + + if (in->echo_reference->read(in->echo_reference, &b) == 0) + { + in->ref_frames_in += b.frame_count; + ALOGV("update_echo_reference: in->ref_frames_in:[%d], " + "in->ref_buf_size:[%d], frames:[%d], b.frame_count:[%d]", + in->ref_frames_in, in->ref_buf_size, frames, b.frame_count); + } + } else + ALOGV("update_echo_reference: NOT enough frames to read ref buffer"); + return b.delay_ns; +} + +static int set_preprocessor_param(effect_handle_t handle, + effect_param_t *param) +{ + uint32_t size = sizeof(int); + uint32_t psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + + param->vsize; + + int status = (*handle)->command(handle, + EFFECT_CMD_SET_PARAM, + sizeof (effect_param_t) + psize, + param, + &size, + ¶m->status); + if (status == 0) + status = param->status; + + return status; +} + +static int set_preprocessor_echo_delay(effect_handle_t handle, + int32_t delay_us) +{ + uint32_t buf[sizeof(effect_param_t) / sizeof(uint32_t) + 2]; + effect_param_t *param = (effect_param_t *)buf; + + param->psize = sizeof(uint32_t); + param->vsize = sizeof(uint32_t); + *(uint32_t *)param->data = AEC_PARAM_ECHO_DELAY; + *((int32_t *)param->data + 1) = delay_us; + + return set_preprocessor_param(handle, param); +} + +static void push_echo_reference(struct imx_stream_in *in, size_t frames) +{ + /* read frames from echo reference buffer and update echo delay + * in->ref_frames_in is updated with frames available in in->ref_buf */ + int32_t delay_us = update_echo_reference(in, frames)/1000; + int i; + audio_buffer_t buf; + + if (in->ref_frames_in < frames) + frames = in->ref_frames_in; + + buf.frameCount = frames; + buf.raw = in->ref_buf; + + for (i = 0; i < in->num_preprocessors; i++) { + if ((*in->preprocessors[i].effect_itfe)->process_reverse == NULL) + continue; + + (*in->preprocessors[i].effect_itfe)->process_reverse(in->preprocessors[i].effect_itfe, + &buf, + NULL); + set_preprocessor_echo_delay(in->preprocessors[i].effect_itfe, delay_us); + } + + in->ref_frames_in -= buf.frameCount; + if (in->ref_frames_in) { + memcpy(in->ref_buf, + in->ref_buf + buf.frameCount * in->requested_channel, + in->ref_frames_in * in->requested_channel * sizeof(int16_t)); + } +} + +static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer) +{ + struct imx_stream_in *in; + + if (buffer_provider == NULL || buffer == NULL) + return -EINVAL; + + in = (struct imx_stream_in *)((char *)buffer_provider - + offsetof(struct imx_stream_in, buf_provider)); + + if (in->pcm == NULL) { + buffer->raw = NULL; + buffer->frame_count = 0; + in->read_status = -ENODEV; + return -ENODEV; + } + + if (in->read_buf_frames == 0) { + size_t size_in_bytes = in->config.period_size * audio_stream_frame_size(&in->stream.common); + if (in->read_buf_size < in->config.period_size) { + in->read_buf_size = in->config.period_size; + in->read_buf = (int16_t *) realloc(in->read_buf, size_in_bytes); + ALOG_ASSERT((in->read_buf != NULL), + "get_next_buffer() failed to reallocate read_buf"); + ALOGV("get_next_buffer(): read_buf %p extended to %d bytes", + in->read_buf, size_in_bytes); + } + + in->read_status = pcm_read_convert(in, in->pcm, (void*)in->read_buf, size_in_bytes); + + if (in->read_status != 0) { + ALOGE("get_next_buffer() pcm_read_convert error %d", in->read_status); + buffer->raw = NULL; + buffer->frame_count = 0; + return in->read_status; + } + in->read_buf_frames = in->config.period_size; + } + + buffer->frame_count = (buffer->frame_count > in->read_buf_frames) ? + in->read_buf_frames : buffer->frame_count; + buffer->i16 = in->read_buf + (in->config.period_size - in->read_buf_frames) * + in->requested_channel; + + return in->read_status; + +} + +static void release_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer) +{ + struct imx_stream_in *in; + + if (buffer_provider == NULL || buffer == NULL) + return; + + in = (struct imx_stream_in *)((char *)buffer_provider - + offsetof(struct imx_stream_in, buf_provider)); + + in->read_buf_frames -= buffer->frame_count; +} + +/* read_frames() reads frames from kernel driver, down samples to capture rate + * if necessary and output the number of frames requested to the buffer specified */ +static ssize_t read_frames(struct imx_stream_in *in, void *buffer, ssize_t frames) +{ + ssize_t frames_wr = 0; + + while (frames_wr < frames) { + size_t frames_rd = frames - frames_wr; + if (in->resampler != NULL) { + in->resampler->resample_from_provider(in->resampler, + (int16_t *)((char *)buffer + + frames_wr * audio_stream_frame_size(&in->stream.common)), + &frames_rd); + } else { + struct resampler_buffer buf = { + { raw : NULL, }, + frame_count : frames_rd, + }; + get_next_buffer(&in->buf_provider, &buf); + if (buf.raw != NULL) { + memcpy((char *)buffer + + frames_wr * audio_stream_frame_size(&in->stream.common), + buf.raw, + buf.frame_count * audio_stream_frame_size(&in->stream.common)); + frames_rd = buf.frame_count; + } + release_buffer(&in->buf_provider, &buf); + } + /* in->read_status is updated by getNextBuffer() also called by + * in->resampler->resample_from_provider() */ + if (in->read_status != 0) + return in->read_status; + + frames_wr += frames_rd; + } + return frames_wr; +} + +/* process_frames() reads frames from kernel driver (via read_frames()), + * calls the active audio pre processings and output the number of frames requested + * to the buffer specified */ +static ssize_t process_frames(struct imx_stream_in *in, void* buffer, ssize_t frames) +{ + ssize_t frames_wr = 0; + audio_buffer_t in_buf; + audio_buffer_t out_buf; + int i; + bool has_aux_channels = (~in->main_channels & in->aux_channels); + void *proc_buf_out; + + if (has_aux_channels) + proc_buf_out = in->proc_buf_out; + else + proc_buf_out = buffer; + + /* since all the processing below is done in frames and using the config.channels + * as the number of channels, no changes is required in case aux_channels are present */ + while (frames_wr < frames) { + /* first reload enough frames at the end of process input buffer */ + if (in->proc_buf_frames < (size_t)frames) { + ssize_t frames_rd; + + if (in->proc_buf_size < (size_t)frames) { + in->proc_buf_size = (size_t)frames; + in->proc_buf_in = (int16_t *)realloc(in->proc_buf_in, + in->proc_buf_size * in->requested_channel * sizeof(int16_t)); + + ALOG_ASSERT((in->proc_buf_in != NULL), + "process_frames() failed to reallocate proc_buf_in"); + if (has_aux_channels) { + in->proc_buf_out = (int16_t *)realloc(in->proc_buf_out, in->proc_buf_size * in->requested_channel * sizeof(int16_t)); + ALOG_ASSERT((in->proc_buf_out != NULL), + "process_frames() failed to reallocate proc_buf_out"); + proc_buf_out = in->proc_buf_out; + } + ALOGV("process_frames(): proc_buf_in %p extended to %d bytes", + in->proc_buf_in,in->proc_buf_size * in->requested_channel * sizeof(int16_t)); + } + frames_rd = read_frames(in, + in->proc_buf_in + + in->proc_buf_frames * in->requested_channel, + frames - in->proc_buf_frames); + if (frames_rd < 0) { + frames_wr = frames_rd; + break; + } + in->proc_buf_frames += frames_rd; + } + + if (in->echo_reference != NULL) + push_echo_reference(in, in->proc_buf_frames); + + /* in_buf.frameCount and out_buf.frameCount indicate respectively + * the maximum number of frames to be consumed and produced by process() */ + in_buf.frameCount = in->proc_buf_frames; + in_buf.s16 = in->proc_buf_in; + out_buf.frameCount = frames - frames_wr; + out_buf.s16 = (int16_t *)proc_buf_out + frames_wr * in->requested_channel; + + /* FIXME: this works because of current pre processing library implementation that + * does the actual process only when the last enabled effect process is called. + * The generic solution is to have an output buffer for each effect and pass it as + * input to the next. + */ + for (i = 0; i < in->num_preprocessors; i++) { + (*in->preprocessors[i].effect_itfe)->process(in->preprocessors[i].effect_itfe, + &in_buf, + &out_buf); + } + + /* process() has updated the number of frames consumed and produced in + * in_buf.frameCount and out_buf.frameCount respectively + * move remaining frames to the beginning of in->proc_buf */ + in->proc_buf_frames -= in_buf.frameCount; + if (in->proc_buf_frames) { + memcpy(in->proc_buf_in, + in->proc_buf_in + in_buf.frameCount * in->requested_channel, + in->proc_buf_frames * in->requested_channel * sizeof(int16_t)); + } + + /* if not enough frames were passed to process(), read more and retry. */ + if (out_buf.frameCount == 0) { + ALOGV("No frames produced by preproc"); + continue; + } + + if ((frames_wr + (ssize_t)out_buf.frameCount) <= frames) { + frames_wr += out_buf.frameCount; + } else { + /* The effect does not comply to the API. In theory, we should never end up here! */ + ALOGE("preprocessing produced too many frames: %d + %d > %d !", + (unsigned int)frames_wr, out_buf.frameCount, (unsigned int)frames); + frames_wr = frames; + } + } + /* Remove aux_channels that have been added on top of main_channels + * Assumption is made that the channels are interleaved and that the main + * channels are first. */ + if (has_aux_channels) + { + size_t src_channels = in->config.channels; + size_t dst_channels = popcount(in->main_channels); + int16_t* src_buffer = (int16_t *)proc_buf_out; + int16_t* dst_buffer = (int16_t *)buffer; + + if (dst_channels == 1) { + for (i = frames_wr; i > 0; i--) + { + *dst_buffer++ = *src_buffer; + src_buffer += src_channels; + } + } else { + for (i = frames_wr; i > 0; i--) + { + memcpy(dst_buffer, src_buffer, dst_channels*sizeof(int16_t)); + dst_buffer += dst_channels; + src_buffer += src_channels; + } + } + } + + return frames_wr; +} + +static ssize_t in_read(struct audio_stream_in *stream, void* buffer, + size_t bytes) +{ + int ret = 0; + struct imx_stream_in *in = (struct imx_stream_in *)stream; + struct imx_audio_device *adev = in->dev; + size_t frames_rq = bytes / audio_stream_frame_size(&stream->common); + + /* acquiring hw device mutex systematically is useful if a low priority thread is waiting + * on the input stream mutex - e.g. executing select_mode() while holding the hw device + * mutex + */ + pthread_mutex_lock(&adev->lock); + pthread_mutex_lock(&in->lock); + if (in->standby) { + ret = start_input_stream(in); + if (ret == 0) { + in->standby = 0; + in->mute_500ms = in->requested_rate * audio_stream_frame_size(&stream->common)/2; + } + } + pthread_mutex_unlock(&adev->lock); + + if (ret < 0) + goto exit; + + spdif_in_rate_check(in); + + if (in->num_preprocessors != 0) + ret = process_frames(in, buffer, frames_rq); + else if (in->resampler != NULL) + ret = read_frames(in, buffer, frames_rq); + else + ret = pcm_read_convert(in, in->pcm, buffer, bytes); + + if(ret < 0) ALOGW("ret %d, pcm read error %s.", ret, pcm_get_error(in->pcm)); + + if (ret > 0) + ret = 0; + + if (ret == 0 && adev->mic_mute) + memset(buffer, 0, bytes); + + if (in->mute_500ms > 0) { + if(bytes <= in->mute_500ms) { + memset(buffer, 0, bytes); + in->mute_500ms = in->mute_500ms - bytes; + } else { + memset(buffer, 0, in->mute_500ms); + in->mute_500ms = 0; + } + } + +exit: + if (ret < 0) { + memset(buffer, 0, bytes); + usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / + in_get_sample_rate(&stream->common)); + } + pthread_mutex_unlock(&in->lock); + + return bytes; +} + +static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) +{ + int times, diff; + struct imx_stream_in *in = (struct imx_stream_in *)stream; + if (in->pcm == NULL) return 0; +#ifdef BRILLO + times = 0; +#else + times = pcm_get_time_of_xrun(in->pcm); +#endif + diff = times - in->last_time_of_xrun; + ALOGW_IF((diff != 0), "in_get_input_frames_lost %d ms total %d ms\n",diff, times); + in->last_time_of_xrun = times; + return diff * in->requested_rate / 1000; +} + +#define GET_COMMAND_STATUS(status, fct_status, cmd_status) \ + do { \ + if (fct_status != 0) \ + status = fct_status; \ + else if (cmd_status != 0) \ + status = cmd_status; \ + } while(0) + +static int in_configure_reverse(struct imx_stream_in *in) +{ + int32_t cmd_status; + uint32_t size = sizeof(int); + effect_config_t config; + int32_t status = 0; + int32_t fct_status = 0; + int i; + + if (in->num_preprocessors > 0) { + config.inputCfg.channels = in->main_channels; + config.outputCfg.channels = in->main_channels; + config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + config.inputCfg.samplingRate = in->requested_rate; + config.outputCfg.samplingRate = in->requested_rate; + config.inputCfg.mask = + ( EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT ); + config.outputCfg.mask = + ( EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT ); + + for (i = 0; i < in->num_preprocessors; i++) + { + if ((*in->preprocessors[i].effect_itfe)->process_reverse == NULL) + continue; + fct_status = (*(in->preprocessors[i].effect_itfe))->command( + in->preprocessors[i].effect_itfe, + EFFECT_CMD_SET_CONFIG_REVERSE, + sizeof(effect_config_t), + &config, + &size, + &cmd_status); + GET_COMMAND_STATUS(status, fct_status, cmd_status); + } + } + return status; +} + +#define MAX_NUM_CHANNEL_CONFIGS 10 + +static void in_read_audio_effect_channel_configs(struct imx_stream_in *in, + struct effect_info_s *effect_info) +{ + /* size and format of the cmd are defined in hardware/audio_effect.h */ + effect_handle_t effect = effect_info->effect_itfe; + uint32_t cmd_size = 2 * sizeof(uint32_t); + uint32_t cmd[] = { EFFECT_FEATURE_AUX_CHANNELS, MAX_NUM_CHANNEL_CONFIGS }; + /* reply = status + number of configs (n) + n x channel_config_t */ + uint32_t reply_size = + 2 * sizeof(uint32_t) + (MAX_NUM_CHANNEL_CONFIGS * sizeof(channel_config_t)); + int32_t reply[reply_size]; + int32_t cmd_status; + + ALOG_ASSERT((effect_info->num_channel_configs == 0), + "in_read_audio_effect_channel_configs() num_channel_configs not cleared"); + ALOG_ASSERT((effect_info->channel_configs == NULL), + "in_read_audio_effect_channel_configs() channel_configs not cleared"); + + /* if this command is not supported, then the effect is supposed to return -EINVAL. + * This error will be interpreted as if the effect supports the main_channels but does not + * support any aux_channels */ + cmd_status = (*effect)->command(effect, + EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS, + cmd_size, + (void*)&cmd, + &reply_size, + (void*)&reply); + + if (cmd_status != 0) { + ALOGV("in_read_audio_effect_channel_configs(): " + "fx->command returned %d", cmd_status); + return; + } + + if (reply[0] != 0) { + ALOGW("in_read_audio_effect_channel_configs(): " + "command EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS error %d num configs %d", + reply[0], (reply[0] == -ENOMEM) ? reply[1] : MAX_NUM_CHANNEL_CONFIGS); + return; + } + + /* the feature is not supported */ + ALOGV("in_read_audio_effect_channel_configs()(): " + "Feature supported and adding %d channel configs to the list", reply[1]); + effect_info->num_channel_configs = reply[1]; + effect_info->channel_configs = + (channel_config_t *) malloc(sizeof(channel_config_t) * reply[1]); /* n x configs */ + memcpy(effect_info->channel_configs, (reply + 2), sizeof(channel_config_t) * reply[1]); +} + + +static uint32_t in_get_aux_channels(struct imx_stream_in *in) +{ + int i; + channel_config_t new_chcfg = {0, 0}; + + if (in->num_preprocessors == 0) + return 0; + + /* do not enable dual mic configurations when capturing from other microphones than + * main or sub */ + if (!(in->device & (AUDIO_DEVICE_IN_BUILTIN_MIC | AUDIO_DEVICE_IN_BACK_MIC))) + return 0; + + /* retain most complex aux channels configuration compatible with requested main channels and + * supported by audio driver and all pre processors */ + for (i = 0; i < NUM_IN_AUX_CNL_CONFIGS; i++) { + channel_config_t *cur_chcfg = &in_aux_cnl_configs[i]; + if (cur_chcfg->main_channels == in->main_channels) { + size_t match_cnt; + size_t idx_preproc; + for (idx_preproc = 0, match_cnt = 0; + /* no need to continue if at least one preprocessor doesn't match */ + idx_preproc < (size_t)in->num_preprocessors && match_cnt == idx_preproc; + idx_preproc++) { + struct effect_info_s *effect_info = &in->preprocessors[idx_preproc]; + size_t idx_chcfg; + + for (idx_chcfg = 0; idx_chcfg < effect_info->num_channel_configs; idx_chcfg++) { + if (memcmp(effect_info->channel_configs + idx_chcfg, + cur_chcfg, + sizeof(channel_config_t)) == 0) { + match_cnt++; + break; + } + } + } + /* if all preprocessors match, we have a candidate */ + if (match_cnt == (size_t)in->num_preprocessors) { + /* retain most complex aux channels configuration */ + if (popcount(cur_chcfg->aux_channels) > popcount(new_chcfg.aux_channels)) { + new_chcfg = *cur_chcfg; + } + } + } + } + + ALOGV("in_get_aux_channels(): return %04x", new_chcfg.aux_channels); + + return new_chcfg.aux_channels; +} + +static int in_configure_effect_channels(effect_handle_t effect, + channel_config_t *channel_config) +{ + int status = 0; + int fct_status; + int32_t cmd_status; + uint32_t reply_size; + effect_config_t config; + uint32_t cmd[(sizeof(uint32_t) + sizeof(channel_config_t) - 1) / sizeof(uint32_t) + 1]; + + ALOGV("in_configure_effect_channels(): configure effect with channels: [%04x][%04x]", + channel_config->main_channels, + channel_config->aux_channels); + + config.inputCfg.mask = EFFECT_CONFIG_CHANNELS; + config.outputCfg.mask = EFFECT_CONFIG_CHANNELS; + reply_size = sizeof(effect_config_t); + fct_status = (*effect)->command(effect, + EFFECT_CMD_GET_CONFIG, + 0, + NULL, + &reply_size, + &config); + if (fct_status != 0) { + ALOGE("in_configure_effect_channels(): EFFECT_CMD_GET_CONFIG failed"); + return fct_status; + } + + config.inputCfg.channels = channel_config->main_channels | channel_config->aux_channels; + config.outputCfg.channels = config.inputCfg.channels; + reply_size = sizeof(uint32_t); + fct_status = (*effect)->command(effect, + EFFECT_CMD_SET_CONFIG, + sizeof(effect_config_t), + &config, + &reply_size, + &cmd_status); + GET_COMMAND_STATUS(status, fct_status, cmd_status); + + cmd[0] = EFFECT_FEATURE_AUX_CHANNELS; + memcpy(cmd + 1, channel_config, sizeof(channel_config_t)); + reply_size = sizeof(uint32_t); + fct_status = (*effect)->command(effect, + EFFECT_CMD_SET_FEATURE_CONFIG, + sizeof(cmd), //sizeof(uint32_t) + sizeof(channel_config_t), + cmd, + &reply_size, + &cmd_status); + GET_COMMAND_STATUS(status, fct_status, cmd_status); + + /* some implementations need to be re-enabled after a config change */ + reply_size = sizeof(uint32_t); + fct_status = (*effect)->command(effect, + EFFECT_CMD_ENABLE, + 0, + NULL, + &reply_size, + &cmd_status); + GET_COMMAND_STATUS(status, fct_status, cmd_status); + + return status; +} + +static int in_reconfigure_channels(struct imx_stream_in *in, + effect_handle_t effect, + channel_config_t *channel_config, + bool config_changed) { + + int status = 0; + + ALOGV("in_reconfigure_channels(): config_changed %d effect %p", + config_changed, effect); + + /* if config changed, reconfigure all previously added effects */ + if (config_changed) { + int i; + for (i = 0; i < in->num_preprocessors; i++) + { + int cur_status = in_configure_effect_channels(in->preprocessors[i].effect_itfe, + channel_config); + if (cur_status != 0) { + ALOGV("in_reconfigure_channels(): error %d configuring effect " + "%d with channels: [%04x][%04x]", + cur_status, + i, + channel_config->main_channels, + channel_config->aux_channels); + status = cur_status; + } + } + } else if (effect != NULL && channel_config->aux_channels) { + /* if aux channels config did not change but aux channels are present, + * we still need to configure the effect being added */ + status = in_configure_effect_channels(effect, channel_config); + } + return status; +} + +static void in_update_aux_channels(struct imx_stream_in *in, + effect_handle_t effect) +{ + uint32_t aux_channels; + channel_config_t channel_config; + int status; + + aux_channels = in_get_aux_channels(in); + + channel_config.main_channels = in->main_channels; + channel_config.aux_channels = aux_channels; + status = in_reconfigure_channels(in, + effect, + &channel_config, + (aux_channels != in->aux_channels)); + + if (status != 0) { + ALOGV("in_update_aux_channels(): in_reconfigure_channels error %d", status); + /* resetting aux channels configuration */ + aux_channels = 0; + channel_config.aux_channels = 0; + in_reconfigure_channels(in, effect, &channel_config, true); + } + if (in->aux_channels != aux_channels) { + ALOGV("aux_channels_changed "); + in->aux_channels_changed = true; + in->aux_channels = aux_channels; + do_input_standby(in); + } +} + +static int in_add_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + int status; + effect_descriptor_t desc; + + pthread_mutex_lock(&in->dev->lock); + pthread_mutex_lock(&in->lock); + if (in->num_preprocessors >= MAX_PREPROCESSORS) { + status = -ENOSYS; + goto exit; + } + + status = (*effect)->get_descriptor(effect, &desc); + if (status != 0) + goto exit; + + in->preprocessors[in->num_preprocessors].effect_itfe = effect; + /* add the supported channel of the effect in the channel_configs */ + in_read_audio_effect_channel_configs(in, &in->preprocessors[in->num_preprocessors]); + + in->num_preprocessors++; + + /* check compatibility between main channel supported and possible auxiliary channels */ + in_update_aux_channels(in, effect); + + ALOGV("in_add_audio_effect(), effect type: %08x", desc.type.timeLow); + + if (memcmp(&desc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) { + in->need_echo_reference = true; + do_input_standby(in); + in_configure_reverse(in); + } + +exit: + + ALOGW_IF(status != 0, "in_add_audio_effect() error %d", status); + pthread_mutex_unlock(&in->lock); + pthread_mutex_unlock(&in->dev->lock); + return status; +} + +static int in_remove_audio_effect(const struct audio_stream *stream, + effect_handle_t effect) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + int i; + int status = -EINVAL; + effect_descriptor_t desc; + + pthread_mutex_lock(&in->dev->lock); + pthread_mutex_lock(&in->lock); + if (in->num_preprocessors <= 0) { + status = -ENOSYS; + goto exit; + } + + for (i = 0; i < in->num_preprocessors; i++) { + if (status == 0) { /* status == 0 means an effect was removed from a previous slot */ + in->preprocessors[i - 1].effect_itfe = in->preprocessors[i].effect_itfe; + in->preprocessors[i - 1].channel_configs = in->preprocessors[i].channel_configs; + in->preprocessors[i - 1].num_channel_configs = in->preprocessors[i].num_channel_configs; + ALOGV("in_remove_audio_effect moving fx from %d to %d", i, i - 1); + continue; + } + if (in->preprocessors[i].effect_itfe == effect) { + ALOGV("in_remove_audio_effect found fx at index %d", i); + free(in->preprocessors[i].channel_configs); + status = 0; + } + } + + if (status != 0) + goto exit; + + in->num_preprocessors--; + /* if we remove one effect, at least the last preproc should be reset */ + in->preprocessors[in->num_preprocessors].num_channel_configs = 0; + in->preprocessors[in->num_preprocessors].effect_itfe = NULL; + in->preprocessors[in->num_preprocessors].channel_configs = NULL; + + + /* check compatibility between main channel supported and possible auxiliary channels */ + in_update_aux_channels(in, NULL); + + status = (*effect)->get_descriptor(effect, &desc); + if (status != 0) + goto exit; + + ALOGV("in_remove_audio_effect(), effect type: %08x", desc.type.timeLow); + + if (memcmp(&desc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) { + in->need_echo_reference = false; + do_input_standby(in); + } + +exit: + + ALOGW_IF(status != 0, "in_remove_audio_effect() error %d", status); + pthread_mutex_unlock(&in->lock); + pthread_mutex_unlock(&in->dev->lock); + return status; +} + +static int out_read_hdmi_channel_masks(struct imx_audio_device *adev, struct imx_stream_out *out) { + + int count = 0; + int sup_channels[MAX_SUP_CHANNEL_NUM]; //temp buffer for supported channels + int card = -1; + int i = 0; + int j = 0; + struct mixer *mixer_hdmi = NULL; + + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if(!strcmp(adev->card_list[i]->driver_name, hdmi_card.driver_name)) { + mixer_hdmi = adev->mixer[i]; + card = adev->card_list[i]->card; + break; + } + } + + if (mixer_hdmi) { + struct mixer_ctl *ctl; + ctl = mixer_get_ctl_by_name(mixer_hdmi, "HDMI Support Channels"); + if (ctl) { + count = mixer_ctl_get_num_values(ctl); + for(i = 0; i < count; i ++) { + sup_channels[i] = mixer_ctl_get_value(ctl, i); + ALOGW("out_read_hdmi_channel_masks() card %d got %d sup channels", card, sup_channels[i]); + } + } + } + + /*when channel is 6, the mask is 5.1,when channel is 8, the mask is 7.1*/ + for(i = 0; i < count; i++ ) { + if(sup_channels[i] == 2) { + out->sup_channel_masks[j] = AUDIO_CHANNEL_OUT_STEREO; + j++; + } + if(sup_channels[i] == 6) { + out->sup_channel_masks[j] = AUDIO_CHANNEL_OUT_5POINT1; + j++; + } + if(sup_channels[i] == 8) { + out->sup_channel_masks[j] = AUDIO_CHANNEL_OUT_7POINT1; + j++; + } + } + /*if HDMI device does not support 2,6,8 channels, then return error*/ + if (j == 0) return -ENOSYS; + + return 0; +} + +static int out_read_hdmi_rates(struct imx_audio_device *adev, struct imx_stream_out *out) { + + int count = 0; + int card = -1; + int i = 0; + struct mixer *mixer_hdmi = NULL; + + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if(!strcmp(adev->card_list[i]->driver_name, hdmi_card.driver_name)) { + mixer_hdmi = adev->mixer[i]; + card = adev->card_list[i]->card; + break; + } + } + + if (mixer_hdmi) { + struct mixer_ctl *ctl; + ctl = mixer_get_ctl_by_name(mixer_hdmi, "HDMI Support Rates"); + if (ctl) { + count = mixer_ctl_get_num_values(ctl); + for(i = 0; i < count; i ++) { + out->sup_rates[i] = mixer_ctl_get_value(ctl, i); + ALOGW("out_read_hdmi_rates() card %d got %d sup rates", card, out->sup_rates[i]); + } + } + } + + return 0; +} + +static int adev_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + audio_output_flags_t flags, + struct audio_config *config, + struct audio_stream_out **stream_out) +{ + struct imx_audio_device *ladev = (struct imx_audio_device *)dev; + struct imx_stream_out *out; + int ret; + int output_type; + int i; + + ALOGW("open output stream devices %d, format %d, channels %d, sample_rate %d, flag %d", + devices, config->format, config->channel_mask, config->sample_rate, flags); + + out = (struct imx_stream_out *)calloc(1, sizeof(struct imx_stream_out)); + if (!out) + return -ENOMEM; + + out->sup_rates[0] = ladev->mm_rate; + out->sup_channel_masks[0] = AUDIO_CHANNEL_OUT_STEREO; + out->channel_mask = AUDIO_CHANNEL_OUT_STEREO; + + if (flags & AUDIO_OUTPUT_FLAG_DIRECT && + devices == AUDIO_DEVICE_OUT_AUX_DIGITAL) { + ALOGW("adev_open_output_stream() HDMI multichannel"); + if (ladev->active_output[OUTPUT_HDMI] != NULL) { + ret = -ENOSYS; + goto err_open; + } + ret = out_read_hdmi_channel_masks(ladev, out); + if (ret != 0) + goto err_open; + + ret = out_read_hdmi_rates(ladev, out); + + output_type = OUTPUT_HDMI; + if (config->sample_rate == 0) + config->sample_rate = ladev->mm_rate; + if (config->channel_mask == 0) + config->channel_mask = AUDIO_CHANNEL_OUT_5POINT1; + out->channel_mask = config->channel_mask; + out->stream.common.get_buffer_size = out_get_buffer_size_hdmi; + out->stream.common.get_sample_rate = out_get_sample_rate_hdmi; + out->stream.get_latency = out_get_latency_hdmi; + out->stream.write = out_write_hdmi; + out->config[PCM_HDMI] = pcm_config_hdmi_multi; + out->config[PCM_HDMI].rate = config->sample_rate; + out->config[PCM_HDMI].channels = popcount(config->channel_mask); + } else if (flags & AUDIO_OUTPUT_FLAG_DIRECT && + devices == AUDIO_DEVICE_OUT_SPEAKER && ladev->device_is_auto) { + ALOGW("adev_open_output_stream() ESAI multichannel"); + if (ladev->active_output[OUTPUT_ESAI] != NULL) { + ret = -ENOSYS; + goto err_open; + } + + output_type = OUTPUT_ESAI; + if (config->sample_rate == 0) + config->sample_rate = ladev->mm_rate; + if (config->channel_mask == 0) + config->channel_mask = AUDIO_CHANNEL_OUT_5POINT1; + out->channel_mask = config->channel_mask; + out->stream.common.get_buffer_size = out_get_buffer_size_esai; + out->stream.common.get_sample_rate = out_get_sample_rate_esai; + out->stream.get_latency = out_get_latency_esai; + out->stream.write = out_write_esai; + out->config[PCM_ESAI] = pcm_config_esai_multi; + out->config[PCM_ESAI].rate = config->sample_rate; + out->config[PCM_ESAI].channels = popcount(config->channel_mask); + } else { + ALOGV("adev_open_output_stream() normal buffer"); + if (ladev->active_output[OUTPUT_PRIMARY] != NULL) { + ret = -ENOSYS; + goto err_open; + } + output_type = OUTPUT_PRIMARY; + out->stream.common.get_buffer_size = out_get_buffer_size_primary; + out->stream.common.get_sample_rate = out_get_sample_rate; + out->stream.get_latency = out_get_latency_primary; + out->stream.write = out_write_primary; + } + + + for(i = 0; i < PCM_TOTAL; i++) { + ret = create_resampler(ladev->default_rate, + ladev->mm_rate, + 2, + RESAMPLER_QUALITY_DEFAULT, + NULL, + &out->resampler[i]); + if (ret != 0) + goto err_open; + } + + out->stream.common.set_sample_rate = out_set_sample_rate; + out->stream.common.get_channels = out_get_channels; + out->stream.common.get_format = out_get_format; + out->stream.common.set_format = out_set_format; + out->stream.common.standby = out_standby; + out->stream.common.dump = out_dump; + out->stream.common.set_parameters = out_set_parameters; + out->stream.common.get_parameters = out_get_parameters; + out->stream.common.add_audio_effect = out_add_audio_effect; + out->stream.common.remove_audio_effect = out_remove_audio_effect; + out->stream.set_volume = out_set_volume; + out->stream.get_render_position = out_get_render_position; + out->stream.get_presentation_position = out_get_presentation_position; + + out->dev = ladev; + out->standby = 1; + out->device = devices; + + /* FIXME: when we support multiple output devices, we will want to + * do the following: + * adev->devices &= ~AUDIO_DEVICE_OUT_ALL; + * adev->devices |= out->device; + * select_output_device(adev); + * This is because out_set_parameters() with a route is not + * guaranteed to be called after an output stream is opened. */ + + config->format = out->stream.common.get_format(&out->stream.common); + config->channel_mask = out->stream.common.get_channels(&out->stream.common); + config->sample_rate = out->stream.common.get_sample_rate(&out->stream.common); + + *stream_out = &out->stream; + ladev->active_output[output_type] = out; + ALOGW("opened out stream...%d, type %d",(int)out, output_type); + return 0; + +err_open: + free(out); + *stream_out = NULL; + return ret; +} + +static void adev_close_output_stream(struct audio_hw_device *dev, + struct audio_stream_out *stream) +{ + struct imx_stream_out *out = (struct imx_stream_out *)stream; + struct imx_audio_device *ladev = (struct imx_audio_device *)dev; + int i; + ALOGW("adev_close_output_stream...%d",(int)out); + + out_standby(&stream->common); + + for (i = 0; i < OUTPUT_TOTAL; i++) { + if (ladev->active_output[i] == out) { + ladev->active_output[i] = NULL; + break; + } + } + + if (out->buffer) + free(out->buffer); + + for (i = 0; i < PCM_TOTAL; i++) { + if (out->resampler[i]) { + release_resampler(out->resampler[i]); + out->resampler[i] = NULL; + } + } + + free(stream); +} + +static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)dev; + struct str_parms *parms; + char *str; + char value[32]; + int ret; + ALOGW("set parameters %s",kvpairs); + parms = str_parms_create_str(kvpairs); + ret = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_TTY_MODE, value, sizeof(value)); + if (ret >= 0) { + int tty_mode; + + if (strcmp(value, AUDIO_PARAMETER_VALUE_TTY_OFF) == 0) + tty_mode = TTY_MODE_OFF; + else if (strcmp(value, AUDIO_PARAMETER_VALUE_TTY_VCO) == 0) + tty_mode = TTY_MODE_VCO; + else if (strcmp(value, AUDIO_PARAMETER_VALUE_TTY_HCO) == 0) + tty_mode = TTY_MODE_HCO; + else if (strcmp(value, AUDIO_PARAMETER_VALUE_TTY_FULL) == 0) + tty_mode = TTY_MODE_FULL; + else + return -EINVAL; + + pthread_mutex_lock(&adev->lock); + if (tty_mode != adev->tty_mode) { + adev->tty_mode = tty_mode; + if (adev->mode == AUDIO_MODE_IN_CALL) + select_output_device(adev); + } + pthread_mutex_unlock(&adev->lock); + } + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_BT_NREC, value, sizeof(value)); + if (ret >= 0) { + if (strcmp(value, AUDIO_PARAMETER_VALUE_ON) == 0) + adev->bluetooth_nrec = true; + else + adev->bluetooth_nrec = false; + } + + ret = str_parms_get_str(parms, "screen_state", value, sizeof(value)); + if (ret >= 0) { + if (strcmp(value, AUDIO_PARAMETER_VALUE_ON) == 0) + adev->low_power = false; + else + adev->low_power = true; + } + + str_parms_destroy(parms); + return ret; +} + +static char * adev_get_parameters(const struct audio_hw_device *dev, + const char *keys) +{ + return strdup(""); +} + +static int adev_init_check(const struct audio_hw_device *dev) +{ + return 0; +} + +static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)dev; + + adev->voice_volume = volume; + + return 0; +} + +static int adev_set_master_volume(struct audio_hw_device *dev, float volume) +{ + return -ENOSYS; +} + +static int adev_set_mode(struct audio_hw_device *dev, int mode) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)dev; + pthread_mutex_lock(&adev->lock); + if (adev->mode != mode) { + adev->mode = mode; + select_mode(adev); + } + pthread_mutex_unlock(&adev->lock); + + return 0; +} + +static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)dev; + + adev->mic_mute = state; + + return 0; +} + +static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)dev; + + *state = adev->mic_mute; + + return 0; +} + +static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, + const struct audio_config *config) +{ + size_t size; + int channel_count = popcount(config->channel_mask); + if (check_input_parameters(config->sample_rate, config->format, channel_count) != 0) + return 0; + + return get_input_buffer_size(config->sample_rate, config->format, channel_count); +} + +static int adev_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, + audio_devices_t devices, + struct audio_config *config, + struct audio_stream_in **stream_in) +{ + struct imx_audio_device *ladev = (struct imx_audio_device *)dev; + struct imx_stream_in *in; + int ret; + int rate, channels; + int channel_count = popcount(config->channel_mask); + + if (check_input_parameters(config->sample_rate, config->format, channel_count) != 0) + return -EINVAL; + + in = (struct imx_stream_in *)calloc(1, sizeof(struct imx_stream_in)); + if (!in) + return -ENOMEM; + + in->stream.common.get_sample_rate = in_get_sample_rate; + in->stream.common.set_sample_rate = in_set_sample_rate; + in->stream.common.get_buffer_size = in_get_buffer_size; + in->stream.common.get_channels = in_get_channels; + in->stream.common.get_format = in_get_format; + in->stream.common.set_format = in_set_format; + in->stream.common.standby = in_standby; + in->stream.common.dump = in_dump; + in->stream.common.set_parameters = in_set_parameters; + in->stream.common.get_parameters = in_get_parameters; + in->stream.common.add_audio_effect = in_add_audio_effect; + in->stream.common.remove_audio_effect = in_remove_audio_effect; + in->stream.set_gain = in_set_gain; + in->stream.read = in_read; + in->stream.get_input_frames_lost = in_get_input_frames_lost; + + in->requested_rate = config->sample_rate; + in->requested_format = PCM_FORMAT_S16_LE; + in->requested_channel = channel_count; + in->device = devices & ~AUDIO_DEVICE_BIT_IN; + + ALOGW("In channels %d, rate %d, devices 0x%x", channel_count, config->sample_rate, devices); + memcpy(&in->config, &pcm_config_mm_in, sizeof(pcm_config_mm_in)); + //in->config.channels = channel_count; + //in->config.rate = *sample_rate; + /*fix to 2 channel, caused by the wm8958 driver*/ + + in->main_channels = config->channel_mask; + + if (in->device & AUDIO_DEVICE_IN_USB_DEVICE) { + scan_available_device(ladev, true, true, false); + } + + in->dev = ladev; + in->standby = 1; + + *stream_in = &in->stream; + + return 0; + +err: + if (in->resampler) + release_resampler(in->resampler); + + free(in); + *stream_in = NULL; + + return ret; +} + +static void adev_close_input_stream(struct audio_hw_device *dev, + struct audio_stream_in *stream) +{ + struct imx_stream_in *in = (struct imx_stream_in *)stream; + + in_standby(&stream->common); + + if (in->read_buf) + free(in->read_buf); + + if (in->resampler) { + release_resampler(in->resampler); + } + if (in->proc_buf_in) + free(in->proc_buf_in); + if (in->proc_buf_out) + free(in->proc_buf_out); + if (in->ref_buf) + free(in->ref_buf); + + free(stream); + return; +} + +static int adev_dump(const audio_hw_device_t *device, int fd) +{ + return 0; +} + +static int adev_close(hw_device_t *device) +{ + struct imx_audio_device *adev = (struct imx_audio_device *)device; + int i; + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + if(adev->mixer[i]) + mixer_close(adev->mixer[i]); + + free(device); + return 0; +} + +static int adev_get_rate_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag) +{ + int i; + if (flag == PCM_OUT) { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_out_devices & devices) + return adev->card_list[i]->out_rate; + } + } else { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_in_devices & devices) + return adev->card_list[i]->in_rate; + } + } + return 0; +} + +static int adev_get_channels_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag) +{ + int i; + if (flag == PCM_OUT) { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_out_devices & devices) + return adev->card_list[i]->out_channels; + } + } else { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_in_devices & devices) + return adev->card_list[i]->in_channels; + } + } + return 0; +} + +static int adev_get_format_for_device(struct imx_audio_device *adev, uint32_t devices, unsigned int flag) +{ + int i; + if (flag == PCM_OUT) { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_out_devices & devices) + return adev->card_list[i]->out_format; + } + } else { + for (i = 0; i < MAX_AUDIO_CARD_NUM; i ++) { + if (adev->card_list[i]->supported_in_devices & devices) + return adev->card_list[i]->in_format; + } + } + return 0; +} + +static int pcm_get_near_param_wrap(unsigned int card, unsigned int device, + unsigned int flags, int type, int *data) +{ +#ifdef BRILLO + return 0; +#else + return pcm_get_near_param(card, device, flags, type, data); +#endif +} + +static int scan_available_device(struct imx_audio_device *adev, bool rescanusb, bool queryInput, bool queryOutput) +{ + int i,j,k; + int m,n; + bool found; + bool scanned; + struct control *imx_control; + int left_out_devices = SUPPORTED_DEVICE_OUT_MODULE; + int left_in_devices = SUPPORTED_DEVICE_IN_MODULE; + int rate, channels, format; + /* open the mixer for main sound card, main sound cara is like sgtl5000, wm8958, cs428888*/ + /* note: some platform do not have main sound card, only have auxiliary card.*/ + /* max num of supported card is 2 */ + k = adev->audio_card_num; + for(i = 0; i < k; i++) { + left_out_devices &= ~adev->card_list[i]->supported_out_devices; + left_in_devices &= ~adev->card_list[i]->supported_in_devices; + } + + for (i = 0; i < MAX_AUDIO_CARD_SCAN ; i ++) { + found = false; + imx_control = control_open(i); + if(!imx_control) + break; + ALOGW("card %d, id %s ,driver %s, name %s", i, control_card_info_get_id(imx_control), + control_card_info_get_driver(imx_control), + control_card_info_get_name(imx_control)); + for(j = 0; j < SUPPORT_CARD_NUM; j++) { + if(strstr(control_card_info_get_driver(imx_control), audio_card_list[j]->driver_name) != NULL){ + // check if the device have been scaned before + scanned = false; + n = k; + for (m = 0; m < k; m++) { + if (!strcmp(audio_card_list[j]->driver_name, adev->card_list[m]->driver_name)) { + scanned = true; + found = true; + if(!strcmp(adev->card_list[m]->driver_name, "USB-Audio")) { + if(strcmp(control_card_info_get_name(imx_control), adev->usb_card_name) || rescanusb) { + scanned = false; + strcpy(adev->usb_card_name, control_card_info_get_name(imx_control)); + left_out_devices |= adev->card_list[m]->supported_out_devices; + left_in_devices |= adev->card_list[m]->supported_in_devices; + if(adev->mixer[m]) + mixer_close(adev->mixer[m]); + n = m; + k --; + } + } + } + } + if (scanned) break; + if(n >= MAX_AUDIO_CARD_NUM) { + break; + } + adev->card_list[n] = audio_card_list[j]; + adev->card_list[n]->card = i; + if(!strcmp(adev->card_list[n]->driver_name, "USB-Audio")) { + adev->mixer[n] = NULL; + } else { + adev->mixer[n] = mixer_open(i); + if (!adev->mixer[n]) { + ALOGE("Unable to open the mixer, aborting."); + return -EINVAL; + } + } + + if(queryOutput) { + rate = 44100; + if( pcm_get_near_param_wrap(i, 0, PCM_OUT, PCM_HW_PARAM_RATE, &rate) == 0) + adev->card_list[n]->out_rate = rate; + ALOGW("out rate %d",adev->card_list[n]->out_rate); + + if(adev->card_list[n]->out_rate > adev->mm_rate) + adev->mm_rate = adev->card_list[n]->out_rate; + + channels = 2; + if( pcm_get_near_param_wrap(i, 0, PCM_OUT, PCM_HW_PARAM_CHANNELS, &channels) == 0) + adev->card_list[n]->out_channels = channels; + } + + if(queryInput) { + rate = 44100; + if( pcm_get_near_param_wrap(i, 0, PCM_IN, PCM_HW_PARAM_RATE, &rate) == 0) + adev->card_list[n]->in_rate = rate; + + channels = 1; + if( pcm_get_near_param_wrap(i, 0, PCM_IN, PCM_HW_PARAM_CHANNELS, &channels) == 0) + adev->card_list[n]->in_channels = channels; + + format = PCM_FORMAT_S16_LE; + +#ifdef BRILLO + adev->card_list[n]->in_format = format; +#else + if( pcm_check_param_mask(i, 0, PCM_IN, PCM_HW_PARAM_FORMAT, format)) + adev->card_list[n]->in_format = format; + else { + format = PCM_FORMAT_S24_LE; + if( pcm_check_param_mask(i, 0, PCM_IN, PCM_HW_PARAM_FORMAT, format)) + adev->card_list[n]->in_format = format; + } +#endif + + ALOGW("in rate %d, channels %d format %d",adev->card_list[n]->in_rate, adev->card_list[n]->in_channels, adev->card_list[n]->in_format); + } + + left_out_devices &= ~audio_card_list[j]->supported_out_devices; + left_in_devices &= ~audio_card_list[j]->supported_in_devices; + k ++; + found = true; + break; + } + } + + control_close(imx_control); + if(!found){ + ALOGW("unrecognized card found."); + } + } + adev->audio_card_num = k; + /*must have one card*/ + if(!adev->card_list[0]) { + ALOGE("no supported sound card found, aborting."); + return -EINVAL; + } + /*second card maybe null*/ + while (k < MAX_AUDIO_CARD_NUM) { + adev->card_list[k] = audio_card_list[SUPPORT_CARD_NUM-1]; + /*FIXME:This is workaround for some board which only have one card, whose supported device only is not full*/ + adev->card_list[k]->supported_out_devices = left_out_devices; + adev->card_list[k]->supported_in_devices = left_in_devices; + k++; + } + + return 0; +} + +static int adev_open(const hw_module_t* module, const char* name, + hw_device_t** device) +{ + struct imx_audio_device *adev; + int ret = 0; + int i,j,k; + bool found; + + if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) + return -EINVAL; + + adev = calloc(1, sizeof(struct imx_audio_device)); + if (!adev) + return -ENOMEM; + + adev->hw_device.common.tag = HARDWARE_DEVICE_TAG; + adev->hw_device.common.version = AUDIO_DEVICE_API_VERSION_2_0; + adev->hw_device.common.module = (struct hw_module_t *) module; + adev->hw_device.common.close = adev_close; + + adev->hw_device.init_check = adev_init_check; + adev->hw_device.set_voice_volume = adev_set_voice_volume; + adev->hw_device.set_master_volume = adev_set_master_volume; + adev->hw_device.set_mode = adev_set_mode; + adev->hw_device.set_mic_mute = adev_set_mic_mute; + adev->hw_device.get_mic_mute = adev_get_mic_mute; + adev->hw_device.set_parameters = adev_set_parameters; + adev->hw_device.get_parameters = adev_get_parameters; + adev->hw_device.get_input_buffer_size = adev_get_input_buffer_size; + adev->hw_device.open_output_stream = adev_open_output_stream; + adev->hw_device.close_output_stream = adev_close_output_stream; + adev->hw_device.open_input_stream = adev_open_input_stream; + adev->hw_device.close_input_stream = adev_close_input_stream; + adev->hw_device.dump = adev_dump; + adev->mm_rate = 44100; + + ret = scan_available_device(adev, true, true, true); + if (ret != 0) { + free(adev); + return ret; + } + + adev->default_rate = adev->mm_rate; + pcm_config_mm_out.rate = adev->mm_rate; + pcm_config_mm_in.rate = adev->mm_rate; + pcm_config_hdmi_multi.rate = adev->mm_rate; + pcm_config_esai_multi.rate = adev->mm_rate; + + /* Set the default route before the PCM stream is opened */ + pthread_mutex_lock(&adev->lock); + for(i = 0; i < MAX_AUDIO_CARD_NUM; i++) + set_route_by_array(adev->mixer[i], adev->card_list[i]->defaults, 1); + adev->mode = AUDIO_MODE_NORMAL; + adev->out_device = AUDIO_DEVICE_OUT_SPEAKER; + adev->in_device = AUDIO_DEVICE_IN_BUILTIN_MIC & ~AUDIO_DEVICE_BIT_IN; + select_output_device(adev); + + adev->pcm_modem_dl = NULL; + adev->pcm_modem_ul = NULL; + adev->voice_volume = 1.0f; + adev->tty_mode = TTY_MODE_OFF; + adev->device_is_auto = is_device_auto(); + adev->bluetooth_nrec = true; + adev->wb_amr = 0; + pthread_mutex_unlock(&adev->lock); + + *device = &adev->hw_device.common; + + return 0; +} + +static struct hw_module_methods_t hal_module_methods = { + .open = adev_open, +}; + +struct audio_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = AUDIO_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = AUDIO_HARDWARE_MODULE_ID, + .name = "Freescale i.MX Audio HW HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; + + |