From c43676b7da7b0de9c14180cf351c75e636b09de3 Mon Sep 17 00:00:00 2001 From: Patrick Lai Date: Thu, 17 Jan 2013 14:30:07 -0800 Subject: [PATCH] sound: Add MSM sound drivers Signed-off-by: Patrick Lai Signed-off-by: Stephen Boyd --- include/sound/apr_audio-v2.h | 6172 +++++++++++++ include/sound/apr_audio.h | 1538 ++++ include/sound/cs8427.h | 27 +- include/sound/dai.h | 49 + include/sound/msm-dai-q6-v2.h | 42 + include/sound/msm-dai-q6.h | 45 + include/sound/omap-abe-dsp.h | 19 + include/sound/q6adm-v2.h | 50 + include/sound/q6adm.h | 49 + include/sound/q6afe-v2.h | 107 + include/sound/q6afe.h | 111 + include/sound/q6asm-v2.h | 303 + include/sound/q6asm.h | 320 + include/sound/q6audio-v2.h | 26 + include/uapi/sound/Kbuild | 1 + sound/core/Kconfig | 1 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 18 +- sound/soc/codecs/Makefile | 9 + sound/soc/codecs/cs8427.c | 904 ++ sound/soc/codecs/msm_stub.c | 80 + sound/soc/codecs/timpani.c | 482 + sound/soc/codecs/timpani.h | 15 + sound/soc/codecs/wcd9304-tables.c | 722 ++ sound/soc/codecs/wcd9304.c | 5013 +++++++++++ sound/soc/codecs/wcd9304.h | 251 + sound/soc/codecs/wcd9310-tables.c | 1114 +++ sound/soc/codecs/wcd9310.c | 7792 +++++++++++++++++ sound/soc/codecs/wcd9310.h | 253 + sound/soc/codecs/wm_hubs.c | 6 + sound/soc/msm/Kconfig | 158 + sound/soc/msm/Makefile | 86 + sound/soc/msm/apq8064.c | 2031 +++++ sound/soc/msm/lpass-dma.c | 488 ++ sound/soc/msm/lpass-i2s.c | 139 + sound/soc/msm/lpass-pcm.c | 369 + sound/soc/msm/lpass-pcm.h | 45 + sound/soc/msm/mdm9615.c | 2203 +++++ sound/soc/msm/mpq8064.c | 1470 ++++ sound/soc/msm/msm-compr-q6.c | 753 ++ sound/soc/msm/msm-compr-q6.h | 36 + sound/soc/msm/msm-dai-fe.c | 412 + sound/soc/msm/msm-dai-q6-hdmi.c | 326 + sound/soc/msm/msm-dai-q6.c | 2024 +++++ sound/soc/msm/msm-dai-stub.c | 102 + sound/soc/msm/msm-dai.c | 150 + sound/soc/msm/msm-multi-ch-pcm-q6.c | 804 ++ sound/soc/msm/msm-mvs.c | 936 ++ sound/soc/msm/msm-pcm-afe.c | 621 ++ sound/soc/msm/msm-pcm-afe.h | 46 + sound/soc/msm/msm-pcm-hostless.c | 61 + sound/soc/msm/msm-pcm-lpa.c | 610 ++ sound/soc/msm/msm-pcm-q6.c | 740 ++ sound/soc/msm/msm-pcm-q6.h | 83 + sound/soc/msm/msm-pcm-routing.c | 2478 ++++++ sound/soc/msm/msm-pcm-routing.h | 124 + sound/soc/msm/msm-pcm-voice.c | 558 ++ sound/soc/msm/msm-pcm-voice.h | 37 + sound/soc/msm/msm-pcm-voip.c | 1169 +++ sound/soc/msm/msm-pcm.c | 646 ++ sound/soc/msm/msm-pcm.h | 204 + sound/soc/msm/msm-voip.c | 610 ++ sound/soc/msm/msm7201.c | 424 + sound/soc/msm/msm7k-pcm.c | 699 ++ sound/soc/msm/msm7kv2-dai.c | 149 + sound/soc/msm/msm7kv2-dsp.c | 633 ++ sound/soc/msm/msm7kv2-pcm.c | 774 ++ sound/soc/msm/msm7kv2-pcm.h | 207 + sound/soc/msm/msm7x30.c | 1004 +++ sound/soc/msm/msm8660-apq-wm8903.c | 725 ++ sound/soc/msm/msm8660.c | 342 + sound/soc/msm/msm8930.c | 1274 +++ sound/soc/msm/msm8960.c | 1740 ++++ sound/soc/msm/msm8974.c | 752 ++ sound/soc/msm/msm8x60-dai.c | 148 + sound/soc/msm/msm8x60-pcm.c | 806 ++ sound/soc/msm/msm8x60-pcm.h | 92 + sound/soc/msm/msm8x60.c | 1231 +++ sound/soc/msm/msm_audio_mvs.h | 369 + sound/soc/msm/mvs-dai.c | 141 + sound/soc/msm/qdsp6/Makefile | 2 + sound/soc/msm/qdsp6/q6adm.c | 1088 +++ sound/soc/msm/qdsp6/q6afe.c | 1738 ++++ sound/soc/msm/qdsp6/q6asm.c | 3533 ++++++++ sound/soc/msm/qdsp6/q6voice.c | 4013 +++++++++ sound/soc/msm/qdsp6/q6voice.h | 997 +++ sound/soc/msm/qdsp6v2/Makefile | 4 + sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c | 666 ++ sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h | 36 + sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c | 1229 +++ .../soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c | 777 ++ sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c | 581 ++ sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h | 45 + sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c | 609 ++ sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c | 725 ++ sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h | 84 + sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c | 1834 ++++ sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h | 103 + sound/soc/msm/qdsp6v2/q6adm.c | 621 ++ sound/soc/msm/qdsp6v2/q6afe.c | 1584 ++++ sound/soc/msm/qdsp6v2/q6asm.c | 3342 +++++++ sound/soc/msm/qdsp6v2/q6audio-v2.c | 151 + 103 files changed, 81306 insertions(+), 6 deletions(-) create mode 100644 include/sound/apr_audio-v2.h create mode 100644 include/sound/apr_audio.h create mode 100644 include/sound/dai.h create mode 100644 include/sound/msm-dai-q6-v2.h create mode 100644 include/sound/msm-dai-q6.h create mode 100644 include/sound/omap-abe-dsp.h create mode 100644 include/sound/q6adm-v2.h create mode 100644 include/sound/q6adm.h create mode 100644 include/sound/q6afe-v2.h create mode 100644 include/sound/q6afe.h create mode 100644 include/sound/q6asm-v2.h create mode 100644 include/sound/q6asm.h create mode 100644 include/sound/q6audio-v2.h create mode 100644 sound/soc/codecs/cs8427.c create mode 100644 sound/soc/codecs/msm_stub.c create mode 100644 sound/soc/codecs/timpani.c create mode 100644 sound/soc/codecs/timpani.h create mode 100644 sound/soc/codecs/wcd9304-tables.c create mode 100644 sound/soc/codecs/wcd9304.c create mode 100644 sound/soc/codecs/wcd9304.h create mode 100644 sound/soc/codecs/wcd9310-tables.c create mode 100644 sound/soc/codecs/wcd9310.c create mode 100644 sound/soc/codecs/wcd9310.h create mode 100644 sound/soc/msm/Kconfig create mode 100644 sound/soc/msm/Makefile create mode 100644 sound/soc/msm/apq8064.c create mode 100644 sound/soc/msm/lpass-dma.c create mode 100644 sound/soc/msm/lpass-i2s.c create mode 100644 sound/soc/msm/lpass-pcm.c create mode 100644 sound/soc/msm/lpass-pcm.h create mode 100644 sound/soc/msm/mdm9615.c create mode 100644 sound/soc/msm/mpq8064.c create mode 100644 sound/soc/msm/msm-compr-q6.c create mode 100644 sound/soc/msm/msm-compr-q6.h create mode 100644 sound/soc/msm/msm-dai-fe.c create mode 100644 sound/soc/msm/msm-dai-q6-hdmi.c create mode 100644 sound/soc/msm/msm-dai-q6.c create mode 100644 sound/soc/msm/msm-dai-stub.c create mode 100644 sound/soc/msm/msm-dai.c create mode 100644 sound/soc/msm/msm-multi-ch-pcm-q6.c create mode 100644 sound/soc/msm/msm-mvs.c create mode 100644 sound/soc/msm/msm-pcm-afe.c create mode 100644 sound/soc/msm/msm-pcm-afe.h create mode 100644 sound/soc/msm/msm-pcm-hostless.c create mode 100644 sound/soc/msm/msm-pcm-lpa.c create mode 100644 sound/soc/msm/msm-pcm-q6.c create mode 100644 sound/soc/msm/msm-pcm-q6.h create mode 100644 sound/soc/msm/msm-pcm-routing.c create mode 100644 sound/soc/msm/msm-pcm-routing.h create mode 100644 sound/soc/msm/msm-pcm-voice.c create mode 100644 sound/soc/msm/msm-pcm-voice.h create mode 100644 sound/soc/msm/msm-pcm-voip.c create mode 100644 sound/soc/msm/msm-pcm.c create mode 100644 sound/soc/msm/msm-pcm.h create mode 100644 sound/soc/msm/msm-voip.c create mode 100644 sound/soc/msm/msm7201.c create mode 100644 sound/soc/msm/msm7k-pcm.c create mode 100644 sound/soc/msm/msm7kv2-dai.c create mode 100644 sound/soc/msm/msm7kv2-dsp.c create mode 100644 sound/soc/msm/msm7kv2-pcm.c create mode 100644 sound/soc/msm/msm7kv2-pcm.h create mode 100644 sound/soc/msm/msm7x30.c create mode 100644 sound/soc/msm/msm8660-apq-wm8903.c create mode 100644 sound/soc/msm/msm8660.c create mode 100644 sound/soc/msm/msm8930.c create mode 100644 sound/soc/msm/msm8960.c create mode 100644 sound/soc/msm/msm8974.c create mode 100644 sound/soc/msm/msm8x60-dai.c create mode 100644 sound/soc/msm/msm8x60-pcm.c create mode 100644 sound/soc/msm/msm8x60-pcm.h create mode 100644 sound/soc/msm/msm8x60.c create mode 100644 sound/soc/msm/msm_audio_mvs.h create mode 100644 sound/soc/msm/mvs-dai.c create mode 100644 sound/soc/msm/qdsp6/Makefile create mode 100644 sound/soc/msm/qdsp6/q6adm.c create mode 100644 sound/soc/msm/qdsp6/q6afe.c create mode 100644 sound/soc/msm/qdsp6/q6asm.c create mode 100644 sound/soc/msm/qdsp6/q6voice.c create mode 100644 sound/soc/msm/qdsp6/q6voice.h create mode 100644 sound/soc/msm/qdsp6v2/Makefile create mode 100644 sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h create mode 100644 sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c create mode 100644 sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h create mode 100644 sound/soc/msm/qdsp6v2/q6adm.c create mode 100644 sound/soc/msm/qdsp6v2/q6afe.c create mode 100644 sound/soc/msm/qdsp6v2/q6asm.c create mode 100644 sound/soc/msm/qdsp6v2/q6audio-v2.c diff --git a/include/sound/apr_audio-v2.h b/include/sound/apr_audio-v2.h new file mode 100644 index 000000000000..760e221859b8 --- /dev/null +++ b/include/sound/apr_audio-v2.h @@ -0,0 +1,6172 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + + +#ifndef _APR_AUDIO_V2_H_ +#define _APR_AUDIO_V2_H_ + +#include + +#define ADSP_ADM_VERSION 0x00070000 + +#define ADM_CMD_SHARED_MEM_MAP_REGIONS 0x00010322 +#define ADM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010323 +#define ADM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010324 + +#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325 + +/* Enumeration for an audio Rx matrix ID.*/ +#define ADM_MATRIX_ID_AUDIO_RX 0 + +#define ADM_MATRIX_ID_AUDIO_TX 1 + +/* Enumeration for an audio Tx matrix ID.*/ +#define ADM_MATRIX_ID_AUDIOX 1 + +#define ADM_MAX_COPPS 5 + + +/* Session map node structure. +* Immediately following this structure are num_copps +* entries of COPP IDs. The COPP IDs are 16 bits, so +* there might be a padding 16-bit field if num_copps +* is odd. +*/ +struct adm_session_map_node_v5 { + u16 session_id; +/* Handle of the ASM session to be routed. Supported values: 1 +* to 8. +*/ + + + u16 num_copps; + /* Number of COPPs to which this session is to be routed. + Supported values: 0 < num_copps <= ADM_MAX_COPPS. + */ +} __packed; + +/* Payload of the #ADM_CMD_MATRIX_MAP_ROUTINGS_V5 command. +* Immediately following this structure are num_sessions of the session map +* node payload (adm_session_map_node_v5). +*/ + +struct adm_cmd_matrix_map_routings_v5 { + struct apr_hdr hdr; + + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx +* (1). Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX +* macros to set this field. +*/ + u32 num_sessions; + /* Number of sessions being updated by this command (optional).*/ +} __packed; + +/* This command allows a client to open a COPP/Voice Proc. TX module +* and sets up the device session: Matrix -> COPP -> AFE on the RX +* and AFE -> COPP -> Matrix on the TX. This enables PCM data to +* be transferred to/from the endpoint (AFEPortID). +* +* @return +* #ADM_CMDRSP_DEVICE_OPEN_V5 with the resulting status and +* COPP ID. +*/ +#define ADM_CMD_DEVICE_OPEN_V5 0x00010326 + +/* Indicates that endpoint_id_2 is to be ignored.*/ +#define ADM_CMD_COPP_OPEN_END_POINT_ID_2_IGNORE 0xFFFF + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATION_RX_PATH_COPP 1 + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATIONX_PATH_LIVE_COPP 2 + +#define ADM_CMD_COPP_OPEN_MODE_OF_OPERATIONX_PATH_NON_LIVE_COPP 3 + +/* Indicates that an audio COPP is to send/receive a mono PCM + * stream to/from + * END_POINT_ID_1. + */ +#define ADM_CMD_COPP_OPEN_CHANNEL_CONFIG_MONO 1 + +/* Indicates that an audio COPP is to send/receive a + * stereo PCM stream to/from END_POINT_ID_1. + */ +#define ADM_CMD_COPP_OPEN_CHANNEL_CONFIG_STEREO 2 + +/* Sample rate is 8000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_8K 8000 + +/* Sample rate is 16000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_16K 16000 + +/* Sample rate is 48000 Hz.*/ +#define ADM_CMD_COPP_OPEN_SAMPLE_RATE_48K 48000 + +/* Definition for a COPP live input flag bitmask.*/ +#define ADM_BIT_MASK_COPP_LIVE_INPUT_FLAG (0x0001U) + +/* Definition for a COPP live shift value bitmask.*/ +#define ADM_SHIFT_COPP_LIVE_INPUT_FLAG 0 + +/* Definition for the COPP ID bitmask.*/ +#define ADM_BIT_MASK_COPP_ID (0x0000FFFFUL) + +/* Definition for the COPP ID shift value.*/ +#define ADM_SHIFT_COPP_ID 0 + +/* Definition for the service ID bitmask.*/ +#define ADM_BIT_MASK_SERVICE_ID (0x00FF0000UL) + +/* Definition for the service ID shift value.*/ +#define ADM_SHIFT_SERVICE_ID 16 + +/* Definition for the domain ID bitmask.*/ +#define ADM_BIT_MASK_DOMAIN_ID (0xFF000000UL) + +/* Definition for the domain ID shift value.*/ +#define ADM_SHIFT_DOMAIN_ID 24 + +/* ADM device open command payload of the + #ADM_CMD_DEVICE_OPEN_V5 command. +*/ +struct adm_cmd_device_open_v5 { + struct apr_hdr hdr; + u16 flags; +/* Reserved for future use. Clients must set this field + * to zero. + */ + + u16 mode_of_operation; +/* Specifies whether the COPP must be opened on the Tx or Rx + * path. Use the ADM_CMD_COPP_OPEN_MODE_OF_OPERATION_* macros for + * supported values and interpretation. + * Supported values: + * - 0x1 -- Rx path COPP + * - 0x2 -- Tx path live COPP + * - 0x3 -- Tx path nonlive COPP + * Live connections cause sample discarding in the Tx device + * matrix if the destination output ports do not pull them + * fast enough. Nonlive connections queue the samples + * indefinitely. + */ + + u16 endpoint_id_1; +/* Logical and physical endpoint ID of the audio path. + * If the ID is a voice processor Tx block, it receives near + * samples. Supported values: Any pseudoport, AFE Rx port, + * or AFE Tx port For a list of valid IDs, refer to + * @xhyperref{Q4,[Q4]}. + * Q4 = Hexagon Multimedia: AFE Interface Specification + */ + + u16 endpoint_id_2; +/* Logical and physical endpoint ID 2 for a voice processor + * Tx block. + * This is not applicable to audio COPP. + * Supported values: + * - AFE Rx port + * - 0xFFFF -- Endpoint 2 is unavailable and the voice + * processor Tx + * block ignores this endpoint + * When the voice processor Tx block is created on the audio + * record path, + * it can receive far-end samples from an AFE Rx port if the + * voice call + * is active. The ID of the AFE port is provided in this + * field. + * For a list of valid IDs, refer @xhyperref{Q4,[Q4]}. + */ + + u32 topology_id; + /* Audio COPP topology ID; 32-bit GUID. */ + + u16 dev_num_channel; +/* Number of channels the audio COPP sends to/receives from + * the endpoint. + * Supported values: 1 to 8. + * The value is ignored for the voice processor Tx block, + * where channel + * configuration is derived from the topology ID. + */ + + u16 bit_width; +/* Bit width (in bits) that the audio COPP sends to/receives + * from the + * endpoint. The value is ignored for the voice processing + * Tx block, + * where the PCM width is 16 bits. + */ + + u32 sample_rate; +/* Sampling rate at which the audio COPP/voice processor + * Tx block + * interfaces with the endpoint. + * Supported values for voice processor Tx: 8000, 16000, + * 48000 Hz + * Supported values for audio COPP: >0 and <=192 kHz + */ + + u8 dev_channel_mapping[8]; +/* Array of channel mapping of buffers that the audio COPP + * sends to the endpoint. Channel[i] mapping describes channel + * I inside the buffer, where 0 < i < dev_num_channel. + * This value is relevent only for an audio Rx COPP. + * For the voice processor block and Tx audio block, this field + * is set to zero and is ignored. + */ +} __packed; + +/* + * This command allows the client to close a COPP and disconnect + * the device session. + */ +#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327 + +/* Sets one or more parameters to a COPP. +*/ +#define ADM_CMD_SET_PP_PARAMS_V5 0x00010328 + +/* Payload of the #ADM_CMD_SET_PP_PARAMS_V5 command. + * If the data_payload_addr_lsw and data_payload_addr_msw element + * are NULL, a series of adm_param_datastructures immediately + * follows, whose total size is data_payload_size bytes. + */ +struct adm_cmd_set_pp_params_v5 { + struct apr_hdr hdr; + u32 data_payload_addr_lsw; + /* LSW of parameter data payload address.*/ + u32 data_payload_addr_msw; + /* MSW of parameter data payload address.*/ + + u32 mem_map_handle; +/* Memory map handle returned by ADM_CMD_SHARED_MEM_MAP_REGIONS + * command */ +/* If mem_map_handle is zero implies the message is in + * the payload */ + + u32 data_payload_size; +/* Size in bytes of the variable payload accompanying this + * message or + * in shared memory. This is used for parsing the parameter + * payload. + */ +} __packed; + +/* Payload format for COPP parameter data. + * Immediately following this structure are param_size bytes + * of parameter + * data. + */ +struct adm_param_data_v5 { + u32 module_id; + /* Unique ID of the module. */ + u32 param_id; + /* Unique ID of the parameter. */ + u16 param_size; + /* Data size of the param_id/module_id combination. + This value is a + multiple of 4 bytes. */ + u16 reserved; + /* Reserved for future enhancements. + * This field must be set to zero. + */ +} __packed; + +/* Returns the status and COPP ID to an #ADM_CMD_DEVICE_OPEN_V5 command. + */ +#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329 + +/* Payload of the #ADM_CMDRSP_DEVICE_OPEN_V5 message, + * which returns the + * status and COPP ID to an #ADM_CMD_DEVICE_OPEN_V5 command. + */ +struct adm_cmd_rsp_device_open_v5 { + u32 status; + /* Status message (error code).*/ + + u16 copp_id; + /* COPP ID: Supported values: 0 <= copp_id < ADM_MAX_COPPS*/ + + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +/* This command allows a query of one COPP parameter. +*/ +#define ADM_CMD_GET_PP_PARAMS_V5 0x0001032A + +/* Payload an #ADM_CMD_GET_PP_PARAMS_V5 command. +*/ +struct adm_cmd_get_pp_params_v5 { + u32 data_payload_addr_lsw; + /* LSW of parameter data payload address.*/ + + u32 data_payload_addr_msw; + /* MSW of parameter data payload address.*/ + + /* If the mem_map_handle is non zero, + * on ACK, the ParamData payloads begin at + * the address specified (out-of-band). + */ + + u32 mem_map_handle; + /* Memory map handle returned + * by ADM_CMD_SHARED_MEM_MAP_REGIONS command. + * If the mem_map_handle is 0, it implies that + * the ACK's payload will contain the ParamData (in-band). + */ + + u32 module_id; + /* Unique ID of the module. */ + + u32 param_id; + /* Unique ID of the parameter. */ + + u16 param_max_size; + /* Maximum data size of the parameter + *ID/module ID combination. This + * field is a multiple of 4 bytes. + */ + u16 reserved; + /* Reserved for future enhancements. + * This field must be set to zero. + */ +} __packed; + +/* Returns parameter values + * in response to an #ADM_CMD_GET_PP_PARAMS_V5 command. + */ +#define ADM_CMDRSP_GET_PP_PARAMS_V5 0x0001032B + +/* Payload of the #ADM_CMDRSP_GET_PP_PARAMS_V5 message, + * which returns parameter values in response + * to an #ADM_CMD_GET_PP_PARAMS_V5 command. + * Immediately following this + * structure is the adm_param_data_v5 + * structure containing the pre/postprocessing + * parameter data. For an in-band + * scenario, the variable payload depends + * on the size of the parameter. +*/ +struct adm_cmd_rsp_get_pp_params_v5 { + u32 status; + /* Status message (error code).*/ +} __packed; + +/* Allows a client to control the gains on various session-to-COPP paths. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_V5 0x0001032C + +/* Indicates that the target gain in the + * current adm_session_copp_gain_v5 + * structure is to be applied to all + * the session-to-COPP paths that exist for + * the specified session. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_COPP_ID_ALL_CONNECTED_COPPS 0xFFFF + +/* Indicates that the target gain is + * to be immediately applied to the + * specified session-to-COPP path, + * without a ramping fashion. + */ +#define ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE 0x0000 + +/* Enumeration for a linear ramping curve.*/ +#define ADM_CMD_MATRIX_RAMP_GAINS_RAMP_CURVE_LINEAR 0x0000 + +/* Payload of the #ADM_CMD_MATRIX_RAMP_GAINS_V5 command. + * Immediately following this structure are num_gains of the + * adm_session_copp_gain_v5structure. + */ +struct adm_cmd_matrix_ramp_gains_v5 { + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx (1). + * Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX + * macros to set this field. +*/ + + u16 num_gains; + /* Number of gains being applied. */ + + u16 reserved_for_align; + /* Reserved. This field must be set to zero.*/ +} __packed; + +/* Session-to-COPP path gain structure, used by the + * #ADM_CMD_MATRIX_RAMP_GAINS_V5 command. + * This structure specifies the target + * gain (per channel) that must be applied + * to a particular session-to-COPP path in + * the audio matrix. The structure can + * also be used to apply the gain globally + * to all session-to-COPP paths that + * exist for the given session. + * The aDSP uses device channel mapping to + * determine which channel gains to + * use from this command. For example, + * if the device is configured as stereo, + * the aDSP uses only target_gain_ch_1 and + * target_gain_ch_2, and it ignores + * the others. + */ +struct adm_session_copp_gain_v5 { + u16 session_id; +/* Handle of the ASM session. + * Supported values: 1 to 8. + */ + + u16 copp_id; +/* Handle of the COPP. Gain will be applied on the Session ID + * COPP ID path. + */ + + u16 ramp_duration; +/* Duration (in milliseconds) of the ramp over + * which target gains are + * to be applied. Use + * #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE + * to indicate that gain must be applied immediately. + */ + + u16 step_duration; +/* Duration (in milliseconds) of each step in the ramp. + * This parameter is ignored if ramp_duration is equal to + * #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_DURATION_IMMEDIATE. + * Supported value: 1 + */ + + u16 ramp_curve; +/* Type of ramping curve. + * Supported value: #ADM_CMD_MATRIX_RAMP_GAINS_RAMP_CURVE_LINEAR + */ + + u16 reserved_for_align; + /* Reserved. This field must be set to zero. */ + + u16 target_gain_ch_1; + /* Target linear gain for channel 1 in Q13 format; */ + + u16 target_gain_ch_2; + /* Target linear gain for channel 2 in Q13 format; */ + + u16 target_gain_ch_3; + /* Target linear gain for channel 3 in Q13 format; */ + + u16 target_gain_ch_4; + /* Target linear gain for channel 4 in Q13 format; */ + + u16 target_gain_ch_5; + /* Target linear gain for channel 5 in Q13 format; */ + + u16 target_gain_ch_6; + /* Target linear gain for channel 6 in Q13 format; */ + + u16 target_gain_ch_7; + /* Target linear gain for channel 7 in Q13 format; */ + + u16 target_gain_ch_8; + /* Target linear gain for channel 8 in Q13 format; */ +} __packed; + +/* Allows to set mute/unmute on various session-to-COPP paths. + * For every session-to-COPP path (stream-device interconnection), + * mute/unmute can be set individually on the output channels. + */ +#define ADM_CMD_MATRIX_MUTE_V5 0x0001032D + +/* Indicates that mute/unmute in the + * current adm_session_copp_mute_v5structure + * is to be applied to all the session-to-COPP + * paths that exist for the specified session. + */ +#define ADM_CMD_MATRIX_MUTE_COPP_ID_ALL_CONNECTED_COPPS 0xFFFF + +/* Payload of the #ADM_CMD_MATRIX_MUTE_V5 command*/ +struct adm_cmd_matrix_mute_v5 { + u32 matrix_id; +/* Specifies whether the matrix ID is Audio Rx (0) or Audio Tx (1). + * Use the ADM_MATRIX_ID_AUDIO_RX or ADM_MATRIX_ID_AUDIOX + * macros to set this field. + */ + + u16 session_id; +/* Handle of the ASM session. + * Supported values: 1 to 8. + */ + + u16 copp_id; +/* Handle of the COPP. + * Use ADM_CMD_MATRIX_MUTE_COPP_ID_ALL_CONNECTED_COPPS + * to indicate that mute/unmute must be applied to + * all the COPPs connected to session_id. + * Supported values: + * - 0xFFFF -- Apply mute/unmute to all connected COPPs + * - Other values -- Valid COPP ID + */ + + u8 mute_flag_ch_1; + /* Mute flag for channel 1 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_2; + /* Mute flag for channel 2 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_3; + /* Mute flag for channel 3 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_4; + /* Mute flag for channel 4 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_5; + /* Mute flag for channel 5 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_6; + /* Mute flag for channel 6 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_7; + /* Mute flag for channel 7 is set to unmute (0) or mute (1). */ + + u8 mute_flag_ch_8; + /* Mute flag for channel 8 is set to unmute (0) or mute (1). */ + + u16 ramp_duration; +/* Period (in milliseconds) over which the soft mute/unmute will be + * applied. + * Supported values: 0 (Default) to 0xFFFF + * The default of 0 means mute/unmute will be applied immediately. + */ + + u16 reserved_for_align; + /* Clients must set this field to zero.*/ +} __packed; + +/* Allows a client to connect the desired stream to + * the desired AFE port through the stream router + * + * This command allows the client to connect specified session to + * specified AFE port. This is used for compressed streams only + * opened using the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED or + * #ASM_STREAM_CMD_OPEN_READ_COMPRESSED command. + * + * @prerequisites + * Session ID and AFE Port ID must be valid. + * #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED or + * #ASM_STREAM_CMD_OPEN_READ_COMPRESSED + * must have been called on this session. + */ + +#define ADM_CMD_CONNECT_AFE_PORT_V5 0x0001032E +#define ADM_CMD_DISCONNECT_AFE_PORT_V5 0x0001032F +/* Enumeration for the Rx stream router ID.*/ +#define ADM_STRTR_ID_RX 0 +/* Enumeration for the Tx stream router ID.*/ +#define ADM_STRTR_IDX 1 + +/* Payload of the #ADM_CMD_CONNECT_AFE_PORT_V5 command.*/ +struct adm_cmd_connect_afe_port_v5 { + u8 mode; +/* ID of the stream router (RX/TX). Use the + * ADM_STRTR_ID_RX or ADM_STRTR_IDX macros + * to set this field. + */ + + u8 session_id; + /* Session ID of the stream to connect */ + + u16 afe_port_id; + /* Port ID of the AFE port to connect to.*/ + u32 num_channels; +/* Number of device channels + * Supported values: 2(Audio Sample Packet), + * 8 (HBR Audio Stream Sample Packet) + */ + + u32 sampling_rate; +/* Device sampling rate +* Supported values: Any +*/ +} __packed; + + +/* adsp_adm_api.h */ + + +/* Port ID. Update afe_get_port_index + * when a new port is added here. */ +#define PRIMARY_I2S_RX 0 /* index = 0 */ +#define PRIMARY_I2S_TX 1 /* index = 1 */ +#define PCM_RX 2 /* index = 2 */ +#define PCM_TX 3 /* index = 3 */ +#define SECONDARY_I2S_RX 4 /* index = 4 */ +#define SECONDARY_I2S_TX 5 /* index = 5 */ +#define MI2S_RX 6 /* index = 6 */ +#define MI2S_TX 7 /* index = 7 */ +#define HDMI_RX 8 /* index = 8 */ +#define RSVD_2 9 /* index = 9 */ +#define RSVD_3 10 /* index = 10 */ +#define DIGI_MIC_TX 11 /* index = 11 */ +#define VOICE_RECORD_RX 0x8003 /* index = 12 */ +#define VOICE_RECORD_TX 0x8004 /* index = 13 */ +#define VOICE_PLAYBACK_TX 0x8005 /* index = 14 */ + +/* Slimbus Multi channel port id pool */ +#define SLIMBUS_0_RX 0x4000 /* index = 15 */ +#define SLIMBUS_0_TX 0x4001 /* index = 16 */ +#define SLIMBUS_1_RX 0x4002 /* index = 17 */ +#define SLIMBUS_1_TX 0x4003 /* index = 18 */ +#define SLIMBUS_2_RX 0x4004 +#define SLIMBUS_2_TX 0x4005 +#define SLIMBUS_3_RX 0x4006 +#define SLIMBUS_3_TX 0x4007 +#define SLIMBUS_4_RX 0x4008 +#define SLIMBUS_4_TX 0x4009 /* index = 24 */ +#define INT_BT_SCO_RX 0x3000 /* index = 25 */ +#define INT_BT_SCO_TX 0x3001 /* index = 26 */ +#define INT_BT_A2DP_RX 0x3002 /* index = 27 */ +#define INT_FM_RX 0x3004 /* index = 28 */ +#define INT_FM_TX 0x3005 /* index = 29 */ +#define RT_PROXY_PORT_001_RX 0x2000 /* index = 30 */ +#define RT_PROXY_PORT_001_TX 0x2001 /* index = 31 */ + +#define AFE_PORT_INVALID 0xFFFF +#define SLIMBUS_INVALID AFE_PORT_INVALID + +#define AFE_PORT_CMD_START 0x000100ca + +#define AFE_EVENT_RTPORT_START 0 +#define AFE_EVENT_RTPORT_STOP 1 +#define AFE_EVENT_RTPORT_LOW_WM 2 +#define AFE_EVENT_RTPORT_HI_WM 3 + +#define ADSP_AFE_VERSION 0x00200000 + +/* Size of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_SIZE 0xF + +/* Size of the range of port IDs for internal BT-FM ports. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_SIZE 0x6 + +/* Size of the range of port IDs for SLIMbus® + * multichannel + * ports. + */ +#define AFE_PORT_ID_SLIMBUS_RANGE_SIZE 0xA + +/* Size of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_SIZE 0x2 + +/* Size of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_SIZE 0x5 + +/* Start of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_START 0x1000 + +/* End of the range of port IDs for the audio interface. */ +#define AFE_PORT_ID_AUDIO_IF_PORT_RANGE_END \ + (AFE_PORT_ID_AUDIO_IF_PORT_RANGE_START +\ + AFE_PORT_ID_AUDIO_IF_PORT_RANGE_SIZE - 1) + +/* Start of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_START 0x2000 + +/* End of the range of port IDs for real-time proxy ports. */ +#define AFE_PORT_ID_RT_PROXY_PORT_RANGE_END \ + (AFE_PORT_ID_RT_PROXY_PORT_RANGE_START +\ + AFE_PORT_ID_RT_PROXY_PORT_RANGE_SIZE-1) + +/* Start of the range of port IDs for internal BT-FM devices. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_START 0x3000 + +/* End of the range of port IDs for internal BT-FM devices. */ +#define AFE_PORT_ID_INTERNAL_BT_FM_RANGE_END \ + (AFE_PORT_ID_INTERNAL_BT_FM_RANGE_START +\ + AFE_PORT_ID_INTERNAL_BT_FM_RANGE_SIZE-1) + +/* Start of the range of port IDs for SLIMbus devices. */ +#define AFE_PORT_ID_SLIMBUS_RANGE_START 0x4000 + +/* End of the range of port IDs for SLIMbus devices. */ +#define AFE_PORT_ID_SLIMBUS_RANGE_END \ + (AFE_PORT_ID_SLIMBUS_RANGE_START +\ + AFE_PORT_ID_SLIMBUS_RANGE_SIZE-1) + +/* Start of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_START 0x8001 + +/* End of the range of port IDs for pseudoports. */ +#define AFE_PORT_ID_PSEUDOPORT_RANGE_END \ + (AFE_PORT_ID_PSEUDOPORT_RANGE_START +\ + AFE_PORT_ID_PSEUDOPORT_RANGE_SIZE-1) + +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_IDERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_IDERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 +#define AUDIO_PORT_ID_I2S_RX 0x1008 +#define AFE_PORT_ID_DIGITAL_MIC_TX 0x1009 +#define AFE_PORT_ID_PRIMARY_PCM_RX 0x100A +#define AFE_PORT_ID_PRIMARY_PCM_TX 0x100B +#define AFE_PORT_ID_SECONDARY_PCM_RX 0x100C +#define AFE_PORT_ID_SECONDARY_PCM_TX 0x100D +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E +#define AFE_PORT_ID_RT_PROXY_PORT_001_RX 0x2000 +#define AFE_PORT_ID_RT_PROXY_PORT_001_TX 0x2001 +#define AFE_PORT_ID_INTERNAL_BT_SCO_RX 0x3000 +#define AFE_PORT_ID_INTERNAL_BT_SCO_TX 0x3001 +#define AFE_PORT_ID_INTERNAL_BT_A2DP_RX 0x3002 +#define AFE_PORT_ID_INTERNAL_FM_RX 0x3004 +#define AFE_PORT_ID_INTERNAL_FM_TX 0x3005 +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* Generic pseudoport 1. */ +#define AFE_PORT_ID_PSEUDOPORT_01 0x8001 +/* Generic pseudoport 2. */ +#define AFE_PORT_ID_PSEUDOPORT_02 0x8002 + +/* @xreflabel{hdr:AfePortIdPrimaryAuxPcmTx} + Primary Aux PCM Tx port ID. +*/ +#define AFE_PORT_ID_PRIMARY_PCM_TX 0x100B +/* Pseudoport that corresponds to the voice Rx path. + * For recording, the voice Rx path samples are written to this + * port and consumed by the audio path. + */ + +#define AFE_PORT_ID_VOICE_RECORD_RX 0x8003 + +/* Pseudoport that corresponds to the voice Tx path. + * For recording, the voice Tx path samples are written to this + * port and consumed by the audio path. + */ + +#define AFE_PORT_ID_VOICE_RECORD_TX 0x8004 +/* Pseudoport that corresponds to in-call voice delivery samples. + * During in-call audio delivery, the audio path delivers samples + * to this port from where the voice path delivers them on the + * Rx path. + */ +#define AFE_PORT_ID_VOICE_PLAYBACK_TX 0x8005 +#define AFE_PORT_ID_INVALID 0xFFFF + +#define AAC_ENC_MODE_AAC_LC 0x02 +#define AAC_ENC_MODE_AAC_P 0x05 +#define AAC_ENC_MODE_EAAC_P 0x1D + +#define AFE_PSEUDOPORT_CMD_START 0x000100cf +struct afe_pseudoport_start_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 timing; /* FTRT = 0 , AVTimer = 1, */ +} __packed; + +#define AFE_PSEUDOPORT_CMD_STOP 0x000100d0 +struct afe_pseudoport_stop_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 reserved; +} __packed; + + +#define AFE_MODULE_SIDETONE_IIR_FILTER 0x00010202 +#define AFE_PARAM_ID_ENABLE 0x00010203 + +/* Payload of the #AFE_PARAM_ID_ENABLE + * parameter, which enables or + * disables any module. + * The fixed size of this structure is four bytes. + */ + +struct afe_mod_enable_param { + u16 enable; + /* Enables (1) or disables (0) the module. */ + + u16 reserved; + /* This field must be set to zero. + */ +} __packed; + +/* ID of the configuration parameter used by the + * #AFE_MODULE_SIDETONE_IIR_FILTER module. + */ +#define AFE_PARAM_ID_SIDETONE_IIR_FILTER_CONFIG 0x00010204 + +struct afe_sidetone_iir_filter_config_params { + u16 num_biquad_stages; +/* Number of stages. + * Supported values: Minimum of 5 and maximum of 10 + */ + + u16 pregain; +/* Pregain for the compensating filter response. + * Supported values: Any number in Q13 format + */ +} __packed; + +#define AFE_MODULE_LOOPBACK 0x00010205 +#define AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH 0x00010206 + +/* Payload of the #AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH parameter, + * which gets/sets loopback gain of a port to an Rx port. + * The Tx port ID of the loopback is part of the set_param command. + */ + +/* Payload of the #AFE_PORT_CMD_SET_PARAM_V2 command's + * configuration/calibration settings for the AFE port. + */ +struct afe_port_cmd_set_param_v2 { + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. + */ + + u16 payload_size; +/* Actual size of the payload in bytes. + * This is used for parsing the parameter payload. + * Supported values: > 0 + */ + +u32 payload_address_lsw; +/* LSW of 64 bit Payload address. + * Address should be 32-byte, + * 4kbyte aligned and must be contiguous memory. + */ + +u32 payload_address_msw; +/* MSW of 64 bit Payload address. + * In case of 32-bit shared memory address, + * this field must be set to zero. + * In case of 36-bit shared memory address, + * bit-4 to bit-31 must be set to zero. + * Address should be 32-byte, 4kbyte aligned + * and must be contiguous memory. + */ + +u32 mem_map_handle; +/* Memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands. + * Supported Values: + * - NULL -- Message. The parameter data is in-band. + * - Non-NULL -- The parameter data is Out-band.Pointer to + * the physical address + * in shared memory of the payload data. + * An optional field is available if parameter + * data is in-band: + * afe_param_data_v2 param_data[...]. + * For detailed payload content, see the + * afe_port_param_data_v2 structure. + */ +} __packed; + +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF + +struct afe_port_param_data_v2 { + u32 module_id; +/* ID of the module to be configured. + * Supported values: Valid module ID + */ + +u32 param_id; +/* ID of the parameter corresponding to the supported parameters + * for the module ID. + * Supported values: Valid parameter ID + */ + +u16 param_size; +/* Actual size of the data for the + * module_id/param_id pair. The size is a + * multiple of four bytes. + * Supported values: > 0 + */ + +u16 reserved; +/* This field must be set to zero. + */ +} __packed; + +struct afe_loopback_gain_per_path_param { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + u16 rx_port_id; +/* Rx port of the loopback. */ + +u16 gain; +/* Loopback gain per path of the port. + * Supported values: Any number in Q13 format + */ +} __packed; + +/* Parameter ID used to configure and enable/disable the + * loopback path. The difference with respect to the existing + * API, AFE_PORT_CMD_LOOPBACK, is that it allows Rx port to be + * configured as source port in loopback path. Port-id in + * AFE_PORT_CMD_SET_PARAM cmd is the source port whcih can be + * Tx or Rx port. In addition, we can configure the type of + * routing mode to handle different use cases. + */ +#define AFE_PARAM_ID_LOOPBACK_CONFIG 0x0001020B +#define AFE_API_VERSION_LOOPBACK_CONFIG 0x1 + +enum afe_loopback_routing_mode { + LB_MODE_DEFAULT = 1, + /* Regular loopback from source to destination port */ + LB_MODE_SIDETONE, + /* Sidetone feed from Tx source to Rx destination port */ + LB_MODE_EC_REF_VOICE_AUDIO, + /* Echo canceller reference, voice + audio + DTMF */ + LB_MODE_EC_REF_VOICE + /* Echo canceller reference, voice alone */ +} __packed; + +/* Payload of the #AFE_PARAM_ID_LOOPBACK_CONFIG , + * which enables/disables one AFE loopback. + */ +struct afe_loopback_cfg_v1 { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + u32 loopback_cfg_minor_version; +/* Minor version used for tracking the version of the RMC module + * configuration interface. + * Supported values: #AFE_API_VERSION_LOOPBACK_CONFIG + */ + u16 dst_port_id; + /* Destination Port Id. */ + u16 routing_mode; +/* Specifies data path type from src to dest port. + * Supported values: + * #LB_MODE_DEFAULT + * #LB_MODE_SIDETONE + * #LB_MODE_EC_REF_VOICE_AUDIO + * #LB_MODE_EC_REF_VOICE_A + * #LB_MODE_EC_REF_VOICE + */ + + u16 enable; +/* Specifies whether to enable (1) or + * disable (0) an AFE loopback. + */ + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0. + */ + +} __packed; + +#define AFE_MODULE_SPEAKER_PROTECTION 0x00010209 +#define AFE_PARAM_ID_SPKR_PROT_CONFIG 0x0001020a +#define AFE_API_VERSION_SPKR_PROT_CONFIG 0x1 +#define AFE_SPKR_PROT_EXCURSIONF_LEN 512 +struct afe_spkr_prot_cfg_param_v1 { + u32 spkr_prot_minor_version; +/* + * Minor version used for tracking the version of the + * speaker protection module configuration interface. + * Supported values: #AFE_API_VERSION_SPKR_PROT_CONFIG + */ + +int16_t win_size; +/* Analysis and synthesis window size (nWinSize). + * Supported values: 1024, 512, 256 samples + */ + +int16_t margin; +/* Allowable margin for excursion prediction, + * in L16Q15 format. This is a + * control parameter to allow + * for overestimation of peak excursion. + */ + +int16_t spkr_exc_limit; +/* Speaker excursion limit, in L16Q15 format.*/ + +int16_t spkr_resonance_freq; +/* Resonance frequency of the speaker; used + * to define a frequency range + * for signal modification. + * + * Supported values: 0 to 2000 Hz */ + +int16_t limhresh; +/* Threshold of the hard limiter; used to + * prevent overshooting beyond a + * signal level that was set by the limiter + * prior to speaker protection. + * Supported values: 0 to 32767 + */ + +int16_t hpf_cut_off_freq; +/* High pass filter cutoff frequency. + * Supported values: 100, 200, 300 Hz + */ + +int16_t hpf_enable; +/* Specifies whether the high pass filter + * is enabled (0) or disabled (1). + */ + +int16_t reserved; +/* This field must be set to zero. */ + +int32_t amp_gain; +/* Amplifier gain in L32Q15 format. + * This is the RMS voltage at the + * loudspeaker when a 0dBFS tone + * is played in the digital domain. + */ + +int16_t excursionf[AFE_SPKR_PROT_EXCURSIONF_LEN]; +/* Array of the excursion transfer function. + * The peak excursion of the + * loudspeaker diaphragm is + * measured in millimeters for 1 Vrms Sine + * tone at all FFT bin frequencies. + * Supported values: Q15 format + */ +} __packed; + + +#define AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER 0x000100E0 + +/* Payload of the #AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER + * command, which registers a real-time port driver + * with the AFE service. + */ +struct afe_service_cmd_register_rt_port_driver { + struct apr_hdr hdr; + u16 port_id; +/* Port ID with which the real-time driver exchanges data + * (registers for events). + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +#define AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER 0x000100E1 + +/* Payload of the #AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER + * command, which unregisters a real-time port driver from + * the AFE service. + */ +struct afe_service_cmd_unregister_rt_port_driver { + struct apr_hdr hdr; + u16 port_id; +/* Port ID from which the real-time + * driver unregisters for events. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +#define AFE_EVENT_RT_PROXY_PORT_STATUS 0x00010105 +#define AFE_EVENTYPE_RT_PROXY_PORT_START 0 +#define AFE_EVENTYPE_RT_PROXY_PORT_STOP 1 +#define AFE_EVENTYPE_RT_PROXY_PORT_LOW_WATER_MARK 2 +#define AFE_EVENTYPE_RT_PROXY_PORT_HIGH_WATER_MARK 3 +#define AFE_EVENTYPE_RT_PROXY_PORT_INVALID 0xFFFF + +/* Payload of the #AFE_EVENT_RT_PROXY_PORT_STATUS + * message, which sends an event from the AFE service + * to a registered client. + */ +struct afe_event_rt_proxy_port_status { + u16 port_id; +/* Port ID to which the event is sent. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 eventype; +/* Type of event. + * Supported values: + * - #AFE_EVENTYPE_RT_PROXY_PORT_START + * - #AFE_EVENTYPE_RT_PROXY_PORT_STOP + * - #AFE_EVENTYPE_RT_PROXY_PORT_LOW_WATER_MARK + * - #AFE_EVENTYPE_RT_PROXY_PORT_HIGH_WATER_MARK + */ +} __packed; + +#define AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2 0x000100ED + +struct afe_port_data_cmd_rt_proxy_port_write_v2 { + struct apr_hdr hdr; + u16 port_id; +/* Tx (mic) proxy port ID with which the real-time + * driver exchanges data. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + */ + + u16 reserved; + /* This field must be set to zero. */ + + u32 buffer_address_lsw; +/* LSW Address of the buffer containing the + * data from the real-time source + * device on a client. + */ + + u32 buffer_address_msw; +/* MSW Address of the buffer containing the + * data from the real-time source + * device on a client. + */ + + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory + * attributes is returned if + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS + * command is successful. + * Supported Values: + * - Any 32 bit value + */ + + u32 available_bytes; +/* Number of valid bytes available + * in the buffer (including all + * channels: number of bytes per + * channel = availableBytesumChannels). + * Supported values: > 0 + * + * This field must be equal to the frame + * size specified in the #AFE_PORT_AUDIO_IF_CONFIG + * command that was sent to configure this + * port. + */ +} __packed; + +#define AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2 0x000100EE + +/* Payload of the + * #AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2 command, which + * delivers an empty buffer to the AFE service. On + * acknowledgment, data is filled in the buffer. + */ +struct afe_port_data_cmd_rt_proxy_port_read_v2 { + struct apr_hdr hdr; + u16 port_id; +/* Rx proxy port ID with which the real-time + * driver exchanges data. + * Supported values: #AFE_PORT_ID_RT_PROXY_PORT_RANGE_START to + * #AFE_PORT_ID_RT_PROXY_PORT_RANGE_END + * (This must be an Rx (speaker) port.) + */ + + u16 reserved; + /* This field must be set to zero. */ + + u32 buffer_address_lsw; +/* LSW Address of the buffer containing the data sent from the AFE + * service to a real-time sink device on the client. + */ + + + u32 buffer_address_msw; +/* MSW Address of the buffer containing the data sent from the AFE + * service to a real-time sink device on the client. + */ + + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned if AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS command is + * successful. + * Supported Values: + * - Any 32 bit value + */ + + u32 available_bytes; +/* Number of valid bytes available in the buffer (including all + * channels). + * Supported values: > 0 + * This field must be equal to the frame size specified in the + * #AFE_PORT_AUDIO_IF_CONFIG command that was sent to configure + * this port. + */ +} __packed; + +/* This module ID is related to device configuring like I2S,PCM, + * HDMI, SLIMBus etc. This module supports follwing parameter ids. + * - #AFE_PARAM_ID_I2S_CONFIG + * - #AFE_PARAM_ID_PCM_CONFIG + * - #AFE_PARAM_ID_DIGI_MIC_CONFIG + * - #AFE_PARAM_ID_HDMI_CONFIG + * - #AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG + * - #AFE_PARAM_ID_SLIMBUS_CONFIG + * - #AFE_PARAM_ID_RT_PROXY_CONFIG + */ + +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C +#define AFE_PORT_SAMPLE_RATE_8K 8000 +#define AFE_PORT_SAMPLE_RATE_16K 16000 +#define AFE_PORT_SAMPLE_RATE_48K 48000 +#define AFE_PORT_SAMPLE_RATE_96K 96000 +#define AFE_PORT_SAMPLE_RATE_192K 192000 +#define AFE_LINEAR_PCM_DATA 0x0 +#define AFE_NON_LINEAR_DATA 0x1 +#define AFE_LINEAR_PCM_DATA_PACKED_60958 0x2 +#define AFE_NON_LINEAR_DATA_PACKED_60958 0x3 + +/* This param id is used to configure I2S interface */ +#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D +#define AFE_API_VERSION_I2S_CONFIG 0x1 +/* Enumeration for setting the I2S configuration + * channel_mode parameter to + * serial data wire number 1-3 (SD3). + */ +#define AFE_PORT_I2S_SD0 0x1 +#define AFE_PORT_I2S_SD1 0x2 +#define AFE_PORT_I2S_SD2 0x3 +#define AFE_PORT_I2S_SD3 0x4 +#define AFE_PORT_I2S_QUAD01 0x5 +#define AFE_PORT_I2S_QUAD23 0x6 +#define AFE_PORT_I2S_6CHS 0x7 +#define AFE_PORT_I2S_8CHS 0x8 +#define AFE_PORT_I2S_MONO 0x0 +#define AFE_PORT_I2S_STEREO 0x1 +#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 + +/* Payload of the #AFE_PARAM_ID_I2S_CONFIG + * command's (I2S configuration + * parameter). + */ +struct afe_param_id_i2s_cfg { + u32 i2s_cfg_minor_version; +/* Minor version used for tracking the version of the I2S + * configuration interface. + * Supported values: #AFE_API_VERSION_I2S_CONFIG + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + + u16 channel_mode; +/* I2S lines and multichannel operation. + * Supported values: + * - #AFE_PORT_I2S_SD0 + * - #AFE_PORT_I2S_SD1 + * - #AFE_PORT_I2S_SD2 + * - #AFE_PORT_I2S_SD3 + * - #AFE_PORT_I2S_QUAD01 + * - #AFE_PORT_I2S_QUAD23 + * - #AFE_PORT_I2S_6CHS + * - #AFE_PORT_I2S_8CHS + */ + + u16 mono_stereo; +/* Specifies mono or stereo. This applies only when + * a single I2S line is used. + * Supported values: + * - #AFE_PORT_I2S_MONO + * - #AFE_PORT_I2S_STEREO + */ + + u16 ws_src; +/* Word select source: internal or external. + * Supported values: + * - #AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL + * - #AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ + + u16 data_format; +/* data format + * Supported values: + * - #LINEAR_PCM_DATA + * - #NON_LINEAR_DATA + * - #LINEAR_PCM_DATA_PACKED_IN_60958 + * - #NON_LINEAR_DATA_PACKED_IN_60958 + */ + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +/* + * This param id is used to configure PCM interface + */ +#define AFE_PARAM_ID_PCM_CONFIG 0x0001020E +#define AFE_API_VERSION_PCM_CONFIG 0x1 +/* Enumeration for the auxiliary PCM synchronization signal + * provided by an external source. + */ + +#define AFE_PORT_PCM_SYNC_SRC_EXTERNAL 0x0 +/* Enumeration for the auxiliary PCM synchronization signal + * provided by an internal source. + */ +#define AFE_PORT_PCM_SYNC_SRC_INTERNAL 0x1 +/* Enumeration for the PCM configuration aux_mode parameter, + * which configures the auxiliary PCM interface to use + * short synchronization. + */ +#define AFE_PORT_PCM_AUX_MODE_PCM 0x0 +/* + * Enumeration for the PCM configuration aux_mode parameter, + * which configures the auxiliary PCM interface to use long + * synchronization. + */ +#define AFE_PORT_PCM_AUX_MODE_AUX 0x1 +/* + * Enumeration for setting the PCM configuration frame to 8. + */ +#define AFE_PORT_PCM_BITS_PER_FRAME_8 0x0 +/* + * Enumeration for setting the PCM configuration frame to 16. + */ +#define AFE_PORT_PCM_BITS_PER_FRAME_16 0x1 + +/* Enumeration for setting the PCM configuration frame to 32.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_32 0x2 + +/* Enumeration for setting the PCM configuration frame to 64.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_64 0x3 + +/* Enumeration for setting the PCM configuration frame to 128.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_128 0x4 + +/* Enumeration for setting the PCM configuration frame to 256.*/ +#define AFE_PORT_PCM_BITS_PER_FRAME_256 0x5 + +/* Enumeration for setting the PCM configuration + * quantype parameter to A-law with no padding. + */ +#define AFE_PORT_PCM_ALAW_NOPADDING 0x0 + +/* Enumeration for setting the PCM configuration quantype + * parameter to mu-law with no padding. + */ +#define AFE_PORT_PCM_MULAW_NOPADDING 0x1 +/* Enumeration for setting the PCM configuration quantype + * parameter to linear with no padding. + */ +#define AFE_PORT_PCM_LINEAR_NOPADDING 0x2 +/* Enumeration for setting the PCM configuration quantype + * parameter to A-law with padding. + */ +#define AFE_PORT_PCM_ALAW_PADDING 0x3 +/* Enumeration for setting the PCM configuration quantype + * parameter to mu-law with padding. + */ +#define AFE_PORT_PCM_MULAW_PADDING 0x4 +/* Enumeration for setting the PCM configuration quantype + * parameter to linear with padding. + */ +#define AFE_PORT_PCM_LINEAR_PADDING 0x5 +/* Enumeration for disabling the PCM configuration + * ctrl_data_out_enable parameter. + * The PCM block is the only master. + */ +#define AFE_PORT_PCM_CTRL_DATA_OE_DISABLE 0x0 +/* + * Enumeration for enabling the PCM configuration + * ctrl_data_out_enable parameter. The PCM block shares + * the signal with other masters. + */ +#define AFE_PORT_PCM_CTRL_DATA_OE_ENABLE 0x1 + +/* Payload of the #AFE_PARAM_ID_PCM_CONFIG command's + * (PCM configuration parameter). + */ + +struct afe_param_id_pcm_cfg { + u32 pcm_cfg_minor_version; +/* Minor version used for tracking the version of the AUX PCM + * configuration interface. + * Supported values: #AFE_API_VERSION_PCM_CONFIG + */ + + u16 aux_mode; +/* PCM synchronization setting. + * Supported values: + * - #AFE_PORT_PCM_AUX_MODE_PCM + * - #AFE_PORT_PCM_AUX_MODE_AUX + */ + + u16 sync_src; +/* Synchronization source. + * Supported values: + * - #AFE_PORT_PCM_SYNC_SRC_EXTERNAL + * - #AFE_PORT_PCM_SYNC_SRC_INTERNAL + */ + + u16 frame_setting; +/* Number of bits per frame. + * Supported values: + * - #AFE_PORT_PCM_BITS_PER_FRAME_8 + * - #AFE_PORT_PCM_BITS_PER_FRAME_16 + * - #AFE_PORT_PCM_BITS_PER_FRAME_32 + * - #AFE_PORT_PCM_BITS_PER_FRAME_64 + * - #AFE_PORT_PCM_BITS_PER_FRAME_128 + * - #AFE_PORT_PCM_BITS_PER_FRAME_256 + */ + + u16 quantype; +/* PCM quantization type. + * Supported values: + * - #AFE_PORT_PCM_ALAW_NOPADDING + * - #AFE_PORT_PCM_MULAW_NOPADDING + * - #AFE_PORT_PCM_LINEAR_NOPADDING + * - #AFE_PORT_PCM_ALAW_PADDING + * - #AFE_PORT_PCM_MULAW_PADDING + * - #AFE_PORT_PCM_LINEAR_PADDING + */ + + u16 ctrl_data_out_enable; +/* Specifies whether the PCM block shares the data-out + * signal to the drive with other masters. + * Supported values: + * - #AFE_PORT_PCM_CTRL_DATA_OE_DISABLE + * - #AFE_PORT_PCM_CTRL_DATA_OE_ENABLE + */ + u16 reserved; + /* This field must be set to zero. */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to 4 + */ + + u16 slot_number_mapping[4]; +/* Specifies the slot number for the each channel in + * multi channel scenario. + * Supported values: 1 to 32 + */ +} __packed; + +/* + * This param id is used to configure DIGI MIC interface + */ +#define AFE_PARAM_ID_DIGI_MIC_CONFIG 0x0001020F +/* This version information is used to handle the new + * additions to the config interface in future in backward + * compatible manner. + */ +#define AFE_API_VERSION_DIGI_MIC_CONFIG 0x1 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to left 0. + */ + +#define AFE_PORT_DIGI_MIC_MODE_LEFT0 0x1 + +/*Enumeration for setting the digital mic configuration + * channel_mode parameter to right 0. + */ + + +#define AFE_PORT_DIGI_MIC_MODE_RIGHT0 0x2 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to left 1. + */ + +#define AFE_PORT_DIGI_MIC_MODE_LEFT1 0x3 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to right 1. + */ + +#define AFE_PORT_DIGI_MIC_MODE_RIGHT1 0x4 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to stereo 0. + */ +#define AFE_PORT_DIGI_MIC_MODE_STEREO0 0x5 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to stereo 1. + */ + + +#define AFE_PORT_DIGI_MIC_MODE_STEREO1 0x6 + +/* Enumeration for setting the digital mic configuration + * channel_mode parameter to quad. + */ + +#define AFE_PORT_DIGI_MIC_MODE_QUAD 0x7 + +/* Payload of the #AFE_PARAM_ID_DIGI_MIC_CONFIG command's + * (DIGI MIC configuration + * parameter). + */ +struct afe_param_id_digi_mic_cfg { + u32 digi_mic_cfg_minor_version; +/* Minor version used for tracking the version of the DIGI Mic + * configuration interface. + * Supported values: #AFE_API_VERSION_DIGI_MIC_CONFIG + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 channel_mode; +/* Digital mic and multichannel operation. + * Supported values: + * - #AFE_PORT_DIGI_MIC_MODE_LEFT0 + * - #AFE_PORT_DIGI_MIC_MODE_RIGHT0 + * - #AFE_PORT_DIGI_MIC_MODE_LEFT1 + * - #AFE_PORT_DIGI_MIC_MODE_RIGHT1 + * - #AFE_PORT_DIGI_MIC_MODE_STEREO0 + * - #AFE_PORT_DIGI_MIC_MODE_STEREO1 + * - #AFE_PORT_DIGI_MIC_MODE_QUAD + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + */ +} __packed; + +/* +* This param id is used to configure HDMI interface +*/ +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_HDMI_CONFIG command, + * which configures a multichannel HDMI audio interface. + */ +struct afe_param_id_hdmi_multi_chan_audio_cfg { + u32 hdmi_cfg_minor_version; +/* Minor version used for tracking the version of the HDMI + * configuration interface. + * Supported values: #AFE_API_VERSION_HDMI_CONFIG + */ + +u16 dataype; +/* data type + * Supported values: + * - #LINEAR_PCM_DATA + * - #NON_LINEAR_DATA + * - #LINEAR_PCM_DATA_PACKED_IN_60958 + * - #NON_LINEAR_DATA_PACKED_IN_60958 + */ + +u16 channel_allocation; +/* HDMI channel allocation information for programming an HDMI + * frame. The default is 0 (Stereo). + * + * This information is defined in the HDMI standard, CEA 861-D + * (refer to @xhyperref{S1,[S1]}). The number of channels is also + * inferred from this parameter. +*/ + + +u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - 22050, 44100, 176400 for compressed streams + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 reserved; + /* This field must be set to zero. */ +} __packed; + +/* +* This param id is used to configure BT or FM(RIVA) interface +*/ +#define AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG 0x00010211 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_INTERNAL_BT_FM_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_INTERNAL_BT_FM_CONFIG + * command's BT voice/BT audio/FM configuration parameter. + */ +struct afe_param_id_internal_bt_fm_cfg { + u32 bt_fm_cfg_minor_version; +/* Minor version used for tracking the version of the BT and FM + * configuration interface. + * Supported values: #AFE_API_VERSION_INTERNAL_BT_FM_CONFIG + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to 2 + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K (only for BTSCO) + * - #AFE_PORT_SAMPLE_RATE_16K (only for BTSCO) + * - #AFE_PORT_SAMPLE_RATE_48K (FM and A2DP) + */ +} __packed; + +/* This param id is used to configure SLIMBUS interface using + * shared channel approach. + */ + + +#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 + +/* Enumeration for setting SLIMbus device ID 1. +*/ +#define AFE_SLIMBUS_DEVICE_1 0x0 + +/* Enumeration for setting SLIMbus device ID 2. +*/ +#define AFE_SLIMBUS_DEVICE_2 0x1 + +/* Enumeration for setting the SLIMbus data formats. +*/ +#define AFE_SB_DATA_FORMAT_NOT_INDICATED 0x0 + +/* Enumeration for setting the maximum number of streams per + * device. + */ + +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8 + +/* Payload of the #AFE_PORT_CMD_SLIMBUS_CONFIG command's SLIMbus + * port configuration parameter. + */ + +struct afe_param_id_slimbus_cfg { + u32 sb_cfg_minor_version; +/* Minor version used for tracking the version of the SLIMBUS + * configuration interface. + * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG + */ + + u16 slimbus_dev_id; +/* SLIMbus hardware device ID, which is required to handle + * multiple SLIMbus hardware blocks. + * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2 + */ + + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + + u16 data_format; +/* Data format supported by the SLIMbus hardware. The default is + * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the + * hardware does not perform any format conversions before the data + * transfer. + */ + + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + + u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +/* Mapping of shared channel IDs (128 to 255) to which the + * master port is to be connected. + * Shared_channel_mapping[i] represents the shared channel assigned + * for audio channel i in multichannel audio data. + */ + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ +} __packed; + +/* +* This param id is used to configure Real Time Proxy interface. +*/ +#define AFE_PARAM_ID_RT_PROXY_CONFIG 0x00010213 + +/* This version information is used to handle the new +* additions to the config interface in future in backward +* compatible manner. +*/ +#define AFE_API_VERSION_RT_PROXY_CONFIG 0x1 + +/* Payload of the #AFE_PARAM_ID_RT_PROXY_CONFIG + * command (real-time proxy port configuration parameter). + */ +struct afe_param_id_rt_proxy_port_cfg { + u32 rt_proxy_cfg_minor_version; +/* Minor version used for tracking the version of rt-proxy + * config interface. + */ + + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16 + */ + + u16 interleaved; +/* Specifies whether the data exchanged between the AFE + * interface and real-time port is interleaved. + * Supported values: - 0 -- Non-interleaved (samples from each + * channel are contiguous in the buffer) - 1 -- Interleaved + * (corresponding samples from each input channel are interleaved + * within the buffer) + */ + + + u16 frame_size; + /* Size of the frames that are used for PCM exchanges with this + * port. + * Supported values: > 0, in bytes + * For example, 5 ms buffers of 16 bits and 16 kHz stereo samples + * is 5 ms * 16 samples/ms * 2 bytes/sample * 2 channels = 320 + * bytes. + */ + u16 jitter_allowance; +/* Configures the amount of jitter that the port will allow. + * Supported values: > 0 + * For example, if +/-10 ms of jitter is anticipated in the timing + * of sending frames to the port, and the configuration is 16 kHz + * mono with 16-bit samples, this field is 10 ms * 16 samples/ms * 2 + * bytes/sample = 320. + */ + + u16 low_water_mark; +/* Low watermark in bytes (including all channels). + * Supported values: + * - 0 -- Do not send any low watermark events + * - > 0 -- Low watermark for triggering an event + * If the number of bytes in an internal circular buffer is lower + * than this low_water_mark parameter, a LOW_WATER_MARK event is + * sent to applications (via the #AFE_EVENT_RT_PROXY_PORT_STATUS + * event). + * Use of watermark events is optional for debugging purposes. + */ + + u16 high_water_mark; +/* High watermark in bytes (including all channels). + * Supported values: + * - 0 -- Do not send any high watermark events + * - > 0 -- High watermark for triggering an event + * If the number of bytes in an internal circular buffer exceeds + * TOTAL_CIRC_BUF_SIZE minus high_water_mark, a high watermark event + * is sent to applications (via the #AFE_EVENT_RT_PROXY_PORT_STATUS + * event). + * The use of watermark events is optional and for debugging + * purposes. + */ + + + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + */ + + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + + u16 reserved; + /* For 32 bit alignment. */ +} __packed; + +union afe_port_config { + struct afe_param_id_pcm_cfg pcm; + struct afe_param_id_i2s_cfg i2s; + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; + struct afe_param_id_slimbus_cfg slim_sch; + struct afe_param_id_rt_proxy_port_cfg rtproxy; +} __packed; + +struct afe_audioif_config_command { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + union afe_port_config port; +} __packed; + +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 + +/* Payload of the #AFE_PORT_CMD_DEVICE_START.*/ +struct afe_port_cmd_device_start { + struct apr_hdr hdr; + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. An even + * number represents the Rx direction, and an odd number represents + * the Tx direction. + */ + + + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ + +} __packed; + +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 + +/* Payload of the #AFE_PORT_CMD_DEVICE_STOP. +*/ +struct afe_port_cmd_device_stop { + struct apr_hdr hdr; + u16 port_id; +/* Port interface and direction (Rx or Tx) to start. An even + * number represents the Rx direction, and an odd number represents + * the Tx direction. + */ + + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed; + +#define AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS 0x000100EA + +/* Memory map regions command payload used by the + * #AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS . + * This structure allows clients to map multiple shared memory + * regions in a single command. Following this structure are + * num_regions of afe_service_shared_map_region_payload. + */ +struct afe_service_cmd_shared_mem_map_regions { + struct apr_hdr hdr; +u16 mem_pool_id; +/* Type of memory on which this memory region is mapped. + * Supported values: + * - #ADSP_MEMORY_MAP_EBI_POOL + * - #ADSP_MEMORY_MAP_SMI_POOL + * - #ADSP_MEMORY_MAP_SHMEM8_4K_POOL + * - Other values are reserved + * + * The memory pool ID implicitly defines the characteristics of the + * memory. Characteristics may include alignment type, permissions, + * etc. + * + * ADSP_MEMORY_MAP_EBI_POOL is External Buffer Interface type memory + * ADSP_MEMORY_MAP_SMI_POOL is Shared Memory Interface type memory + * ADSP_MEMORY_MAP_SHMEM8_4K_POOL is shared memory, byte + * addressable, and 4 KB aligned. + */ + + + u16 num_regions; +/* Number of regions to map. + * Supported values: + * - Any value greater than zero + */ + + u32 property_flag; +/* Configures one common property for all the regions in the + * payload. + * + * Supported values: - 0x00000000 to 0x00000001 + * + * b0 - bit 0 indicates physical or virtual mapping 0 Shared memory + * address provided in afe_service_shared_map_region_payloadis a + * physical address. The shared memory needs to be mapped( hardware + * TLB entry) and a software entry needs to be added for internal + * book keeping. + * + * 1 Shared memory address provided in + * afe_service_shared_map_region_payloadis a virtual address. The + * shared memory must not be mapped (since hardware TLB entry is + * already available) but a software entry needs to be added for + * internal book keeping. This can be useful if two services with in + * ADSP is communicating via APR. They can now directly communicate + * via the Virtual address instead of Physical address. The virtual + * regions must be contiguous. num_regions must be 1 in this case. + * + * b31-b1 - reserved bits. must be set to zero + */ + + +} __packed; +/* Map region payload used by the + * afe_service_shared_map_region_payloadstructure. + */ +struct afe_service_shared_map_region_payload { + u32 shm_addr_lsw; +/* least significant word of starting address in the memory + * region to map. It must be contiguous memory, and it must be 4 KB + * aligned. + * Supported values: - Any 32 bit value + */ + + + u32 shm_addr_msw; +/* most significant word of startng address in the memory region + * to map. For 32 bit shared memory address, this field must be set + * to zero. For 36 bit shared memory address, bit31 to bit 4 must be + * set to zero + * + * Supported values: - For 32 bit shared memory address, this field + * must be set to zero. - For 36 bit shared memory address, bit31 to + * bit 4 must be set to zero - For 64 bit shared memory address, any + * 32 bit value + */ + + + u32 mem_size_bytes; +/* Number of bytes in the region. The aDSP will always map the + * regions as virtual contiguous memory, but the memory size must be + * in multiples of 4 KB to avoid gaps in the virtually contiguous + * mapped memory. + * + * Supported values: - multiples of 4KB + */ + +} __packed; + +#define AFE_SERVICE_CMDRSP_SHARED_MEM_MAP_REGIONS 0x000100EB +struct afe_service_cmdrsp_shared_mem_map_regions { + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned iff AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS command is + * successful. In the case of failure , a generic APR error response + * is returned to the client. + * + * Supported Values: - Any 32 bit value + */ + +} __packed; +#define AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS 0x000100EC +/* Memory unmap regions command payload used by the + * #AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS + * + * This structure allows clients to unmap multiple shared memory + * regions in a single command. + */ + + +struct afe_service_cmd_shared_mem_unmap_regions { + struct apr_hdr hdr; +u32 mem_map_handle; +/* memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands + * + * Supported Values: + * - Any 32 bit value + */ +} __packed; + +#define AFE_PORT_CMD_GET_PARAM_V2 0x000100F0 + +/* Payload of the #AFE_PORT_CMD_GET_PARAM_V2 command, + * which queries for one post/preprocessing parameter of a + * stream. + */ +struct afe_port_cmd_get_param_v2 { + + struct apr_hdr hdr; +u16 port_id; +/* Port interface and direction (Rx or Tx) to start. */ + + u16 payload_size; +/* Maximum data size of the parameter ID/module ID combination. + * This is a multiple of four bytes + * Supported values: > 0 + */ + + u32 payload_address_lsw; +/* LSW of 64 bit Payload address. Address should be 32-byte, + * 4kbyte aligned and must be contig memory. + */ + + + u32 payload_address_msw; +/* MSW of 64 bit Payload address. In case of 32-bit shared + * memory address, this field must be set to zero. In case of 36-bit + * shared memory address, bit-4 to bit-31 must be set to zero. + * Address should be 32-byte, 4kbyte aligned and must be contiguous + * memory. + */ + + u32 mem_map_handle; +/* Memory map handle returned by + * AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS commands. + * Supported Values: - NULL -- Message. The parameter data is + * in-band. - Non-NULL -- The parameter data is Out-band.Pointer to + * - the physical address in shared memory of the payload data. + * For detailed payload content, see the afe_port_param_data_v2 + * structure + */ + + + u32 module_id; +/* ID of the module to be queried. + * Supported values: Valid module ID + */ + + u32 param_id; +/* ID of the parameter to be queried. + * Supported values: Valid parameter ID + */ +} __packed; + +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 + +/* Payload of the #AFE_PORT_CMDRSP_GET_PARAM_V2 message, which + * responds to an #AFE_PORT_CMD_GET_PARAM_V2 command. + * + * Immediately following this structure is the parameters structure + * (afe_port_param_data) containing the response(acknowledgment) + * parameter payload. This payload is included for an in-band + * scenario. For an address/shared memory-based set parameter, this + * payload is not needed. + */ + + +struct afe_port_cmdrsp_get_param_v2 { + u32 status; +} __packed; + +/* adsp_afe_service_commands.h */ + +#define ADSP_MEMORY_MAP_EBI_POOL 0 + +#define ADSP_MEMORY_MAP_SMI_POOL 1 +#define ADSP_MEMORY_MAP_IMEM_POOL 2 +#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3 +/* +* Definition of virtual memory flag +*/ +#define ADSP_MEMORY_MAP_VIRTUAL_MEMORY 1 + +/* +* Definition of physical memory flag +*/ +#define ADSP_MEMORY_MAP_PHYSICAL_MEMORY 0 + + +#define DEFAULT_COPP_TOPOLOGY 0x00010be3 +#define DEFAULT_POPP_TOPOLOGY 0x00010be4 +#define VPM_TX_SM_ECNS_COPP_TOPOLOGY 0x00010F71 +#define VPM_TX_DM_FLUENCE_COPP_TOPOLOGY 0x00010F72 +#define VPM_TX_QMIC_FLUENCE_COPP_TOPOLOGY 0x00010F75 + +/* Memory map regions command payload used by the + * #ASM_CMD_SHARED_MEM_MAP_REGIONS ,#ADM_CMD_SHARED_MEM_MAP_REGIONS + * commands. + * + * This structure allows clients to map multiple shared memory + * regions in a single command. Following this structure are + * num_regions of avs_shared_map_region_payload. + */ + + +struct avs_cmd_shared_mem_map_regions { + struct apr_hdr hdr; + u16 mem_pool_id; +/* Type of memory on which this memory region is mapped. + * + * Supported values: - #ADSP_MEMORY_MAP_EBI_POOL - + * #ADSP_MEMORY_MAP_SMI_POOL - #ADSP_MEMORY_MAP_IMEM_POOL + * (unsupported) - #ADSP_MEMORY_MAP_SHMEM8_4K_POOL - Other values + * are reserved + * + * The memory ID implicitly defines the characteristics of the + * memory. Characteristics may include alignment type, permissions, + * etc. + * + * SHMEM8_4K is shared memory, byte addressable, and 4 KB aligned. + */ + + + u16 num_regions; + /* Number of regions to map.*/ + + u32 property_flag; +/* Configures one common property for all the regions in the + * payload. No two regions in the same memory map regions cmd can + * have differnt property. Supported values: - 0x00000000 to + * 0x00000001 + * + * b0 - bit 0 indicates physical or virtual mapping 0 shared memory + * address provided in avs_shared_map_regions_payload is physical + * address. The shared memory needs to be mapped( hardware TLB + * entry) + * + * and a software entry needs to be added for internal book keeping. + * + * 1 Shared memory address provided in MayPayload[usRegions] is + * virtual address. The shared memory must not be mapped (since + * hardware TLB entry is already available) but a software entry + * needs to be added for internal book keeping. This can be useful + * if two services with in ADSP is communicating via APR. They can + * now directly communicate via the Virtual address instead of + * Physical address. The virtual regions must be contiguous. + * + * b31-b1 - reserved bits. must be set to zero + */ + +} __packed; + +struct avs_shared_map_region_payload { + u32 shm_addr_lsw; +/* least significant word of shared memory address of the memory + * region to map. It must be contiguous memory, and it must be 4 KB + * aligned. + */ + + u32 shm_addr_msw; +/* most significant word of shared memory address of the memory + * region to map. For 32 bit shared memory address, this field must + * tbe set to zero. For 36 bit shared memory address, bit31 to bit 4 + * must be set to zero + */ + + u32 mem_size_bytes; +/* Number of bytes in the region. + * + * The aDSP will always map the regions as virtual contiguous + * memory, but the memory size must be in multiples of 4 KB to avoid + * gaps in the virtually contiguous mapped memory. + */ + +} __packed; + +struct avs_cmd_shared_mem_unmap_regions { + struct apr_hdr hdr; + u32 mem_map_handle; +/* memory map handle returned by ASM_CMD_SHARED_MEM_MAP_REGIONS + * , ADM_CMD_SHARED_MEM_MAP_REGIONS, commands + */ + +} __packed; + +/* Memory map command response payload used by the + * #ASM_CMDRSP_SHARED_MEM_MAP_REGIONS + * ,#ADM_CMDRSP_SHARED_MEM_MAP_REGIONS + */ + + +struct avs_cmdrsp_shared_mem_map_regions { + u32 mem_map_handle; +/* A memory map handle encapsulating shared memory attributes is + * returned + */ + +} __packed; + +/*adsp_audio_memmap_api.h*/ + +/* ASM related data structures */ +struct asm_wma_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +} __packed; + +struct asm_wmapro_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +} __packed; + +struct asm_aac_cfg { + u16 format; + u16 aot; + u16 ep_config; + u16 section_data_resilience; + u16 scalefactor_data_resilience; + u16 spectral_data_resilience; + u16 ch_cfg; + u16 reserved; + u32 sample_rate; +} __packed; + +struct asm_softpause_params { + u32 enable; + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +struct asm_softvolume_params { + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +#define ASM_END_POINT_DEVICE_MATRIX 0 +/* Front left channel. */ +#define PCM_CHANNEL_FL 1 + +/* Front right channel. */ +#define PCM_CHANNEL_FR 2 + +/* Front center channel. */ +#define PCM_CHANNEL_FC 3 + +/* Left surround channel.*/ +#define PCM_CHANNEL_LS 4 + +/* Right surround channel.*/ +#define PCM_CHANNEL_RS 5 + +/* Low frequency effect channel. */ +#define PCM_CHANNEL_LFE 6 + +/* Center surround channel; Rear center channel. */ +#define PCM_CHANNEL_CS 7 + +/* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_LB 8 + +/* Right back channel; Rear right channel. */ +#define PCM_CHANNEL_RB 9 + +/* Top surround channel. */ +#define PCM_CHANNELS 10 + +/* Center vertical height channel.*/ +#define PCM_CHANNEL_CVH 11 + +/* Mono surround channel.*/ +#define PCM_CHANNEL_MS 12 + +/* Front left of center. */ +#define PCM_CHANNEL_FLC 13 + +/* Front right of center. */ +#define PCM_CHANNEL_FRC 14 + +/* Rear left of center. */ +#define PCM_CHANNEL_RLC 15 + +/* Rear right of center. */ +#define PCM_CHANNEL_RRC 16 + +#define PCM_FORMAT_MAX_NUM_CHANNEL 8 + +#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5 + +#define ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT 0x00010BE4 + +#define ASM_MEDIA_FMT_EVRCB_FS 0x00010BEF + +#define ASM_MEDIA_FMT_EVRCWB_FS 0x00010BF0 + +#define ASM_MAX_EQ_BANDS 12 + +#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98 + +struct asm_data_cmd_media_fmt_update_v2 { +u32 fmt_blk_size; + /* Media format block size in bytes.*/ +} __packed; + +struct asm_multi_channel_pcm_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + + u16 num_channels; + /* Number of channels. Supported values: 1 to 8 */ + u16 bits_per_sample; +/* Number of bits per sample per channel. * Supported values: + * 16, 24 * When used for playback, the client must send 24-bit + * samples packed in 32-bit words. The 24-bit samples must be placed + * in the most significant 24 bits of the 32-bit word. When used for + * recording, the aDSP sends 24-bit samples packed in 32-bit words. + * The 24-bit samples are placed in the most significant 24 bits of + * the 32-bit word. + */ + + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 2000 to 48000 + */ + + u16 is_signed; + /* Flag that indicates the samples are signed (1). */ + + u16 reserved; + /* reserved field for 32 bit alignment. must be set to zero. */ + + u8 channel_mapping[8]; +/* Channel array of size 8. + * Supported values: + * - #PCM_CHANNEL_L + * - #PCM_CHANNEL_R + * - #PCM_CHANNEL_C + * - #PCM_CHANNEL_LS + * - #PCM_CHANNEL_RS + * - #PCM_CHANNEL_LFE + * - #PCM_CHANNEL_CS + * - #PCM_CHANNEL_LB + * - #PCM_CHANNEL_RB + * - #PCM_CHANNELS + * - #PCM_CHANNEL_CVH + * - #PCM_CHANNEL_MS + * - #PCM_CHANNEL_FLC + * - #PCM_CHANNEL_FRC + * - #PCM_CHANNEL_RLC + * - #PCM_CHANNEL_RRC + * + * Channel[i] mapping describes channel I. Each element i of the + * array describes channel I inside the buffer where 0 @le I < + * num_channels. An unused channel is set to zero. + */ +} __packed; + +struct asm_stream_cmd_set_encdec_param { + u32 param_id; + /* ID of the parameter. */ + + u32 param_size; +/* Data size of this parameter, in bytes. The size is a multiple + * of 4 bytes. + */ + +} __packed; + +struct asm_enc_cfg_blk_param_v2 { + u32 frames_per_buf; +/* Number of encoded frames to pack into each buffer. + * + * @note1hang This is only guidance information for the aDSP. The + * number of encoded frames put into each buffer (specified by the + * client) is less than or equal to this number. + */ + + u32 enc_cfg_blk_size; +/* Size in bytes of the encoder configuration block that follows + * this member. + */ + +} __packed; + +/* @brief Multichannel PCM encoder configuration structure used + * in the #ASM_STREAM_CMD_OPEN_READ_V2 command. + */ + +struct asm_multi_channel_pcm_enc_cfg_v2 { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + uint16_t num_channels; +/*< Number of PCM channels. + * + * Supported values: - 0 -- Native mode - 1 -- 8 Native mode + * indicates that encoding must be performed with the number of + * channels at the input. + */ + + uint16_t bits_per_sample; +/*< Number of bits per sample per channel. + * Supported values: 16, 24 + */ + + uint32_t sample_rate; +/*< Number of samples per second (in Hertz). + * + * Supported values: 0, 8000 to 48000 A value of 0 indicates the + * native sampling rate. Encoding is performed at the input sampling + * rate. + */ + + uint16_t is_signed; +/*< Specifies whether the samples are signed (1). Currently, + * only signed samples are supported. + */ + + uint16_t reserved; +/*< reserved field for 32 bit alignment. must be set to zero.*/ + + + uint8_t channel_mapping[8]; +} __packed; + +#define ASM_MEDIA_FMT_MP3 0x00010BE9 +#define ASM_MEDIA_FMT_AAC_V2 0x00010DA6 + +/* @xreflabel + * {hdr:AsmMediaFmtDolbyAac} Media format ID for the + * Dolby AAC decoder. This format ID is be used if the client wants + * to use the Dolby AAC decoder to decode MPEG2 and MPEG4 AAC + * contents. + */ + +#define ASM_MEDIA_FMT_DOLBY_AAC 0x00010D86 + +/* Enumeration for the audio data transport stream AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS 0 + +/* Enumeration for low overhead audio stream AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_LOAS 1 + +/* Enumeration for the audio data interchange format + * AAC format. + */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADIF 2 + +/* Enumeration for the raw AAC format. */ +#define ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW 3 + +#define ASM_MEDIA_FMT_AAC_AOT_LC 2 +#define ASM_MEDIA_FMT_AAC_AOT_SBR 5 +#define ASM_MEDIA_FMT_AAC_AOT_PS 29 +#define ASM_MEDIA_FMT_AAC_AOT_BSAC 22 + +struct asm_aac_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + + u16 aac_fmt_flag; +/* Bitstream format option. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_LOAS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADIF + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW + */ + + u16 audio_objype; +/* Audio Object Type (AOT) present in the AAC stream. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_AOT_LC + * - #ASM_MEDIA_FMT_AAC_AOT_SBR + * - #ASM_MEDIA_FMT_AAC_AOT_BSAC + * - #ASM_MEDIA_FMT_AAC_AOT_PS + * - Otherwise -- Not supported + */ + + u16 channel_config; +/* Number of channels present in the AAC stream. + * Supported values: + * - 1 -- Mono + * - 2 -- Stereo + * - 6 -- 5.1 content + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero. */ + + u16 total_size_of_PCE_bits; +/* greater or equal to zero. * -In case of RAW formats and + * channel config = 0 (PCE), client can send * the bit stream + * containing PCE immediately following this structure * (in-band). + * -This number does not include bits included for 32 bit alignment. + * -If zero, then the PCE info is assumed to be available in the + * audio -bit stream & not in-band. + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * + * Supported values: 8000, 11025, 12000, 16000, 22050, 24000, 32000, + * 44100, 48000 + * + * This field must be equal to the sample rate of the AAC-LC + * decoder's output. - For MP4 or 3GP containers, this is indicated + * by the samplingFrequencyIndex field in the AudioSpecificConfig + * element. - For ADTS format, this is indicated by the + * samplingFrequencyIndex in the ADTS fixed header. - For ADIF + * format, this is indicated by the samplingFrequencyIndex in the + * program_config_element present in the ADIF header. + */ + +} __packed; + +struct asm_aac_enc_cfg_v2 { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u32 bit_rate; + /* Encoding rate in bits per second. */ + u32 enc_mode; +/* Encoding mode. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_AOT_LC + * - #ASM_MEDIA_FMT_AAC_AOT_SBR + * - #ASM_MEDIA_FMT_AAC_AOT_PS + */ + u16 aac_fmt_flag; +/* AAC format flag. + * Supported values: + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_ADTS + * - #ASM_MEDIA_FMT_AAC_FORMAT_FLAG_RAW + */ + u16 channel_cfg; +/* Number of channels to encode. + * Supported values: + * - 0 -- Native mode + * - 1 -- Mono + * - 2 -- Stereo + * - Other values are not supported. + * @note1hang The eAAC+ encoder mode supports only stereo. + * Native mode indicates that encoding must be performed with the + * number of channels at the input. + * The number of channels must not change during encoding. + */ + + u32 sample_rate; +/* Number of samples per second. + * Supported values: - 0 -- Native mode - For other values, + * Native mode indicates that encoding must be performed with the + * sampling rate at the input. + * The sampling rate must not change during encoding. + */ + +} __packed; + +#define ASM_MEDIA_FMT_AMRNB_FS 0x00010BEB + +/* Enumeration for 4.75 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MR475 0 + +/* Enumeration for 5.15 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MR515 1 + +/* Enumeration for 5.90 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR59 2 + +/* Enumeration for 6.70 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR67 3 + +/* Enumeration for 7.40 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR74 4 + +/* Enumeration for 7.95 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR795 5 + +/* Enumeration for 10.20 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR102 6 + +/* Enumeration for 12.20 kbps AMR-NB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_MMR122 7 + +/* Enumeration for AMR-NB Discontinuous Transmission mode off. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF 0 + +/* Enumeration for AMR-NB DTX mode VAD1. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 1 + +/* Enumeration for AMR-NB DTX mode VAD2. */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD2 2 + +/* Enumeration for AMR-NB DTX mode auto. + */ +#define ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_AUTO 3 + +struct asm_amrnb_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u16 enc_mode; +/* AMR-NB encoding rate. + * Supported values: + * Use the ASM_MEDIA_FMT_AMRNB_FS_ENCODE_MODE_* + * macros + */ + + u16 dtx_mode; +/* Specifies whether DTX mode is disabled or enabled. + * Supported values: + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 + */ +} __packed; + +#define ASM_MEDIA_FMT_AMRWB_FS 0x00010BEC + +/* Enumeration for 6.6 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR66 0 + +/* Enumeration for 8.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR885 1 + +/* Enumeration for 12.65 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1265 2 + +/* Enumeration for 14.25 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1425 3 + +/* Enumeration for 15.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1585 4 + +/* Enumeration for 18.25 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1825 5 + +/* Enumeration for 19.85 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR1985 6 + +/* Enumeration for 23.05 kbps AMR-WB Encoding mode. */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR2305 7 + +/* Enumeration for 23.85 kbps AMR-WB Encoding mode. + */ +#define ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_MR2385 8 + +struct asm_amrwb_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u16 enc_mode; +/* AMR-WB encoding rate. + * Suupported values: + * Use the ASM_MEDIA_FMT_AMRWB_FS_ENCODE_MODE_* + * macros + */ + + u16 dtx_mode; +/* Specifies whether DTX mode is disabled or enabled. + * Supported values: + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_OFF + * - #ASM_MEDIA_FMT_AMRNB_FS_DTX_MODE_VAD1 + */ +} __packed; + +#define ASM_MEDIA_FMT_V13K_FS 0x00010BED + +/* Enumeration for 14.4 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1440 0 + +/* Enumeration for 12.2 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1220 1 + +/* Enumeration for 11.2 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1120 2 + +/* Enumeration for 9.0 kbps V13K Encoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR90 3 + +/* Enumeration for 7.2 kbps V13K eEncoding mode. */ +#define ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR720 4 + +/* Enumeration for 1/8 vocoder rate.*/ +#define ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE 1 + +/* Enumeration for 1/4 vocoder rate. */ +#define ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE 2 + +/* Enumeration for 1/2 vocoder rate. */ +#define ASM_MEDIA_FMT_VOC_HALF_RATE 3 + +/* Enumeration for full vocoder rate. + */ +#define ASM_MEDIA_FMT_VOC_FULL_RATE 4 + +struct asm_v13k_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 max_rate; +/* Maximum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 min_rate; +/* Minimum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 reduced_rate_cmd; +/* Reduced rate command, used to change + * the average bitrate of the V13K + * vocoder. + * Supported values: + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1440 (Default) + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1220 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR1120 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR90 + * - #ASM_MEDIA_FMT_V13K_FS_ENCODE_MODE_MR720 + */ + + u16 rate_mod_cmd; +/* Rate modulation command. Default = 0. + *- If bit 0=1, rate control is enabled. + *- If bit 1=1, the maximum number of consecutive full rate + * frames is limited with numbers supplied in + * bits 2 to 10. + *- If bit 1=0, the minimum number of non-full rate frames + * in between two full rate frames is forced to + * the number supplied in bits 2 to 10. In both cases, if necessary, + * half rate is used to substitute full rate. - Bits 15 to 10 are + * reserved and must all be set to zero. + */ + +} __packed; + +#define ASM_MEDIA_FMT_EVRC_FS 0x00010BEE + +/* EVRC encoder configuration structure used in the + * #ASM_STREAM_CMD_OPEN_READ_V2 command. + */ +struct asm_evrc_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 max_rate; +/* Maximum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 min_rate; +/* Minimum allowed encoder frame rate. + * Supported values: + * - #ASM_MEDIA_FMT_VOC_ONE_EIGHTH_RATE + * - #ASM_MEDIA_FMT_VOC_ONE_FOURTH_RATE + * - #ASM_MEDIA_FMT_VOC_HALF_RATE + * - #ASM_MEDIA_FMT_VOC_FULL_RATE + */ + + u16 rate_mod_cmd; +/* Rate modulation command. Default: 0. + * - If bit 0=1, rate control is enabled. + * - If bit 1=1, the maximum number of consecutive full rate frames + * is limited with numbers supplied in bits 2 to 10. + * + * - If bit 1=0, the minimum number of non-full rate frames in + * between two full rate frames is forced to the number supplied in + * bits 2 to 10. In both cases, if necessary, half rate is used to + * substitute full rate. + * + * - Bits 15 to 10 are reserved and must all be set to zero. + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero. */ +} __packed; + +#define ASM_MEDIA_FMT_WMA_V10PRO_V2 0x00010DA7 + +struct asm_wmaprov10_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + + u16 fmtag; +/* WMA format type. + * Supported values: + * - 0x162 -- WMA 9 Pro + * - 0x163 -- WMA 9 Pro Lossless + * - 0x166 -- WMA 10 Pro + * - 0x167 -- WMA 10 Pro Lossless + */ + + u16 num_channels; +/* Number of channels encoded in the input stream. + * Supported values: 1 to 8 + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 11025, 16000, 22050, 32000, 44100, 48000, + * 88200, 96000 + */ + + u32 avg_bytes_per_sec; +/* Bitrate expressed as the average bytes per second. + * Supported values: 2000 to 96000 + */ + + u16 blk_align; +/* Size of the bitstream packet size in bytes. WMA Pro files + * have a payload of one block per bitstream packet. + * Supported values: @le 13376 + */ + + u16 bits_per_sample; +/* Number of bits per sample in the encoded WMA stream. + * Supported values: 16, 24 + */ + + u32 channel_mask; +/* Bit-packed double word (32-bits) that indicates the + * recommended speaker positions for each source channel. + */ + + u16 enc_options; +/* Bit-packed word with values that indicate whether certain + * features of the bitstream are used. + * Supported values: - 0x0001 -- ENCOPT3_PURE_LOSSLESS - 0x0006 -- + * ENCOPT3_FRM_SIZE_MOD - 0x0038 -- ENCOPT3_SUBFRM_DIV - 0x0040 -- + * ENCOPT3_WRITE_FRAMESIZE_IN_HDR - 0x0080 -- + * ENCOPT3_GENERATE_DRC_PARAMS - 0x0100 -- ENCOPT3_RTMBITS + */ + + + u16 usAdvancedEncodeOpt; + /* Advanced encoding option. */ + + u32 advanced_enc_options2; + /* Advanced encoding option 2. */ + +} __packed; + +#define ASM_MEDIA_FMT_WMA_V9_V2 0x00010DA8 +struct asm_wmastdv9_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + u16 fmtag; +/* WMA format tag. + * Supported values: 0x161 (WMA 9 standard) + */ + + u16 num_channels; +/* Number of channels in the stream. + * Supported values: 1, 2 + */ + + u32 sample_rate; +/* Number of samples per second (in Hertz). + * Supported values: 48000 + */ + + u32 avg_bytes_per_sec; + /* Bitrate expressed as the average bytes per second. */ + + u16 blk_align; +/* Block align. All WMA files with a maximum packet size of + * 13376 are supported. + */ + + + u16 bits_per_sample; +/* Number of bits per sample in the output. + * Supported values: 16 + */ + + u32 channel_mask; +/* Channel mask. + * Supported values: + * - 3 -- Stereo (front left/front right) + * - 4 -- Mono (center) + */ + + u16 enc_options; + /* Options used during encoding. */ + +} __packed; + +#define ASM_MEDIA_FMT_WMA_V8 0x00010D91 + +struct asm_wmastdv8_enc_cfg { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 bit_rate; + /* Encoding rate in bits per second. */ + + u32 sample_rate; +/* Number of samples per second. + * + * Supported values: + * - 0 -- Native mode + * - Other Supported values are 22050, 32000, 44100, and 48000. + * + * Native mode indicates that encoding must be performed with the + * sampling rate at the input. + * The sampling rate must not change during encoding. + */ + + u16 channel_cfg; +/* Number of channels to encode. + * Supported values: + * - 0 -- Native mode + * - 1 -- Mono + * - 2 -- Stereo + * - Other values are not supported. + * + * Native mode indicates that encoding must be performed with the + * number of channels at the input. + * The number of channels must not change during encoding. + */ + + u16 reserved; + /* Reserved. Clients must set this field to zero.*/ + } __packed; + +#define ASM_MEDIA_FMT_AMR_WB_PLUS_V2 0x00010DA9 + +struct asm_amrwbplus_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmtblk; + u32 amr_frame_fmt; +/* AMR frame format. + * Supported values: + * - 6 -- Transport Interface Format (TIF) + * - Any other value -- File storage format (FSF) + * + * TIF stream contains 2-byte header for each frame within the + * superframe. FSF stream contains one 2-byte header per superframe. + */ + +} __packed; + +#define ASM_MEDIA_FMT_AC3_DEC 0x00010BF6 +#define ASM_MEDIA_FMT_EAC3_DEC 0x00010C3C +#define ASM_MEDIA_FMT_DTS 0x00010D88 + +/* Media format ID for adaptive transform acoustic coding. This + * ID is used by the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED command + * only. + */ + +#define ASM_MEDIA_FMT_ATRAC 0x00010D89 + +/* Media format ID for metadata-enhanced audio transmission. + * This ID is used by the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED + * command only. + */ + +#define ASM_MEDIA_FMT_MAT 0x00010D8A + +/* adsp_media_fmt.h */ + +#define ASM_DATA_CMD_WRITE_V2 0x00010DAB + +struct asm_data_cmd_write_v2 { + struct apr_hdr hdr; + u32 buf_addr_lsw; +/* The 64 bit address msw-lsw should be a valid, mapped address. + * 64 bit address should be a multiple of 32 bytes + */ + + u32 buf_addr_msw; +/* The 64 bit address msw-lsw should be a valid, mapped address. + * 64 bit address should be a multiple of 32 bytes. + * -Address of the buffer containing the data to be decoded. + * The buffer should be aligned to a 32 byte boundary. + * -In the case of 32 bit Shared memory address, msw field must + * -be set to zero. + * -In the case of 36 bit shared memory address, bit 31 to bit 4 + * -of msw must be set to zero. + */ + u32 mem_map_handle; +/* memory map handle returned by DSP through + * ASM_CMD_SHARED_MEM_MAP_REGIONS command + */ + u32 buf_size; +/* Number of valid bytes available in the buffer for decoding. The + * first byte starts at buf_addr. + */ + + u32 seq_id; + /* Optional buffer sequence ID. */ + + u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first buffer sample. + */ + + u32 timestamp_msw; +/* Upper 32 bits of the 64-bit session time in microseconds of the + * first buffer sample. + */ + + u32 flags; +/* Bitfield of flags. + * Supported values for bit 31: + * - 1 -- Valid timestamp. + * - 0 -- Invalid timestamp. + * - Use #ASM_BIT_MASKIMESTAMP_VALID_FLAG as the bitmask and + * #ASM_SHIFTIMESTAMP_VALID_FLAG as the shift value to set this bit. + * Supported values for bit 30: + * - 1 -- Last buffer. + * - 0 -- Not the last buffer. + * + * Supported values for bit 29: + * - 1 -- Continue the timestamp from the previous buffer. + * - 0 -- Timestamp of the current buffer is not related + * to the timestamp of the previous buffer. + * - Use #ASM_BIT_MASKS_CONTINUE_FLAG and #ASM_SHIFTS_CONTINUE_FLAG + * to set this bit. + * + * Supported values for bit 4: + * - 1 -- End of the frame. + * - 0 -- Not the end of frame, or this information is not known. + * - Use #ASM_BIT_MASK_EOF_FLAG as the bitmask and #ASM_SHIFT_EOF_FLAG + * as the shift value to set this bit. + * + * All other bits are reserved and must be set to 0. + * + * If bit 31=0 and bit 29=1: The timestamp of the first sample in + * this buffer continues from the timestamp of the last sample in + * the previous buffer. If there is no previous buffer (i.e., this + * is the first buffer sent after opening the stream or after a + * flush operation), or if the previous buffer does not have a valid + * timestamp, the samples in the current buffer also do not have a + * valid timestamp. They are played out as soon as possible. + * + * + * If bit 31=0 and bit 29=0: No timestamp is associated with the + * first sample in this buffer. The samples are played out as soon + * as possible. + * + * + * If bit 31=1 and bit 29 is ignored: The timestamp specified in + * this payload is honored. + * + * + * If bit 30=0: Not the last buffer in the stream. This is useful + * in removing trailing samples. + * + * + * For bit 4: The client can set this flag for every buffer sent in + * which the last byte is the end of a frame. If this flag is set, + * the buffer can contain data from multiple frames, but it should + * always end at a frame boundary. Restrictions allow the aDSP to + * detect an end of frame without requiring additional processing. + */ + +} __packed; + +#define ASM_DATA_CMD_READ_V2 0x00010DAC + +struct asm_data_cmd_read_v2 { + struct apr_hdr hdr; + u32 buf_addr_lsw; +/* the 64 bit address msw-lsw should be a valid mapped address + * and should be a multiple of 32 bytes + */ + + + u32 buf_addr_msw; +/* the 64 bit address msw-lsw should be a valid mapped address + * and should be a multiple of 32 bytes. +* - Address of the buffer where the DSP puts the encoded data, +* potentially, at an offset specified by the uOffset field in +* ASM_DATA_EVENT_READ_DONE structure. The buffer should be aligned +* to a 32 byte boundary. +*- In the case of 32 bit Shared memory address, msw field must +*- be set to zero. +*- In the case of 36 bit shared memory address, bit 31 to bit +*- 4 of msw must be set to zero. +*/ + u32 mem_map_handle; +/* memory map handle returned by DSP through + * ASM_CMD_SHARED_MEM_MAP_REGIONS command. + */ + + u32 buf_size; +/* Number of bytes available for the aDSP to write. The aDSP + * starts writing from buf_addr. + */ + + u32 seq_id; + /* Optional buffer sequence ID. + */ +} __packed; + +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_RENDERED_EOS 0x00010C1C +#define ASM_DATA_EVENT_EOS 0x00010BDD + +#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99 +struct asm_data_event_write_done_v2 { + u32 buf_addr_lsw; + /* lsw of the 64 bit address */ + u32 buf_addr_msw; + /* msw of the 64 bit address. address given by the client in + * ASM_DATA_CMD_WRITE_V2 command. + */ + u32 mem_map_handle; + /* memory map handle in the ASM_DATA_CMD_WRITE_V2 */ + + u32 status; +/* Status message (error code) that indicates whether the + * referenced buffer has been successfully consumed. + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ +} __packed; + +#define ASM_DATA_EVENT_READ_DONE_V2 0x00010D9A + +/* Definition of the frame metadata flag bitmask.*/ +#define ASM_BIT_MASK_FRAME_METADATA_FLAG (0x40000000UL) + +/* Definition of the frame metadata flag shift value. */ +#define ASM_SHIFT_FRAME_METADATA_FLAG 30 + +struct asm_data_event_read_done_v2 { + u32 status; +/* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ + +u32 buf_addr_lsw; +/* 64 bit address msw-lsw is a valid, mapped address. 64 bit + * address is a multiple of 32 bytes. + */ + +u32 buf_addr_msw; +/* 64 bit address msw-lsw is a valid, mapped address. 64 bit +* address is a multiple of 32 bytes. +* +* -Same address provided by the client in ASM_DATA_CMD_READ_V2 +* -In the case of 32 bit Shared memory address, msw field is set to +* zero. +* -In the case of 36 bit shared memory address, bit 31 to bit 4 +* -of msw is set to zero. +*/ + +u32 mem_map_handle; +/* memory map handle in the ASM_DATA_CMD_READ_V2 */ + +u32 enc_framesotal_size; +/* Total size of the encoded frames in bytes. + * Supported values: >0 + */ + +u32 offset; +/* Offset (from buf_addr) to the first byte of the first encoded + * frame. All encoded frames are consecutive, starting from this + * offset. + * Supported values: > 0 + */ + +u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of + * the first sample in the buffer. If Bit 5 of mode_flags flag of + * ASM_STREAM_CMD_OPEN_READ_V2 is 1 then the 64 bit timestamp is + * absolute capture time otherwise it is relative session time. The + * absolute timestamp doesnt reset unless the system is reset. + */ + + +u32 timestamp_msw; +/* Upper 32 bits of the 64-bit session time in microseconds of + * the first sample in the buffer. + */ + + +u32 flags; +/* Bitfield of flags. Bit 30 indicates whether frame metadata is + * present. If frame metadata is present, num_frames consecutive + * instances of @xhyperref{hdr:FrameMetaData,Frame metadata} start + * at the buffer address. + * Supported values for bit 31: + * - 1 -- Timestamp is valid. + * - 0 -- Timestamp is invalid. + * - Use #ASM_BIT_MASKIMESTAMP_VALID_FLAG and + * #ASM_SHIFTIMESTAMP_VALID_FLAG to set this bit. + * + * Supported values for bit 30: + * - 1 -- Frame metadata is present. + * - 0 -- Frame metadata is absent. + * - Use #ASM_BIT_MASK_FRAME_METADATA_FLAG and + * #ASM_SHIFT_FRAME_METADATA_FLAG to set this bit. + * + * All other bits are reserved; the aDSP sets them to 0. + */ + +u32 num_frames; +/* Number of encoded frames in the buffer. */ + +u32 seq_id; +/* Optional buffer sequence ID. */ +} __packed; + +struct asm_data_read_buf_metadata_v2 { + u32 offset; +/* Offset from buf_addr in #ASM_DATA_EVENT_READ_DONE_PAYLOAD to + * the frame associated with this metadata. + * Supported values: > 0 + */ + +u32 frm_size; +/* Size of the encoded frame in bytes. + * Supported values: > 0 + */ + +u32 num_encoded_pcm_samples; +/* Number of encoded PCM samples (per channel) in the frame + * associated with this metadata. + * Supported values: > 0 + */ + +u32 timestamp_lsw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first sample for this frame. + * If Bit 5 of mode_flags flag of ASM_STREAM_CMD_OPEN_READ_V2 is 1 + * then the 64 bit timestamp is absolute capture time otherwise it + * is relative session time. The absolute timestamp doesnt reset + * unless the system is reset. + */ + + +u32 timestamp_msw; +/* Lower 32 bits of the 64-bit session time in microseconds of the + * first sample for this frame. + */ + +u32 flags; +/* Frame flags. + * Supported values for bit 31: + * - 1 -- Time stamp is valid + * - 0 -- Time stamp is not valid + * - All other bits are reserved; the aDSP sets them to 0. +*/ +} __packed; + +/* Notifies the client of a change in the data sampling rate or + * Channel mode. This event is raised by the decoder service. The + * event is enabled through the mode flags of + * #ASM_STREAM_CMD_OPEN_WRITE_V2 or + * #ASM_STREAM_CMD_OPEN_READWRITE_V2. - The decoder detects a change + * in the output sampling frequency or the number/positioning of + * output channels, or if it is the first frame decoded.The new + * sampling frequency or the new channel configuration is + * communicated back to the client asynchronously. + */ + +#define ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY 0x00010C65 + +/* Payload of the #ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY event. + * This event is raised when the following conditions are both true: + * - The event is enabled through the mode_flags of + * #ASM_STREAM_CMD_OPEN_WRITE_V2 or + * #ASM_STREAM_CMD_OPEN_READWRITE_V2. - The decoder detects a change + * in either the output sampling frequency or the number/positioning + * of output channels, or if it is the first frame decoded. + * This event is not raised (even if enabled) if the decoder is + * MIDI, because + */ + + +struct asm_data_event_sr_cm_change_notify { + u32 sample_rate; +/* New sampling rate (in Hertz) after detecting a change in the + * bitstream. + * Supported values: 2000 to 48000 + */ + + u16 num_channels; +/* New number of channels after detecting a change in the + * bitstream. + * Supported values: 1 to 8 + */ + + + u16 reserved; + /* Reserved for future use. This field must be set to 0.*/ + + u8 channel_mapping[8]; + +} __packed; + +/* Notifies the client of a data sampling rate or channel mode + * change. This event is raised by the encoder service. + * This event is raised when : + * - Native mode encoding was requested in the encoder + * configuration (i.e., the channel number was 0), the sample rate + * was 0, or both were 0. + * + * - The input data frame at the encoder is the first one, or the + * sampling rate/channel mode is different from the previous input + * data frame. + * + */ +#define ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY 0x00010BDE + +struct asm_data_event_enc_sr_cm_change_notify { + u32 sample_rate; +/* New sampling rate (in Hertz) after detecting a change in the + * input data. + * Supported values: 2000 to 48000 + */ + + + u16 num_channels; +/* New number of channels after detecting a change in the input + * data. Supported values: 1 to 8 + */ + + + u16 bits_per_sample; +/* New bits per sample after detecting a change in the input + * data. + * Supported values: 16, 24 + */ + + + u8 channel_mapping[8]; + +} __packed; +#define ASM_DATA_CMD_IEC_60958_FRAME_RATE 0x00010D87 + + +/* Payload of the #ASM_DATA_CMD_IEC_60958_FRAME_RATE command, + * which is used to indicate the IEC 60958 frame rate of a given + * packetized audio stream. + */ + +struct asm_data_cmd_iec_60958_frame_rate { + u32 frame_rate; +/* IEC 60958 frame rate of the incoming IEC 61937 packetized stream. + * Supported values: Any valid frame rate + */ +} __packed; + +/* adsp_asm_data_commands.h*/ +#define ASM_SVC_CMD_GET_STREAM_HANDLES 0x00010C0B + +#define ASM_SVC_CMDRSP_GET_STREAM_HANDLES 0x00010C1B + +/* Definition of the stream ID bitmask.*/ +#define ASM_BIT_MASK_STREAM_ID (0x000000FFUL) + +/* Definition of the stream ID shift value.*/ +#define ASM_SHIFT_STREAM_ID 0 + +/* Definition of the session ID bitmask.*/ +#define ASM_BIT_MASK_SESSION_ID (0x0000FF00UL) + +/* Definition of the session ID shift value.*/ +#define ASM_SHIFT_SESSION_ID 8 + +/* Definition of the service ID bitmask.*/ +#define ASM_BIT_MASK_SERVICE_ID (0x00FF0000UL) + +/* Definition of the service ID shift value.*/ +#define ASM_SHIFT_SERVICE_ID 16 + +/* Definition of the domain ID bitmask.*/ +#define ASM_BIT_MASK_DOMAIN_ID (0xFF000000UL) + +/* Definition of the domain ID shift value.*/ +#define ASM_SHIFT_DOMAIN_ID 24 + +/* Payload of the #ASM_SVC_CMDRSP_GET_STREAM_HANDLES message, + * which returns a list of currently active stream handles. + * Immediately following this structure are num_handles of uint32 + * stream handles. + */ + + +struct asm_svc_cmdrsp_get_stream_handles { + u32 num_handles; + /* Number of active stream handles. */ +} __packed; + +#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 +#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 +#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 + +/* adsp_asm_service_commands.h */ + +#define ASM_MAX_SESSION_ID (8) + +/* Maximum number of sessions.*/ +#define ASM_MAX_NUM_SESSIONS ASM_MAX_SESSION_ID + +/* Maximum number of streams per session.*/ +#define ASM_MAX_STREAMS_PER_SESSION (8) +#define ASM_SESSION_CMD_RUN_V2 0x00010DAA +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_IMMEDIATE 0 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_ABSOLUTEIME 1 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_RELATIVEIME 2 +#define ASM_SESSION_CMD_RUN_STARTIME_RUN_WITH_DELAY 3 + +#define ASM_BIT_MASK_RUN_STARTIME (0x00000003UL) + +/* Bit shift value used to specify the start time for the + * ASM_SESSION_CMD_RUN_V2 command. + */ +#define ASM_SHIFT_RUN_STARTIME 0 +struct asm_session_cmd_run_v2 { + struct apr_hdr hdr; + u32 flags; +/* Specifies whether to run immediately or at a specific + * rendering time or with a specified delay. Run with delay is + * useful for delaying in case of ASM loopback opened through + * ASM_STREAM_CMD_OPEN_LOOPBACK_V2. Use #ASM_BIT_MASK_RUN_STARTIME + * and #ASM_SHIFT_RUN_STARTIME to set this 2-bit flag. + * + * + *Bits 0 and 1 can take one of four possible values: + * + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_IMMEDIATE + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_ABSOLUTEIME + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_AT_RELATIVEIME + *- #ASM_SESSION_CMD_RUN_STARTIME_RUN_WITH_DELAY + * + *All other bits are reserved; clients must set them to zero. + */ + + u32 time_lsw; +/* Lower 32 bits of the time in microseconds used to align the + * session origin time. When bits 0-1 of flags is + * ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, time lsw is the lsw of + * the delay in us. For ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, + * maximum value of the 64 bit delay is 150 ms. + */ + + u32 time_msw; +/* Upper 32 bits of the time in microseconds used to align the + * session origin time. When bits 0-1 of flags is + * ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, time msw is the msw of + * the delay in us. For ASM_SESSION_CMD_RUN_START_RUN_WITH_DELAY, + * maximum value of the 64 bit delay is 150 ms. + */ + +} __packed; + +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_SESSION_CMD_GET_SESSIONTIME_V3 0x00010D9D +#define ASM_SESSION_CMD_REGISTER_FOR_RX_UNDERFLOW_EVENTS 0x00010BD5 + +struct asm_session_cmd_rgstr_rx_underflow { + struct apr_hdr hdr; + u16 enable_flag; +/* Specifies whether a client is to receive events when an Rx + * session underflows. + * Supported values: + * - 0 -- Do not send underflow events + * - 1 -- Send underflow events + */ + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +#define ASM_SESSION_CMD_REGISTER_FORX_OVERFLOW_EVENTS 0x00010BD6 + +struct asm_session_cmd_regx_overflow { + struct apr_hdr hdr; + u16 enable_flag; +/* Specifies whether a client is to receive events when a Tx +* session overflows. + * Supported values: + * - 0 -- Do not send overflow events + * - 1 -- Send overflow events + */ + + u16 reserved; + /* Reserved. This field must be set to zero.*/ +} __packed; + +#define ASM_SESSION_EVENT_RX_UNDERFLOW 0x00010C17 +#define ASM_SESSION_EVENTX_OVERFLOW 0x00010C18 +#define ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3 0x00010D9E + +struct asm_session_cmdrsp_get_sessiontime_v3 { + u32 status; + /* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + */ + + u32 sessiontime_lsw; + /* Lower 32 bits of the current session time in microseconds.*/ + + u32 sessiontime_msw; + /* Upper 32 bits of the current session time in microseconds.*/ + + u32 absolutetime_lsw; +/* Lower 32 bits in micro seconds of the absolute time at which + * the * sample corresponding to the above session time gets + * rendered * to hardware. This absolute time may be slightly in the + * future or past. + */ + + + u32 absolutetime_msw; +/* Upper 32 bits in micro seconds of the absolute time at which + * the * sample corresponding to the above session time gets + * rendered to * hardware. This absolute time may be slightly in the + * future or past. + */ + +} __packed; + +#define ASM_SESSION_CMD_ADJUST_SESSION_CLOCK_V2 0x00010D9F + +struct asm_session_cmd_adjust_session_clock_v2 { + struct apr_hdr hdr; +u32 adjustime_lsw; +/* Lower 32 bits of the signed 64-bit quantity that specifies the + * adjustment time in microseconds to the session clock. + * + * Positive values indicate advancement of the session clock. + * Negative values indicate delay of the session clock. + */ + + + u32 adjustime_msw; +/* Upper 32 bits of the signed 64-bit quantity that specifies + * the adjustment time in microseconds to the session clock. + * Positive values indicate advancement of the session clock. + * Negative values indicate delay of the session clock. + */ + +} __packed; + +#define ASM_SESSION_CMDRSP_ADJUST_SESSION_CLOCK_V2 0x00010DA0 + +struct asm_session_cmdrsp_adjust_session_clock_v2 { + u32 status; +/* Status message (error code). + * Supported values: Refer to @xhyperref{Q3,[Q3]} + * An error means the session clock is not adjusted. In this case, + * the next two fields are irrelevant. + */ + + + u32 actual_adjustime_lsw; +/* Lower 32 bits of the signed 64-bit quantity that specifies + * the actual adjustment in microseconds performed by the aDSP. + * A positive value indicates advancement of the session clock. A + * negative value indicates delay of the session clock. + */ + + + u32 actual_adjustime_msw; +/* Upper 32 bits of the signed 64-bit quantity that specifies + * the actual adjustment in microseconds performed by the aDSP. + * A positive value indicates advancement of the session clock. A + * negative value indicates delay of the session clock. + */ + + + u32 cmd_latency_lsw; +/* Lower 32 bits of the unsigned 64-bit quantity that specifies + * the amount of time in microseconds taken to perform the session + * clock adjustment. + */ + + + u32 cmd_latency_msw; +/* Upper 32 bits of the unsigned 64-bit quantity that specifies + * the amount of time in microseconds taken to perform the session + * clock adjustment. + */ + +} __packed; + +#define ASM_SESSION_CMD_GET_PATH_DELAY_V2 0x00010DAF +#define ASM_SESSION_CMDRSP_GET_PATH_DELAY_V2 0x00010DB0 + +struct asm_session_cmdrsp_get_path_delay_v2 { + u32 status; +/* Status message (error code). Whether this get delay operation + * is successful or not. Delay value is valid only if status is + * success. + * Supported values: Refer to @xhyperref{Q5,[Q5]} + */ + + u32 audio_delay_lsw; + /* Upper 32 bits of the aDSP delay in microseconds. */ + + u32 audio_delay_msw; + /* Lower 32 bits of the aDSP delay in microseconds. */ + +} __packed; + +/* adsp_asm_session_command.h*/ +#define ASM_STREAM_CMD_OPEN_WRITE_V2 0x00010D8F + +struct asm_stream_cmd_open_write_v2 { + struct apr_hdr hdr; + uint32_t mode_flags; +/* Mode flags that configure the stream to notify the client + * whenever it detects an SR/CM change at the input to its POPP. + * Supported values for bits 0 to 1: + * - Reserved; clients must set them to zero. + * Supported values for bit 2: + * - 0 -- SR/CM change notification event is disabled. + * - 1 -- SR/CM change notification event is enabled. + * - Use #ASM_BIT_MASK_SR_CM_CHANGE_NOTIFY_FLAG and + * #ASM_SHIFT_SR_CM_CHANGE_NOTIFY_FLAG to set or get this bit. + * + * Supported values for bit 31: + * - 0 -- Stream to be opened in on-Gapless mode. + * - 1 -- Stream to be opened in Gapless mode. In Gapless mode, + * successive streams must be opened with same session ID but + * different stream IDs. + * + * - Use #ASM_BIT_MASK_GAPLESS_MODE_FLAG and + * #ASM_SHIFT_GAPLESS_MODE_FLAG to set or get this bit. + * + * + * @note1hang MIDI and DTMF streams cannot be opened in Gapless mode. + */ + + uint16_t sink_endpointype; +/*< Sink point type. + * Supported values: + * - 0 -- Device matrix + * - Other values are reserved. + * + * The device matrix is the gateway to the hardware ports. + */ + + uint16_t bits_per_sample; +/*< Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + uint32_t postprocopo_id; +/*< Specifies the topology (order of processing) of + * postprocessing algorithms. None means no postprocessing. + * Supported values: + * - #ASM_STREAM_POSTPROCOPO_ID_DEFAULT + * - #ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL + * - #ASM_STREAM_POSTPROCOPO_ID_NONE + * + * This field can also be enabled through SetParams flags. + */ + + uint32_t dec_fmt_id; +/*< Configuration ID of the decoder media format. + * + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_ADPCM + * - #ASM_MEDIA_FMT_MP3 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_DOLBY_AAC + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_AMR_WB_PLUS_V2 + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_WMA_V10PRO_V2 + * - #ASM_MEDIA_FMT_WMA_V9_V2 + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_EAC3_DEC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_FR_FS + * - #ASM_MEDIA_FMT_VORBIS + * - #ASM_MEDIA_FMT_FLAC + * - #ASM_MEDIA_FMT_EXAMPLE + */ +} __packed; + +#define ASM_STREAM_CMD_OPEN_READ_V2 0x00010D8C +/* Definition of the timestamp type flag bitmask */ +#define ASM_BIT_MASKIMESTAMPYPE_FLAG (0x00000020UL) + +/* Definition of the timestamp type flag shift value. */ +#define ASM_SHIFTIMESTAMPYPE_FLAG 5 + +/* Relative timestamp is identified by this value.*/ +#define ASM_RELATIVEIMESTAMP 0 + +/* Absolute timestamp is identified by this value.*/ +#define ASM_ABSOLUTEIMESTAMP 1 + + +struct asm_stream_cmd_open_read_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags that indicate whether meta information per encoded + * frame is to be provided. + * Supported values for bit 4: + * + * - 0 -- Return data buffer contains all encoded frames only; it + * does not contain frame metadata. + * + * - 1 -- Return data buffer contains an array of metadata and + * encoded frames. + * + * - Use #ASM_BIT_MASK_META_INFO_FLAG as the bitmask and + * #ASM_SHIFT_META_INFO_FLAG as the shift value for this bit. + * + * + * Supported values for bit 5: + * + * - ASM_RELATIVEIMESTAMP -- ASM_DATA_EVENT_READ_DONE_V2 will have + * - relative time-stamp. + * - ASM_ABSOLUTEIMESTAMP -- ASM_DATA_EVENT_READ_DONE_V2 will + * - have absolute time-stamp. + * + * - Use #ASM_BIT_MASKIMESTAMPYPE_FLAG as the bitmask and + * #ASM_SHIFTIMESTAMPYPE_FLAG as the shift value for this bit. + * + * All other bits are reserved; clients must set them to zero. + */ + + u32 src_endpointype; +/* Specifies the endpoint providing the input samples. + * Supported values: + * - 0 -- Device matrix + * - All other values are reserved; clients must set them to zero. + * Otherwise, an error is returned. + * The device matrix is the gateway from the tunneled Tx ports. + */ + + u32 preprocopo_id; +/* Specifies the topology (order of processing) of preprocessing + * algorithms. None means no preprocessing. + * Supported values: + * - #ASM_STREAM_PREPROCOPO_ID_DEFAULT + * - #ASM_STREAM_PREPROCOPO_ID_NONE + * + * This field can also be enabled through SetParams flags. + */ + + u32 enc_cfg_id; +/* Media configuration ID for encoded output. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + * - #ASM_MEDIA_FMT_WMA_V8 + */ + + u16 bits_per_sample; +/* Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + u16 reserved; +/* Reserved for future use. This field must be set to zero.*/ +} __packed; + +#define ASM_POPP_OUTPUT_SR_NATIVE_RATE 0 + +/* Enumeration for the maximum sampling rate at the POPP output.*/ +#define ASM_POPP_OUTPUT_SR_MAX_RATE 48000 + +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D +#define ASM_STREAM_CMD_OPEN_READ_V2 0x00010D8C + +struct asm_stream_cmd_open_readwrite_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags. + * Supported values for bit 2: + * - 0 -- SR/CM change notification event is disabled. + * - 1 -- SR/CM change notification event is enabled. Use + * #ASM_BIT_MASK_SR_CM_CHANGE_NOTIFY_FLAG and + * #ASM_SHIFT_SR_CM_CHANGE_NOTIFY_FLAG to set or + * getting this flag. + * + * Supported values for bit 4: + * - 0 -- Return read data buffer contains all encoded frames only; it + * does not contain frame metadata. + * - 1 -- Return read data buffer contains an array of metadata and + * encoded frames. + * + * All other bits are reserved; clients must set them to zero. + */ + + u32 postprocopo_id; +/* Specifies the topology (order of processing) of postprocessing + * algorithms. None means no postprocessing. + * + * Supported values: + * - #ASM_STREAM_POSTPROCOPO_ID_DEFAULT + * - #ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL + * - #ASM_STREAM_POSTPROCOPO_ID_NONE + */ + + u32 dec_fmt_id; +/* Specifies the media type of the input data. PCM indicates that + * no decoding must be performed, e.g., this is an NT encoder + * session. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_ADPCM + * - #ASM_MEDIA_FMT_MP3 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_DOLBY_AAC + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_WMA_V10PRO_V2 + * - #ASM_MEDIA_FMT_WMA_V9_V2 + * - #ASM_MEDIA_FMT_AMR_WB_PLUS_V2 + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + */ + + u32 enc_cfg_id; +/* Specifies the media type for the output of the stream. PCM + * indicates that no encoding must be performed, e.g., this is an NT + * decoder session. + * Supported values: + * - #ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 + * - #ASM_MEDIA_FMT_AAC_V2 + * - #ASM_MEDIA_FMT_AMRNB_FS + * - #ASM_MEDIA_FMT_AMRWB_FS + * - #ASM_MEDIA_FMT_V13K_FS + * - #ASM_MEDIA_FMT_EVRC_FS + * - #ASM_MEDIA_FMT_EVRCB_FS + * - #ASM_MEDIA_FMT_EVRCWB_FS + * - #ASM_MEDIA_FMT_SBC + * - #ASM_MEDIA_FMT_G711_ALAW_FS + * - #ASM_MEDIA_FMT_G711_MLAW_FS + * - #ASM_MEDIA_FMT_G729A_FS + * - #ASM_MEDIA_FMT_EXAMPLE + * - #ASM_MEDIA_FMT_WMA_V8 + */ + + u16 bits_per_sample; +/* Number of bits per sample processed by ASM modules. + * Supported values: 16 and 24 bits per sample + */ + + u16 reserved; +/* Reserved for future use. This field must be set to zero.*/ + +} __packed; + +#define ASM_STREAM_CMD_OPEN_LOOPBACK_V2 0x00010D8E +struct asm_stream_cmd_open_loopback_v2 { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags. + * Bit 0-31: reserved; client should set these bits to 0 + */ + u16 src_endpointype; + /* Endpoint type. 0 = Tx Matrix */ + u16 sink_endpointype; + /* Endpoint type. 0 = Rx Matrix */ + u32 postprocopo_id; +/* Postprocessor topology ID. Specifies the topology of + * postprocessing algorithms. + */ + + u16 bits_per_sample; +/* The number of bits per sample processed by ASM modules + * Supported values: 16 and 24 bits per sample + */ + u16 reserved; +/* Reserved for future use. This field must be set to zero. */ +} __packed; + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE + + +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 +#define ASM_STREAM_CMD_SET_PP_PARAMS_V2 0x00010DA1 + +struct asm_stream_cmd_set_pp_params_v2 { + u32 data_payload_addr_lsw; +/* LSW of parameter data payload address. Supported values: any. */ + u32 data_payload_addr_msw; +/* MSW of Parameter data payload address. Supported values: any. + * - Must be set to zero for in-band data. + * - In the case of 32 bit Shared memory address, msw field must be + * - set to zero. + * - In the case of 36 bit shared memory address, bit 31 to bit 4 of + * msw + * + * - must be set to zero. + */ + u32 mem_map_handle; +/* Supported Values: Any. +* memory map handle returned by DSP through +* ASM_CMD_SHARED_MEM_MAP_REGIONS +* command. +* if mmhandle is NULL, the ParamData payloads are within the +* message payload (in-band). +* If mmhandle is non-NULL, the ParamData payloads begin at the +* address specified in the address msw and lsw (out-of-band). +*/ + + u32 data_payload_size; +/* Size in bytes of the variable payload accompanying the +message, or in shared memory. This field is used for parsing the +parameter payload. */ + +} __packed; + + +struct asm_stream_param_data_v2 { + u32 module_id; + /* Unique module ID. */ + + u32 param_id; + /* Unique parameter ID. */ + + u16 param_size; +/* Data size of the param_id/module_id combination. This is + * a multiple of 4 bytes. + */ + + u16 reserved; +/* Reserved for future enhancements. This field must be set to + * zero. + */ + +} __packed; + +#define ASM_STREAM_CMD_GET_PP_PARAMS_V2 0x00010DA2 + +struct asm_stream_cmd_get_pp_params_v2 { + u32 data_payload_addr_lsw; + /* LSW of the parameter data payload address. */ + u32 data_payload_addr_msw; +/* MSW of the parameter data payload address. + * - Size of the shared memory, if specified, shall be large enough + * to contain the whole ParamData payload, including Module ID, + * Param ID, Param Size, and Param Values + * - Must be set to zero for in-band data + * - In the case of 32 bit Shared memory address, msw field must be + * set to zero. + * - In the case of 36 bit shared memory address, bit 31 to bit 4 of + * msw must be set to zero. + */ + + u32 mem_map_handle; +/* Supported Values: Any. +* memory map handle returned by DSP through ASM_CMD_SHARED_MEM_MAP_REGIONS +* command. +* if mmhandle is NULL, the ParamData payloads in the ACK are within the +* message payload (in-band). +* If mmhandle is non-NULL, the ParamData payloads in the ACK begin at the +* address specified in the address msw and lsw. +* (out-of-band). +*/ + + u32 module_id; + /* Unique module ID. */ + + u32 param_id; + /* Unique parameter ID. */ + + u16 param_max_size; +/* Maximum data size of the module_id/param_id combination. This + * is a multiple of 4 bytes. + */ + + + u16 reserved; +/* Reserved for backward compatibility. Clients must set this +* field to zero. +*/ + +} __packed; + +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 + +#define ASM_PARAM_ID_ENCDEC_BITRATE 0x00010C13 + +struct asm_bitrate_param { + u32 bitrate; +/* Maximum supported bitrate. Only the AAC encoder is supported.*/ + +} __packed; + +#define ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2 0x00010DA3 +#define ASM_PARAM_ID_AAC_SBR_PS_FLAG 0x00010C63 + +/* Flag to turn off both SBR and PS processing, if they are + * present in the bitstream. + */ + +#define ASM_AAC_SBR_OFF_PS_OFF (2) + +/* Flag to turn on SBR but turn off PS processing,if they are + * present in the bitstream. + */ + +#define ASM_AAC_SBR_ON_PS_OFF (1) + +/* Flag to turn on both SBR and PS processing, if they are + * present in the bitstream (default behavior). + */ + + +#define ASM_AAC_SBR_ON_PS_ON (0) + +/* Structure for an AAC SBR PS processing flag. */ + +/* Payload of the #ASM_PARAM_ID_AAC_SBR_PS_FLAG parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_aac_sbr_ps_flag_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + + u32 sbr_ps_flag; +/* Control parameter to enable or disable SBR/PS processing in + * the AAC bitstream. Use the following macros to set this field: + * - #ASM_AAC_SBR_OFF_PS_OFF -- Turn off both SBR and PS + * processing, if they are present in the bitstream. + * - #ASM_AAC_SBR_ON_PS_OFF -- Turn on SBR processing, but not PS + * processing, if they are present in the bitstream. + * - #ASM_AAC_SBR_ON_PS_ON -- Turn on both SBR and PS processing, + * if they are present in the bitstream (default behavior). + * - All other values are invalid. + * Changes are applied to the next decoded frame. + */ +} __packed; + +#define ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING 0x00010C64 + +/* First single channel element in a dual mono bitstream.*/ +#define ASM_AAC_DUAL_MONO_MAP_SCE_1 (1) + +/* Second single channel element in a dual mono bitstream.*/ +#define ASM_AAC_DUAL_MONO_MAP_SCE_2 (2) + +/* Structure for AAC decoder dual mono channel mapping. */ + + +struct asm_aac_dual_mono_mapping_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u16 left_channel_sce; + u16 right_channel_sce; + +} __packed; + +#define ASM_STREAM_CMDRSP_GET_PP_PARAMS_V2 0x00010DA4 + +struct asm_stream_cmdrsp_get_pp_params_v2 { + u32 status; +} __packed; + +#define ASM_PARAM_ID_AC3_KARAOKE_MODE 0x00010D73 + +/* Enumeration for both vocals in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_NO_VOCAL (0) + +/* Enumeration for only the left vocal in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_LEFT_VOCAL (1) + +/* Enumeration for only the right vocal in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_RIGHT_VOCAL (2) + +/* Enumeration for both vocal channels in a karaoke stream.*/ +#define AC3_KARAOKE_MODE_BOTH_VOCAL (3) +#define ASM_PARAM_ID_AC3_DRC_MODE 0x00010D74 +/* Enumeration for the Custom Analog mode.*/ +#define AC3_DRC_MODE_CUSTOM_ANALOG (0) + +/* Enumeration for the Custom Digital mode.*/ +#define AC3_DRC_MODE_CUSTOM_DIGITAL (1) +/* Enumeration for the Line Out mode (light compression).*/ +#define AC3_DRC_MODE_LINE_OUT (2) + +/* Enumeration for the RF remodulation mode (heavy compression).*/ +#define AC3_DRC_MODE_RF_REMOD (3) +#define ASM_PARAM_ID_AC3_DUAL_MONO_MODE 0x00010D75 + +/* Enumeration for playing dual mono in stereo mode.*/ +#define AC3_DUAL_MONO_MODE_STEREO (0) + +/* Enumeration for playing left mono.*/ +#define AC3_DUAL_MONO_MODE_LEFT_MONO (1) + +/* Enumeration for playing right mono.*/ +#define AC3_DUAL_MONO_MODE_RIGHT_MONO (2) + +/* Enumeration for mixing both dual mono channels and playing them.*/ +#define AC3_DUAL_MONO_MODE_MIXED_MONO (3) +#define ASM_PARAM_ID_AC3_STEREO_DOWNMIX_MODE 0x00010D76 + +/* Enumeration for using the Downmix mode indicated in the bitstream. */ + +#define AC3_STEREO_DOWNMIX_MODE_AUTO_DETECT (0) + +/* Enumeration for Surround Compatible mode (preserves the + * surround information). + */ + +#define AC3_STEREO_DOWNMIX_MODE_LT_RT (1) +/* Enumeration for Mono Compatible mode (if the output is to be + * further downmixed to mono). + */ + +#define AC3_STEREO_DOWNMIX_MODE_LO_RO (2) + +/* ID of the AC3 PCM scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_PCM_SCALEFACTOR 0x00010D78 + +/* ID of the AC3 DRC boost scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_DRC_BOOST_SCALEFACTOR 0x00010D79 + +/* ID of the AC3 DRC cut scale factor parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +#define ASM_PARAM_ID_AC3_DRC_CUT_SCALEFACTOR 0x00010D7A + +/* Structure for AC3 Generic Parameter. */ + +/* Payload of the AC3 parameters in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_ac3_generic_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 generic_parameter; +/* AC3 generic parameter. Select from one of the following + * possible values. + * + * For #ASM_PARAM_ID_AC3_KARAOKE_MODE, supported values are: + * - AC3_KARAOKE_MODE_NO_VOCAL + * - AC3_KARAOKE_MODE_LEFT_VOCAL + * - AC3_KARAOKE_MODE_RIGHT_VOCAL + * - AC3_KARAOKE_MODE_BOTH_VOCAL + * + * For #ASM_PARAM_ID_AC3_DRC_MODE, supported values are: + * - AC3_DRC_MODE_CUSTOM_ANALOG + * - AC3_DRC_MODE_CUSTOM_DIGITAL + * - AC3_DRC_MODE_LINE_OUT + * - AC3_DRC_MODE_RF_REMOD + * + * For #ASM_PARAM_ID_AC3_DUAL_MONO_MODE, supported values are: + * - AC3_DUAL_MONO_MODE_STEREO + * - AC3_DUAL_MONO_MODE_LEFT_MONO + * - AC3_DUAL_MONO_MODE_RIGHT_MONO + * - AC3_DUAL_MONO_MODE_MIXED_MONO + * + * For #ASM_PARAM_ID_AC3_STEREO_DOWNMIX_MODE, supported values are: + * - AC3_STEREO_DOWNMIX_MODE_AUTO_DETECT + * - AC3_STEREO_DOWNMIX_MODE_LT_RT + * - AC3_STEREO_DOWNMIX_MODE_LO_RO + * + * For #ASM_PARAM_ID_AC3_PCM_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + * + * For #ASM_PARAM_ID_AC3_DRC_BOOST_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + * + * For #ASM_PARAM_ID_AC3_DRC_CUT_SCALEFACTOR, supported values are + * 0 to 1 in Q31 format. + */ +} __packed; + +/* Enumeration for Raw mode (no downmixing), which specifies + * that all channels in the bitstream are to be played out as is + * without any downmixing. (Default) + */ + +#define WMAPRO_CHANNEL_MASK_RAW (-1) + +/* Enumeration for setting the channel mask to 0. The 7.1 mode + * (Home Theater) is assigned. + */ + + +#define WMAPRO_CHANNEL_MASK_ZERO 0x0000 + +/* Speaker layout mask for one channel (Home Theater, mono). + * - Speaker front center + */ +#define WMAPRO_CHANNEL_MASK_1_C 0x0004 + +/* Speaker layout mask for two channels (Home Theater, stereo). + * - Speaker front left + * - Speaker front right + */ +#define WMAPRO_CHANNEL_MASK_2_L_R 0x0003 + +/* Speaker layout mask for three channels (Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + */ +#define WMAPRO_CHANNEL_MASK_3_L_C_R 0x0007 + +/* Speaker layout mask for two channels (stereo). + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_2_Bl_Br 0x0030 + +/* Speaker layout mask for four channels. + * - Speaker front left + * - Speaker front right + * - Speaker back left + * - Speaker back right +*/ +#define WMAPRO_CHANNEL_MASK_4_L_R_Bl_Br 0x0033 + +/* Speaker layout mask for four channels (Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back center +*/ +#define WMAPRO_CHANNEL_MASK_4_L_R_C_Bc_HT 0x0107 +/* Speaker layout mask for five channels. + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_5_L_C_R_Bl_Br 0x0037 + +/* Speaker layout mask for five channels (5 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5_L_C_R_Sl_Sr_HT 0x0607 +/* Speaker layout mask for six channels (5.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back left + * - Speaker back right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Bl_Br_SLF 0x003F +/* Speaker layout mask for six channels (5.1 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Sl_Sr_SLF_HT 0x060F +/* Speaker layout mask for six channels (5.1 mode, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker back center + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Bl_Br_Bc 0x0137 +/* Speaker layout mask for six channels (5.1 mode, Home Theater, + * no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back center + * - Speaker side left + * - Speaker side right + */ +#define WMAPRO_CHANNEL_MASK_5DOT1_L_C_R_Sl_Sr_Bc_HT 0x0707 + +/* Speaker layout mask for seven channels (6.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back left + * - Speaker back right + * - Speaker back center + */ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Bl_Br_Bc_SLF 0x013F + +/* Speaker layout mask for seven channels (6.1 mode, Home + * Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker low frequency + * - Speaker back center + * - Speaker side left + * - Speaker side right +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Sl_Sr_Bc_SLF_HT 0x070F + +/* Speaker layout mask for seven channels (6.1 mode, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker front left of center + * - Speaker front right of center +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Bl_Br_SFLOC_SFROC 0x00F7 + +/* Speaker layout mask for seven channels (6.1 mode, Home + * Theater, no LFE). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + * - Speaker front left of center + * - Speaker front right of center +*/ +#define WMAPRO_CHANNEL_MASK_6DOT1_L_C_R_Sl_Sr_SFLOC_SFROC_HT 0x0637 + +/* Speaker layout mask for eight channels (7.1 mode). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker back left + * - Speaker back right + * - Speaker low frequency + * - Speaker front left of center + * - Speaker front right of center + */ +#define WMAPRO_CHANNEL_MASK_7DOT1_L_C_R_Bl_Br_SLF_SFLOC_SFROC \ + 0x00FF + +/* Speaker layout mask for eight channels (7.1 mode, Home Theater). + * - Speaker front left + * - Speaker front right + * - Speaker front center + * - Speaker side left + * - Speaker side right + * - Speaker low frequency + * - Speaker front left of center + * - Speaker front right of center + * +*/ +#define WMAPRO_CHANNEL_MASK_7DOT1_L_C_R_Sl_Sr_SLF_SFLOC_SFROC_HT \ + 0x063F + +#define ASM_PARAM_ID_DEC_OUTPUT_CHAN_MAP 0x00010D82 + +/* Maximum number of decoder output channels.*/ +#define MAX_CHAN_MAP_CHANNELS 16 + +/* Structure for decoder output channel mapping. */ + +/* Payload of the #ASM_PARAM_ID_DEC_OUTPUT_CHAN_MAP parameter in the + * #ASM_STREAM_CMD_SET_ENCDEC_PARAM command. + */ +struct asm_dec_out_chan_map_param { + struct apr_hdr hdr; + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + u32 num_channels; +/* Number of decoder output channels. + * Supported values: 0 to #MAX_CHAN_MAP_CHANNELS + * + * A value of 0 indicates native channel mapping, which is valid + * only for NT mode. This means the output of the decoder is to be + * preserved as is. + */ + u8 channel_mapping[MAX_CHAN_MAP_CHANNELS]; +} __packed; + +#define ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED 0x00010D84 + +/* Bitmask for the IEC 61937 enable flag.*/ +#define ASM_BIT_MASK_IEC_61937_STREAM_FLAG (0x00000001UL) + +/* Shift value for the IEC 61937 enable flag.*/ +#define ASM_SHIFT_IEC_61937_STREAM_FLAG 0 + +/* Bitmask for the IEC 60958 enable flag.*/ +#define ASM_BIT_MASK_IEC_60958_STREAM_FLAG (0x00000002UL) + +/* Shift value for the IEC 60958 enable flag.*/ +#define ASM_SHIFT_IEC_60958_STREAM_FLAG 1 + +/* Payload format for open write compressed comand */ + +/* Payload format for the #ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED + * comand, which opens a stream for a given session ID and stream ID + * to be rendered in the compressed format. + */ + +struct asm_stream_cmd_open_write_compressed { + struct apr_hdr hdr; + u32 flags; +/* Mode flags that configure the stream for a specific format. + * Supported values: + * - Bit 0 -- IEC 61937 compatibility + * - 0 -- Stream is not in IEC 61937 format + * - 1 -- Stream is in IEC 61937 format + * - Bit 1 -- IEC 60958 compatibility + * - 0 -- Stream is not in IEC 60958 format + * - 1 -- Stream is in IEC 60958 format + * - Bits 2 to 31 -- 0 (Reserved) + * + * For the same stream, bit 0 cannot be set to 0 and bit 1 cannot + * be set to 1. A compressed stream connot have IEC 60958 + * packetization applied without IEC 61937 packetization. + * @note1hang Currently, IEC 60958 packetized input streams are not + * supported. + */ + + + u32 fmt_id; +/* Specifies the media type of the HDMI stream to be opened. + * Supported values: + * - #ASM_MEDIA_FMT_AC3_DEC + * - #ASM_MEDIA_FMT_EAC3_DEC + * - #ASM_MEDIA_FMT_DTS + * - #ASM_MEDIA_FMT_ATRAC + * - #ASM_MEDIA_FMT_MAT + * + * @note1hang This field must be set to a valid media type even if + * IEC 61937 packetization is not performed by the aDSP. + */ + +} __packed; + +#define ASM_STREAM_CMD_OPEN_READ_COMPRESSED 0x00010D95 + +struct asm_stream_cmd_open_read_compressed { + struct apr_hdr hdr; + u32 mode_flags; +/* Mode flags that indicate whether meta information per encoded + * frame is to be provided. + * Supported values for bit 4: + * - 0 -- Return data buffer contains all encoded frames only; it does + * not contain frame metadata. + * - 1 -- Return data buffer contains an array of metadata and encoded + * frames. + * - Use #ASM_BIT_MASK_META_INFO_FLAG to set the bitmask and + * #ASM_SHIFT_META_INFO_FLAG to set the shift value for this bit. + * All other bits are reserved; clients must set them to zero. + */ + + u32 frames_per_buf; +/* Indicates the number of frames that need to be returned per + * read buffer + * Supported values: should be greater than 0 + */ + +} __packed; + +/* adsp_asm_stream_commands.h*/ + + +/* adsp_asm_api.h (no changes)*/ +#define ASM_STREAM_POSTPROCOPO_ID_DEFAULT \ + 0x00010BE4 +#define ASM_STREAM_POSTPROCOPO_ID_PEAKMETER \ + 0x00010D83 +#define ASM_STREAM_POSTPROCOPO_ID_NONE \ + 0x00010C68 +#define ASM_STREAM_POSTPROCOPO_ID_MCH_PEAK_VOL \ + 0x00010D8B +#define ASM_STREAM_PREPROCOPO_ID_DEFAULT \ + ASM_STREAM_POSTPROCOPO_ID_DEFAULT +#define ASM_STREAM_PREPROCOPO_ID_NONE \ + ASM_STREAM_POSTPROCOPO_ID_NONE +#define ADM_CMD_COPP_OPENOPOLOGY_ID_NONE_AUDIO_COPP \ + 0x00010312 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MONO_AUDIO_COPP \ + 0x00010313 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_AUDIO_COPP \ + 0x00010314 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_IIR_AUDIO_COPP\ + 0x00010704 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MONO_AUDIO_COPP_MBDRCV2\ + 0x0001070D +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_AUDIO_COPP_MBDRCV2\ + 0x0001070E +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_STEREO_IIR_AUDIO_COPP_MBDRCV2\ + 0x0001070F +#define ADM_CMD_COPP_OPENOPOLOGY_ID_SPEAKER_MCH_PEAK_VOL \ + 0x0001031B +#define ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_MONO_AUDIO_COPP 0x00010315 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_STEREO_AUDIO_COPP 0x00010316 +#define AUDPROC_COPPOPOLOGY_ID_MCHAN_IIR_AUDIO 0x00010715 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_DEFAULT_AUDIO_COPP 0x00010BE3 +#define ADM_CMD_COPP_OPENOPOLOGY_ID_PEAKMETER_AUDIO_COPP 0x00010317 +#define AUDPROC_MODULE_ID_AIG 0x00010716 +#define AUDPROC_PARAM_ID_AIG_ENABLE 0x00010717 +#define AUDPROC_PARAM_ID_AIG_CONFIG 0x00010718 + +struct Audio_AigParam { + uint16_t mode; +/*< Mode word for enabling AIG/SIG mode . + * Byte offset: 0 + */ + int16_t staticGainL16Q12; +/*< Static input gain when aigMode is set to 1. + * Byte offset: 2 + */ + int16_t initialGainDBL16Q7; +/* + +/* + * Audio Front End (AFE) + */ + +/* Port ID. Update afe_get_port_index when a new port is added here. */ +#define PRIMARY_I2S_RX 0 /* index = 0 */ +#define PRIMARY_I2S_TX 1 /* index = 1 */ +#define PCM_RX 2 /* index = 2 */ +#define PCM_TX 3 /* index = 3 */ +#define SECONDARY_I2S_RX 4 /* index = 4 */ +#define SECONDARY_I2S_TX 5 /* index = 5 */ +#define MI2S_RX 6 /* index = 6 */ +#define MI2S_TX 7 /* index = 7 */ +#define HDMI_RX 8 /* index = 8 */ +#define RSVD_2 9 /* index = 9 */ +#define RSVD_3 10 /* index = 10 */ +#define DIGI_MIC_TX 11 /* index = 11 */ +#define VOICE_RECORD_RX 0x8003 /* index = 12 */ +#define VOICE_RECORD_TX 0x8004 /* index = 13 */ +#define VOICE_PLAYBACK_TX 0x8005 /* index = 14 */ + +/* Slimbus Multi channel port id pool */ +#define SLIMBUS_0_RX 0x4000 /* index = 15 */ +#define SLIMBUS_0_TX 0x4001 /* index = 16 */ +#define SLIMBUS_1_RX 0x4002 /* index = 17 */ +#define SLIMBUS_1_TX 0x4003 /* index = 18 */ +#define SLIMBUS_2_RX 0x4004 +#define SLIMBUS_2_TX 0x4005 +#define SLIMBUS_3_RX 0x4006 +#define SLIMBUS_3_TX 0x4007 +#define SLIMBUS_4_RX 0x4008 +#define SLIMBUS_4_TX 0x4009 /* index = 24 */ + +#define INT_BT_SCO_RX 0x3000 /* index = 25 */ +#define INT_BT_SCO_TX 0x3001 /* index = 26 */ +#define INT_BT_A2DP_RX 0x3002 /* index = 27 */ +#define INT_FM_RX 0x3004 /* index = 28 */ +#define INT_FM_TX 0x3005 /* index = 29 */ +#define RT_PROXY_PORT_001_RX 0x2000 /* index = 30 */ +#define RT_PROXY_PORT_001_TX 0x2001 /* index = 31 */ +#define SECONDARY_PCM_RX 12 /* index = 32 */ +#define SECONDARY_PCM_TX 13 /* index = 33 */ + + +#define AFE_PORT_INVALID 0xFFFF +#define SLIMBUS_EXTPROC_RX AFE_PORT_INVALID + +#define AFE_PORT_CMD_START 0x000100ca + +#define AFE_EVENT_RTPORT_START 0 +#define AFE_EVENT_RTPORT_STOP 1 +#define AFE_EVENT_RTPORT_LOW_WM 2 +#define AFE_EVENT_RTPORT_HI_WM 3 + +struct afe_port_start_command { + struct apr_hdr hdr; + u16 port_id; + u16 gain; /* Q13 */ + u32 sample_rate; /* 8 , 16, 48khz */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_STOP 0x000100cb +struct afe_port_stop_command { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_APPLY_GAIN 0x000100cc +struct afe_port_gain_command { + struct apr_hdr hdr; + u16 port_id; + u16 gain;/* Q13 */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_SIDETONE_CTL 0x000100cd +struct afe_port_sidetone_command { + struct apr_hdr hdr; + u16 rx_port_id; /* Primary i2s tx = 1 */ + /* PCM tx = 3 */ + /* Secondary i2s tx = 5 */ + /* Mi2s tx = 7 */ + /* Digital mic tx = 11 */ + u16 tx_port_id; /* Primary i2s rx = 0 */ + /* PCM rx = 2 */ + /* Secondary i2s rx = 4 */ + /* Mi2S rx = 6 */ + /* HDMI rx = 8 */ + u16 gain; /* Q13 */ + u16 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_LOOPBACK 0x000100ce +struct afe_loopback_command { + struct apr_hdr hdr; + u16 tx_port_id; /* Primary i2s rx = 0 */ + /* PCM rx = 2 */ + /* Secondary i2s rx = 4 */ + /* Mi2S rx = 6 */ + /* HDMI rx = 8 */ + u16 rx_port_id; /* Primary i2s tx = 1 */ + /* PCM tx = 3 */ + /* Secondary i2s tx = 5 */ + /* Mi2s tx = 7 */ + /* Digital mic tx = 11 */ + u16 mode; /* Default -1, DSP will conver + the tx to rx format */ + u16 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +#define AFE_PSEUDOPORT_CMD_START 0x000100cf +struct afe_pseudoport_start_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 timing; /* FTRT = 0 , AVTimer = 1, */ +} __attribute__ ((packed)); + +#define AFE_PSEUDOPORT_CMD_STOP 0x000100d0 +struct afe_pseudoport_stop_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_CMD_GET_ACTIVE_PORTS 0x000100d1 + + +#define AFE_CMD_GET_ACTIVE_HANDLES_FOR_PORT 0x000100d2 +struct afe_get_active_handles_command { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PCM_CFG_MODE_PCM 0x0 +#define AFE_PCM_CFG_MODE_AUX 0x1 +#define AFE_PCM_CFG_SYNC_EXT 0x0 +#define AFE_PCM_CFG_SYNC_INT 0x1 +#define AFE_PCM_CFG_FRM_8BPF 0x0 +#define AFE_PCM_CFG_FRM_16BPF 0x1 +#define AFE_PCM_CFG_FRM_32BPF 0x2 +#define AFE_PCM_CFG_FRM_64BPF 0x3 +#define AFE_PCM_CFG_FRM_128BPF 0x4 +#define AFE_PCM_CFG_FRM_256BPF 0x5 +#define AFE_PCM_CFG_QUANT_ALAW_NOPAD 0x0 +#define AFE_PCM_CFG_QUANT_MULAW_NOPAD 0x1 +#define AFE_PCM_CFG_QUANT_LINEAR_NOPAD 0x2 +#define AFE_PCM_CFG_QUANT_ALAW_PAD 0x3 +#define AFE_PCM_CFG_QUANT_MULAW_PAD 0x4 +#define AFE_PCM_CFG_QUANT_LINEAR_PAD 0x5 +#define AFE_PCM_CFG_CDATAOE_MASTER 0x0 +#define AFE_PCM_CFG_CDATAOE_SHARE 0x1 + +struct afe_port_pcm_cfg { + u16 mode; /* PCM (short sync) = 0, AUXPCM (long sync) = 1 */ + u16 sync; /* external = 0 , internal = 1 */ + u16 frame; /* 8 bpf = 0 */ + /* 16 bpf = 1 */ + /* 32 bpf = 2 */ + /* 64 bpf = 3 */ + /* 128 bpf = 4 */ + /* 256 bpf = 5 */ + u16 quant; + u16 slot; /* Slot for PCM stream , 0 - 31 */ + u16 data; /* 0, PCM block is the only master */ + /* 1, PCM block is shares to driver data out signal */ + /* other master */ + u16 reserved; +} __attribute__ ((packed)); + +enum { + AFE_I2S_SD0 = 1, + AFE_I2S_SD1, + AFE_I2S_SD2, + AFE_I2S_SD3, + AFE_I2S_QUAD01, + AFE_I2S_QUAD23, + AFE_I2S_6CHS, + AFE_I2S_8CHS, +}; + +#define AFE_MI2S_MONO 0 +#define AFE_MI2S_STEREO 3 +#define AFE_MI2S_4CHANNELS 4 +#define AFE_MI2S_6CHANNELS 6 +#define AFE_MI2S_8CHANNELS 8 + +struct afe_port_mi2s_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 line; /* Called ChannelMode in documentation */ + /* i2s_sd0 = 1 */ + /* i2s_sd1 = 2 */ + /* i2s_sd2 = 3 */ + /* i2s_sd3 = 4 */ + /* i2s_quad01 = 5 */ + /* i2s_quad23 = 6 */ + /* i2s_6chs = 7 */ + /* i2s_8chs = 8 */ + u16 channel; /* Called MonoStereo in documentation */ + /* i2s mono = 0 */ + /* i2s mono right = 1 */ + /* i2s mono left = 2 */ + /* i2s stereo = 3 */ + u16 ws; /* 0, word select signal from external source */ + /* 1, word select signal from internal source */ + u16 format; /* don't touch this field if it is not for */ + /* AFE_PORT_CMD_I2S_CONFIG opcode */ +} __attribute__ ((packed)); + +struct afe_port_hdmi_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 channel_mode; /* HDMI Stereo = 0 */ + /* HDMI_3Point1 (4-ch) = 1 */ + /* HDMI_5Point1 (6-ch) = 2 */ + /* HDMI_6Point1 (8-ch) = 3 */ + u16 data_type; /* HDMI_Linear = 0 */ + /* HDMI_non_Linear = 1 */ +} __attribute__ ((packed)); + + +struct afe_port_hdmi_multi_ch_cfg { + u16 data_type; /* HDMI_Linear = 0 */ + /* HDMI_non_Linear = 1 */ + u16 channel_allocation; /* The default is 0 (Stereo) */ + u16 reserved; /* must be set to 0 */ +} __packed; + + +/* Slimbus Device Ids */ +#define AFE_SLIMBUS_DEVICE_1 0x0 +#define AFE_SLIMBUS_DEVICE_2 0x1 +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 16 + +struct afe_port_slimbus_cfg { + u16 slimbus_dev_id; /* SLIMBUS Device id.*/ + + u16 slave_dev_pgd_la; /* Slave ported generic device + * logical address. + */ + u16 slave_dev_intfdev_la; /* Slave interface device logical + * address. + */ + u16 bit_width; /** bit width of the samples, 16, 24.*/ + + u16 data_format; /** data format.*/ + + u16 num_channels; /** Number of channels.*/ + + /** Slave port mapping for respective channels.*/ + u16 slave_port_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; + + u16 reserved; +} __packed; + +struct afe_port_slimbus_sch_cfg { + u16 slimbus_dev_id; /* SLIMBUS Device id.*/ + u16 bit_width; /** bit width of the samples, 16, 24.*/ + u16 data_format; /** data format.*/ + u16 num_channels; /** Number of channels.*/ + u16 reserved; + /** Slave channel mapping for respective channels.*/ + u8 slave_ch_mapping[8]; +} __packed; + +struct afe_port_rtproxy_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 interleaved; /* interleaved = 1 */ + /* Noninterleaved = 0 */ + u16 frame_sz; /* 5ms buffers = 160bytes */ + u16 jitter; /* 10ms of jitter = 320 */ + u16 lw_mark; /* Low watermark in bytes for triggering event*/ + u16 hw_mark; /* High watermark bytes for triggering event*/ + u16 rsvd; + int num_ch; /* 1 to 8 */ +} __packed; + +#define AFE_PORT_AUDIO_IF_CONFIG 0x000100d3 +#define AFE_PORT_AUDIO_SLIM_SCH_CONFIG 0x000100e4 +#define AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG 0x000100D9 +#define AFE_PORT_CMD_I2S_CONFIG 0x000100E7 + +union afe_port_config { + struct afe_port_pcm_cfg pcm; + struct afe_port_mi2s_cfg mi2s; + struct afe_port_hdmi_cfg hdmi; + struct afe_port_hdmi_multi_ch_cfg hdmi_multi_ch; + struct afe_port_slimbus_cfg slimbus; + struct afe_port_slimbus_sch_cfg slim_sch; + struct afe_port_rtproxy_cfg rtproxy; +} __attribute__((packed)); + +struct afe_audioif_config_command { + struct apr_hdr hdr; + u16 port_id; + union afe_port_config port; +} __attribute__ ((packed)); + +#define AFE_TEST_CODEC_LOOPBACK_CTL 0x000100d5 +struct afe_codec_loopback_command { + u16 port_inf; /* Primary i2s = 0 */ + /* PCM = 2 */ + /* Secondary i2s = 4 */ + /* Mi2s = 6 */ + u16 enable; /* 0, disable. 1, enable */ +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_SIDETONE_GAIN 0x00010300 +struct afe_param_sidetone_gain { + u16 gain; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PARAM_ID_SAMPLING_RATE 0x00010301 +struct afe_param_sampling_rate { + u32 sampling_rate; +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_CHANNELS 0x00010302 +struct afe_param_channels { + u16 channels; + u16 reserved; +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_LOOPBACK_GAIN 0x00010303 +struct afe_param_loopback_gain { + u16 gain; + u16 reserved; +} __attribute__ ((packed)); + +/* Parameter ID used to configure and enable/disable the loopback path. The + * difference with respect to the existing API, AFE_PORT_CMD_LOOPBACK, is that + * it allows Rx port to be configured as source port in loopback path. Port-id + * in AFE_PORT_CMD_SET_PARAM cmd is the source port whcih can be Tx or Rx port. + * In addition, we can configure the type of routing mode to handle different + * use cases. +*/ +enum { + /* Regular loopback from source to destination port */ + LB_MODE_DEFAULT = 1, + /* Sidetone feed from Tx source to Rx destination port */ + LB_MODE_SIDETONE, + /* Echo canceller reference, voice + audio + DTMF */ + LB_MODE_EC_REF_VOICE_AUDIO, + /* Echo canceller reference, voice alone */ + LB_MODE_EC_REF_VOICE +}; + +#define AFE_PARAM_ID_LOOPBACK_CONFIG 0x0001020B +#define AFE_API_VERSION_LOOPBACK_CONFIG 0x1 +struct afe_param_loopback_cfg { + /* Minor version used for tracking the version of the configuration + * interface. + */ + uint32_t loopback_cfg_minor_version; + + /* Destination Port Id. */ + uint16_t dst_port_id; + + /* Specifies data path type from src to dest port. Supported values: + * LB_MODE_DEFAULT + * LB_MODE_SIDETONE + * LB_MODE_EC_REF_VOICE_AUDIO + * LB_MODE_EC_REF_VOICE + */ + uint16_t routing_mode; + + /* Specifies whether to enable (1) or disable (0) an AFE loopback. */ + uint16_t enable; + + /* Reserved for 32-bit alignment. This field must be set to 0. */ + uint16_t reserved; +} __packed; + +#define AFE_MODULE_ID_PORT_INFO 0x00010200 +/* Module ID for the loopback-related parameters. */ +#define AFE_MODULE_LOOPBACK 0x00010205 +struct afe_param_payload { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; + union { + struct afe_param_sidetone_gain sidetone_gain; + struct afe_param_sampling_rate sampling_rate; + struct afe_param_channels channels; + struct afe_param_loopback_gain loopback_gain; + struct afe_param_loopback_cfg loopback_cfg; + } __attribute__((packed)) param; +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_SET_PARAM 0x000100dc + +struct afe_port_cmd_set_param { + struct apr_hdr hdr; + u16 port_id; + u16 payload_size; + u32 payload_address; + struct afe_param_payload payload; +} __attribute__ ((packed)); + +struct afe_port_cmd_set_param_no_payload { + struct apr_hdr hdr; + u16 port_id; + u16 payload_size; + u32 payload_address; +} __packed; + +#define AFE_EVENT_GET_ACTIVE_PORTS 0x00010100 +struct afe_get_active_ports_rsp { + u16 num_ports; + u16 port_id; +} __attribute__ ((packed)); + + +#define AFE_EVENT_GET_ACTIVE_HANDLES 0x00010102 +struct afe_get_active_handles_rsp { + u16 port_id; + u16 num_handles; + u16 mode; /* 0, voice rx */ + /* 1, voice tx */ + /* 2, audio rx */ + /* 3, audio tx */ + u16 handle; +} __attribute__ ((packed)); + +#define AFE_SERVICE_CMD_MEMORY_MAP 0x000100DE +struct afe_cmd_memory_map { + struct apr_hdr hdr; + u32 phy_addr; + u32 mem_sz; + u16 mem_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_MEMORY_UNMAP 0x000100DF +struct afe_cmd_memory_unmap { + struct apr_hdr hdr; + u32 phy_addr; +} __packed; + +#define AFE_SERVICE_CMD_REG_RTPORT 0x000100E0 +struct afe_cmd_reg_rtport { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_UNREG_RTPORT 0x000100E1 +struct afe_cmd_unreg_rtport { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; +} __packed; + +#define AFE_SERVICE_CMD_RTPORT_WR 0x000100E2 +struct afe_cmd_rtport_wr { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; + u32 buf_addr; + u32 bytes_avail; +} __packed; + +#define AFE_SERVICE_CMD_RTPORT_RD 0x000100E3 +struct afe_cmd_rtport_rd { + struct apr_hdr hdr; + u16 port_id; + u16 rsvd; + u32 buf_addr; + u32 bytes_avail; +} __packed; + +#define AFE_EVENT_RT_PROXY_PORT_STATUS 0x00010105 + +#define ADM_MAX_COPPS 5 + +#define ADM_SERVICE_CMD_GET_COPP_HANDLES 0x00010300 +struct adm_get_copp_handles_command { + struct apr_hdr hdr; +} __attribute__ ((packed)); + +#define ADM_CMD_MATRIX_MAP_ROUTINGS 0x00010301 +struct adm_routings_session { + u16 id; + u16 num_copps; + u16 copp_id[ADM_MAX_COPPS+1]; /*Padding if numCopps is odd */ +} __packed; + +struct adm_routings_command { + struct apr_hdr hdr; + u32 path; /* 0 = Rx, 1 Tx */ + u32 num_sessions; + struct adm_routings_session session[8]; +} __attribute__ ((packed)); + + +#define ADM_CMD_MATRIX_RAMP_GAINS 0x00010302 +struct adm_ramp_gain { + struct apr_hdr hdr; + u16 session_id; + u16 copp_id; + u16 initial_gain; + u16 gain_increment; + u16 ramp_duration; + u16 reserved; +} __attribute__ ((packed)); + +struct adm_ramp_gains_command { + struct apr_hdr hdr; + u32 id; + u32 num_gains; + struct adm_ramp_gain gains[ADM_MAX_COPPS]; +} __attribute__ ((packed)); + + +#define ADM_CMD_COPP_OPEN 0x00010304 +struct adm_copp_open_command { + struct apr_hdr hdr; + u16 flags; + u16 mode; /* 1-RX, 2-Live TX, 3-Non Live TX */ + u16 endpoint_id1; + u16 endpoint_id2; + u32 topology_id; + u16 channel_config; + u16 reserved; + u32 rate; +} __attribute__ ((packed)); + +#define ADM_CMD_COPP_CLOSE 0x00010305 + +#define ADM_CMD_MULTI_CHANNEL_COPP_OPEN 0x00010310 +struct adm_multi_ch_copp_open_command { + struct apr_hdr hdr; + u16 flags; + u16 mode; /* 1-RX, 2-Live TX, 3-Non Live TX */ + u16 endpoint_id1; + u16 endpoint_id2; + u32 topology_id; + u16 channel_config; + u16 reserved; + u32 rate; + u8 dev_channel_mapping[8]; +} __packed; + +#define ADM_CMD_MEMORY_MAP 0x00010C30 +struct adm_cmd_memory_map{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u16 mempool_id; + u16 reserved; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_UNMAP 0x00010C31 +struct adm_cmd_memory_unmap{ + struct apr_hdr hdr; + u32 buf_add; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_MAP_REGIONS 0x00010C47 +struct adm_memory_map_regions{ + u32 phys; + u32 buf_size; +} __attribute__((packed)); + +struct adm_cmd_memory_map_regions{ + struct apr_hdr hdr; + u16 mempool_id; + u16 nregions; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_UNMAP_REGIONS 0x00010C48 +struct adm_memory_unmap_regions{ + u32 phys; +} __attribute__((packed)); + +struct adm_cmd_memory_unmap_regions{ + struct apr_hdr hdr; + u16 nregions; + u16 reserved; +} __attribute__((packed)); + +#define DEFAULT_COPP_TOPOLOGY 0x00010be3 +#define DEFAULT_POPP_TOPOLOGY 0x00010be4 +#define VPM_TX_SM_ECNS_COPP_TOPOLOGY 0x00010F71 +#define VPM_TX_DM_FLUENCE_COPP_TOPOLOGY 0x00010F72 +#define VPM_TX_QMIC_FLUENCE_COPP_TOPOLOGY 0x00010F75 + +/* SRS TRUMEDIA GUIDS */ +/* topology */ +#define SRS_TRUMEDIA_TOPOLOGY_ID 0x00010D90 +/* module */ +#define SRS_TRUMEDIA_MODULE_ID 0x10005010 +/* parameters */ +#define SRS_TRUMEDIA_PARAMS 0x10005011 +#define SRS_TRUMEDIA_PARAMS_WOWHD 0x10005012 +#define SRS_TRUMEDIA_PARAMS_CSHP 0x10005013 +#define SRS_TRUMEDIA_PARAMS_HPF 0x10005014 +#define SRS_TRUMEDIA_PARAMS_PEQ 0x10005015 +#define SRS_TRUMEDIA_PARAMS_HL 0x10005016 + +#define ASM_MAX_EQ_BANDS 12 + +struct asm_eq_band { + u32 band_idx; /* The band index, 0 .. 11 */ + u32 filter_type; /* Filter band type */ + u32 center_freq_hz; /* Filter band center frequency */ + u32 filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + u32 q_factor; +} __attribute__ ((packed)); + +struct asm_equalizer_params { + u32 enable; + u32 num_bands; + struct asm_eq_band eq_bands[ASM_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +struct asm_master_gain_params { + u16 master_gain; + u16 padding; +} __attribute__ ((packed)); + +struct asm_lrchannel_gain_params { + u16 left_gain; + u16 right_gain; +} __attribute__ ((packed)); + +struct asm_mute_params { + u32 muteflag; +} __attribute__ ((packed)); + +struct asm_softvolume_params { + u32 period; + u32 step; + u32 rampingcurve; +} __attribute__ ((packed)); + +struct asm_softpause_params { + u32 enable; + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +struct asm_pp_param_data_hdr { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; +} __attribute__ ((packed)); + +struct asm_pp_params_command { + struct apr_hdr hdr; + u32 *payload; + u32 payload_size; + struct asm_pp_param_data_hdr params; +} __attribute__ ((packed)); + +#define EQUALIZER_MODULE_ID 0x00010c27 +#define EQUALIZER_PARAM_ID 0x00010c28 + +#define VOLUME_CONTROL_MODULE_ID 0x00010bfe +#define MASTER_GAIN_PARAM_ID 0x00010bff +#define L_R_CHANNEL_GAIN_PARAM_ID 0x00010c00 +#define MUTE_CONFIG_PARAM_ID 0x00010c01 +#define SOFT_PAUSE_PARAM_ID 0x00010D6A +#define SOFT_VOLUME_PARAM_ID 0x00010C29 + +#define IIR_FILTER_ENABLE_PARAM_ID 0x00010c03 +#define IIR_FILTER_PREGAIN_PARAM_ID 0x00010c04 +#define IIR_FILTER_CONFIG_PARAM_ID 0x00010c05 + +#define MBADRC_MODULE_ID 0x00010c06 +#define MBADRC_ENABLE_PARAM_ID 0x00010c07 +#define MBADRC_CONFIG_PARAM_ID 0x00010c08 + + +#define ADM_CMD_SET_PARAMS 0x00010306 +#define ADM_CMD_GET_PARAMS 0x0001030B +#define ADM_CMDRSP_GET_PARAMS 0x0001030C +struct adm_set_params_command { + struct apr_hdr hdr; + u32 payload; + u32 payload_size; +} __attribute__ ((packed)); + + +#define ADM_CMD_TAP_COPP_PCM 0x00010307 +struct adm_tap_copp_pcm_command { + struct apr_hdr hdr; +} __attribute__ ((packed)); + + +/* QDSP6 to Client messages +*/ +#define ADM_SERVICE_CMDRSP_GET_COPP_HANDLES 0x00010308 +struct adm_get_copp_handles_respond { + struct apr_hdr hdr; + u32 handles; + u32 copp_id; +} __attribute__ ((packed)); + +#define ADM_CMDRSP_COPP_OPEN 0x0001030A +struct adm_copp_open_respond { + u32 status; + u16 copp_id; + u16 reserved; +} __attribute__ ((packed)); + +#define ADM_CMDRSP_MULTI_CHANNEL_COPP_OPEN 0x00010311 + + +#define ASM_STREAM_PRIORITY_NORMAL 0 +#define ASM_STREAM_PRIORITY_LOW 1 +#define ASM_STREAM_PRIORITY_HIGH 2 +#define ASM_STREAM_PRIORITY_RESERVED 3 + +#define ASM_END_POINT_DEVICE_MATRIX 0 +#define ASM_END_POINT_STREAM 1 + +#define AAC_ENC_MODE_AAC_LC 0x02 +#define AAC_ENC_MODE_AAC_P 0x05 +#define AAC_ENC_MODE_EAAC_P 0x1D + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE +#define ASM_STREAM_CMD_SET_PP_PARAMS 0x00010BCF +#define ASM_STREAM_CMD_GET_PP_PARAMS 0x00010BD0 +#define ASM_STREAM_CMDRSP_GET_PP_PARAMS 0x00010BD1 +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_SESSION_CMD_GET_SESSION_TIME 0x00010BD4 +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_EOS 0x00010BDD + +#define ASM_SERVICE_CMD_GET_STREAM_HANDLES 0x00010C0B +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 + +#define ASM_SESSION_EVENT_RX_UNDERFLOW 0x00010C17 +#define ASM_SESSION_EVENT_TX_OVERFLOW 0x00010C18 +#define ASM_SERVICE_CMD_GET_WALLCLOCK_TIME 0x00010C19 +#define ASM_DATA_CMDRSP_EOS 0x00010C1C + +/* ASM Data structures */ + +/* common declarations */ +struct asm_pcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; + u16 is_signed; + u16 interleaved; +}; + +#define PCM_CHANNEL_NULL 0 + +/* Front left channel. */ +#define PCM_CHANNEL_FL 1 + +/* Front right channel. */ +#define PCM_CHANNEL_FR 2 + +/* Front center channel. */ +#define PCM_CHANNEL_FC 3 + +/* Left surround channel.*/ +#define PCM_CHANNEL_LS 4 + +/* Right surround channel.*/ +#define PCM_CHANNEL_RS 5 + +/* Low frequency effect channel. */ +#define PCM_CHANNEL_LFE 6 + +/* Center surround channel; Rear center channel. */ +#define PCM_CHANNEL_CS 7 + +/* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_LB 8 + +/* Right back channel; Rear right channel. */ +#define PCM_CHANNEL_RB 9 + +/* Top surround channel. */ +#define PCM_CHANNEL_TS 10 + +/* Center vertical height channel.*/ +#define PCM_CHANNEL_CVH 11 + +/* Mono surround channel.*/ +#define PCM_CHANNEL_MS 12 + +/* Front left of center. */ +#define PCM_CHANNEL_FLC 13 + +/* Front right of center. */ +#define PCM_CHANNEL_FRC 14 + +/* Rear left of center. */ +#define PCM_CHANNEL_RLC 15 + +/* Rear right of center. */ +#define PCM_CHANNEL_RRC 16 + +#define PCM_FORMAT_MAX_NUM_CHANNEL 8 + +/* Maximum number of channels supported + * in ASM_ENCDEC_DEC_CHAN_MAP command + */ +#define MAX_CHAN_MAP_CHANNELS 16 +/* + * Multiple-channel PCM decoder format block structure used in the + * #ASM_STREAM_CMD_OPEN_WRITE command. + * The data must be in little-endian format. + */ +struct asm_multi_channel_pcm_fmt_blk { + + u16 num_channels; /* + * Number of channels. + * Supported values:1 to 8 + */ + + u16 bits_per_sample; /* + * Number of bits per sample per channel. + * Supported values: 16, 24 When used for + * playback, the client must send 24-bit + * samples packed in 32-bit words. The + * 24-bit samples must be placed in the most + * significant 24 bits of the 32-bit word. When + * used for recording, the aDSP sends 24-bit + * samples packed in 32-bit words. The 24-bit + * samples are placed in the most significant + * 24 bits of the 32-bit word. + */ + + u32 sample_rate; /* + * Number of samples per second + * (in Hertz). Supported values: + * 2000 to 48000 + */ + + u16 is_signed; /* + * Flag that indicates the samples + * are signed (1). + */ + + u16 is_interleaved; /* + * Flag that indicates whether the channels are + * de-interleaved (0) or interleaved (1). + * Interleaved format means corresponding + * samples from the left and right channels are + * interleaved within the buffer. + * De-interleaved format means samples from + * each channel are contiguous in the buffer. + * The samples from one channel immediately + * follow those of the previous channel. + */ + + u8 channel_mapping[8]; /* + * Supported values: + * PCM_CHANNEL_NULL, PCM_CHANNEL_FL, + * PCM_CHANNEL_FR, PCM_CHANNEL_FC, + * PCM_CHANNEL_LS, PCM_CHANNEL_RS, + * PCM_CHANNEL_LFE, PCM_CHANNEL_CS, + * PCM_CHANNEL_LB, PCM_CHANNEL_RB, + * PCM_CHANNEL_TS, PCM_CHANNEL_CVH, + * PCM_CHANNEL_MS, PCM_CHANNEL_FLC, + * PCM_CHANNEL_FRC, PCM_CHANNEL_RLC, + * PCM_CHANNEL_RRC. + * Channel[i] mapping describes channel I. Each + * element i of the array describes channel I + * inside the buffer where I < num_channels. + * An unused channel is set to zero. + */ +}; + +struct asm_adpcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; + u32 block_size; +}; + +struct asm_yadpcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; +}; + +struct asm_midi_cfg { + u32 nMode; +}; + +struct asm_wma_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +}; + +struct asm_wmapro_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +}; + +struct asm_aac_cfg { + u16 format; + u16 aot; + u16 ep_config; + u16 section_data_resilience; + u16 scalefactor_data_resilience; + u16 spectral_data_resilience; + u16 ch_cfg; + u16 reserved; + u32 sample_rate; +}; + +struct asm_flac_cfg { + u16 stream_info_present; + u16 min_blk_size; + u16 max_blk_size; + u16 ch_cfg; + u16 sample_size; + u16 sample_rate; + u16 md5_sum; + u32 ext_sample_rate; + u32 min_frame_size; + u32 max_frame_size; +}; + +struct asm_vorbis_cfg { + u32 ch_cfg; + u32 bit_rate; + u32 min_bit_rate; + u32 max_bit_rate; + u16 bit_depth_pcm_sample; + u16 bit_stream_format; +}; + +struct asm_aac_read_cfg { + u32 bitrate; + u32 enc_mode; + u16 format; + u16 ch_cfg; + u32 sample_rate; +}; + +struct asm_amrnb_read_cfg { + u16 mode; + u16 dtx_mode; +}; + +struct asm_amrwb_read_cfg { + u16 mode; + u16 dtx_mode; +}; + +struct asm_evrc_read_cfg { + u16 max_rate; + u16 min_rate; + u16 rate_modulation_cmd; + u16 reserved; +}; + +struct asm_qcelp13_read_cfg { + u16 max_rate; + u16 min_rate; + u16 reduced_rate_level; + u16 rate_modulation_cmd; +}; + +struct asm_sbc_read_cfg { + u32 subband; + u32 block_len; + u32 ch_mode; + u32 alloc_method; + u32 bit_rate; + u32 sample_rate; +}; + +struct asm_sbc_bitrate { + u32 bitrate; +}; + +struct asm_immed_decode { + u32 mode; +}; + +struct asm_sbr_ps { + u32 enable; +}; + +struct asm_dual_mono { + u16 sce_left; + u16 sce_right; +}; + +struct asm_dec_chan_map { + u32 num_channels; /* Number of decoder output + * channels. A value of 0 + * indicates native channel + * mapping, which is valid + * only for NT mode. This + * means the output of the + * decoder is to be preserved + * as is. + */ + + u8 channel_mapping[MAX_CHAN_MAP_CHANNELS];/* Channel array of size + * num_channels. It can grow + * till MAX_CHAN_MAP_CHANNELS. + * Channel[i] mapping + * describes channel I inside + * the decoder output buffer. + * Valid channel mapping + * values are to be present at + * the beginning of the array. + * All remaining elements of + * the array are to be filled + * with PCM_CHANNEL_NULL. + */ +}; + +struct asm_encode_cfg_blk { + u32 frames_per_buf; + u32 format_id; + u32 cfg_size; + union { + struct asm_pcm_cfg pcm; + struct asm_aac_read_cfg aac; + struct asm_amrnb_read_cfg amrnb; + struct asm_evrc_read_cfg evrc; + struct asm_qcelp13_read_cfg qcelp13; + struct asm_sbc_read_cfg sbc; + struct asm_amrwb_read_cfg amrwb; + struct asm_multi_channel_pcm_fmt_blk mpcm; + } __attribute__((packed)) cfg; +}; + +struct asm_frame_meta_info { + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +}; + +/* Stream level commands */ +#define ASM_STREAM_CMD_OPEN_READ 0x00010BCB +struct asm_stream_cmd_open_read { + struct apr_hdr hdr; + u32 uMode; + u32 src_endpoint; + u32 pre_proc_top; + u32 format; +} __attribute__((packed)); + +/* Supported formats */ +#define LINEAR_PCM 0x00010BE5 +#define DTMF 0x00010BE6 +#define ADPCM 0x00010BE7 +#define YADPCM 0x00010BE8 +#define MP3 0x00010BE9 +#define MPEG4_AAC 0x00010BEA +#define AMRNB_FS 0x00010BEB +#define AMRWB_FS 0x00010BEC +#define V13K_FS 0x00010BED +#define EVRC_FS 0x00010BEE +#define EVRCB_FS 0x00010BEF +#define EVRCWB_FS 0x00010BF0 +#define MIDI 0x00010BF1 +#define SBC 0x00010BF2 +#define WMA_V10PRO 0x00010BF3 +#define WMA_V9 0x00010BF4 +#define AMR_WB_PLUS 0x00010BF5 +#define AC3_DECODER 0x00010BF6 +#define EAC3_DECODER 0x00010C3C +#define DTS 0x00010D88 +#define ATRAC 0x00010D89 +#define MAT 0x00010D8A +#define G711_ALAW_FS 0x00010BF7 +#define G711_MLAW_FS 0x00010BF8 +#define G711_PCM_FS 0x00010BF9 +#define MPEG4_MULTI_AAC 0x00010D86 +#define US_POINT_EPOS_FORMAT 0x00012310 +#define US_RAW_FORMAT 0x0001127C +#define MULTI_CHANNEL_PCM 0x00010C66 + +#define ASM_ENCDEC_SBCRATE 0x00010C13 +#define ASM_ENCDEC_IMMDIATE_DECODE 0x00010C14 +#define ASM_ENCDEC_CFG_BLK 0x00010C2C + +#define ASM_ENCDEC_SBCRATE 0x00010C13 +#define ASM_ENCDEC_IMMDIATE_DECODE 0x00010C14 +#define ASM_ENCDEC_CFG_BLK 0x00010C2C + +#define ASM_STREAM_CMD_OPEN_WRITE 0x00010BCA +struct asm_stream_cmd_open_write { + struct apr_hdr hdr; + u32 uMode; + u16 sink_endpoint; + u16 stream_handle; + u32 post_proc_top; + u32 format; +} __attribute__((packed)); + +#define IEC_61937_MASK 0x00000001 +#define IEC_60958_MASK 0x00000002 + +#define ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED 0x00010D84 +struct asm_stream_cmd_open_write_compressed { + struct apr_hdr hdr; + u32 flags; + u32 format; +} __packed; + +#define ASM_STREAM_CMD_OPEN_READWRITE 0x00010BCC + +struct asm_stream_cmd_open_read_write { + struct apr_hdr hdr; + u32 uMode; + u32 post_proc_top; + u32 write_format; + u32 read_format; +} __attribute__((packed)); + +#define ADM_CMD_CONNECT_AFE_PORT 0x00010320 + +struct adm_cmd_connect_afe_port { + struct apr_hdr hdr; + u8 mode; /*mode represent the interface is for RX or TX*/ + u8 session_id; /*ASM session ID*/ + u16 afe_port_id; +} __packed; + +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 +#define ASM_STREAM_CMD_GET_ENCDEC_PARAM 0x00010C11 +#define ASM_ENCDEC_CFG_BLK_ID 0x00010C2C +#define ASM_ENABLE_SBR_PS 0x00010C63 +#define ASM_CONFIGURE_DUAL_MONO 0x00010C64 +struct asm_stream_cmd_encdec_cfg_blk{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_encode_cfg_blk enc_blk; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_sbc_bitrate{ + struct apr_hdr hdr; + u32 param_id; + struct asm_sbc_bitrate sbc_bitrate; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_immed_decode{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_immed_decode dec; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_sbr{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_sbr_ps sbr_ps; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_dualmono { + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_dual_mono channel_map; +} __packed; + +#define ASM_ENCDEC_DEC_CHAN_MAP 0x00010D82 +struct asm_stream_cmd_encdec_channelmap { + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_dec_chan_map chan_map; +} __packed; + +#define ASM_STREAM _CMD_ADJUST_SAMPLES 0x00010C0A +struct asm_stream_cmd_adjust_samples{ + struct apr_hdr hdr; + u16 nsamples; + u16 reserved; +} __attribute__((packed)); + +#define ASM_STREAM_CMD_TAP_POPP_PCM 0x00010BF9 +struct asm_stream_cmd_tap_popp_pcm{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; + u32 module_id; +} __attribute__((packed)); + +/* Session Level commands */ +#define ASM_SESSION_CMD_MEMORY_MAP 0x00010C32 +struct asm_stream_cmd_memory_map{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u16 mempool_id; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_UNMAP 0x00010C33 +struct asm_stream_cmd_memory_unmap{ + struct apr_hdr hdr; + u32 buf_add; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_MAP_REGIONS 0x00010C45 +struct asm_memory_map_regions{ + u32 phys; + u32 buf_size; +} __attribute__((packed)); + +struct asm_stream_cmd_memory_map_regions{ + struct apr_hdr hdr; + u16 mempool_id; + u16 nregions; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS 0x00010C46 +struct asm_memory_unmap_regions{ + u32 phys; +} __attribute__((packed)); + +struct asm_stream_cmd_memory_unmap_regions{ + struct apr_hdr hdr; + u16 nregions; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_RUN 0x00010BD2 +struct asm_stream_cmd_run{ + struct apr_hdr hdr; + u32 flags; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +/* Session level events */ +#define ASM_SESSION_CMD_REGISTER_FOR_RX_UNDERFLOW_EVENTS 0x00010BD5 +struct asm_stream_cmd_reg_rx_underflow_event{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS 0x00010BD6 +struct asm_stream_cmd_reg_tx_overflow_event{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; +} __attribute__((packed)); + +/* Data Path commands */ +#define ASM_DATA_CMD_WRITE 0x00010BD9 +struct asm_stream_cmd_write{ + struct apr_hdr hdr; + u32 buf_add; + u32 avail_bytes; + u32 uid; + u32 msw_ts; + u32 lsw_ts; + u32 uflags; +} __attribute__((packed)); + +#define ASM_DATA_CMD_READ 0x00010BDA +struct asm_stream_cmd_read{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u32 uid; +} __attribute__((packed)); + +#define ASM_DATA_CMD_MEDIA_FORMAT_UPDATE 0x00010BDC +#define ASM_DATA_EVENT_ENC_SR_CM_NOTIFY 0x00010BDE +struct asm_stream_media_format_update{ + struct apr_hdr hdr; + u32 format; + u32 cfg_size; + union { + struct asm_pcm_cfg pcm_cfg; + struct asm_adpcm_cfg adpcm_cfg; + struct asm_yadpcm_cfg yadpcm_cfg; + struct asm_midi_cfg midi_cfg; + struct asm_wma_cfg wma_cfg; + struct asm_wmapro_cfg wmapro_cfg; + struct asm_aac_cfg aac_cfg; + struct asm_flac_cfg flac_cfg; + struct asm_vorbis_cfg vorbis_cfg; + struct asm_multi_channel_pcm_fmt_blk multi_ch_pcm_cfg; + } __attribute__((packed)) write_cfg; +} __attribute__((packed)); + + +/* Command Responses */ +#define ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM 0x00010C12 +struct asm_stream_cmdrsp_get_readwrite_param{ + struct apr_hdr hdr; + u32 status; + u32 param_id; + u16 param_size; + u16 padding; + union { + struct asm_sbc_bitrate sbc_bitrate; + struct asm_immed_decode aac_dec; + } __attribute__((packed)) read_write_cfg; +} __attribute__((packed)); + + +#define ASM_SESSION_CMDRSP_GET_SESSION_TIME 0x00010BD8 +struct asm_stream_cmdrsp_get_session_time{ + struct apr_hdr hdr; + u32 status; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_WRITE_DONE 0x00010BDF +struct asm_data_event_write_done{ + u32 buf_add; + u32 status; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_READ_DONE 0x00010BE0 +struct asm_data_event_read_done{ + u32 status; + u32 buffer_add; + u32 enc_frame_size; + u32 offset; + u32 msw_ts; + u32 lsw_ts; + u32 flags; + u32 num_frames; + u32 id; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY 0x00010C65 +struct asm_data_event_sr_cm_change_notify { + u32 sample_rate; + u16 no_of_channels; + u16 reserved; + u8 channel_map[8]; +} __packed; + +/* service level events */ + +#define ASM_SERVICE_CMDRSP_GET_STREAM_HANDLES 0x00010C1B +struct asm_svc_cmdrsp_get_strm_handles{ + struct apr_hdr hdr; + u32 num_handles; + u32 stream_handles; +} __attribute__((packed)); + + +#define ASM_SERVICE_CMDRSP_GET_WALLCLOCK_TIME 0x00010C1A +struct asm_svc_cmdrsp_get_wallclock_time{ + struct apr_hdr hdr; + u32 status; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +/* + * Error code +*/ +#define ADSP_EOK 0x00000000 /* Success / completed / no errors. */ +#define ADSP_EFAILED 0x00000001 /* General failure. */ +#define ADSP_EBADPARAM 0x00000002 /* Bad operation parameter(s). */ +#define ADSP_EUNSUPPORTED 0x00000003 /* Unsupported routine/operation. */ +#define ADSP_EVERSION 0x00000004 /* Unsupported version. */ +#define ADSP_EUNEXPECTED 0x00000005 /* Unexpected problem encountered. */ +#define ADSP_EPANIC 0x00000006 /* Unhandled problem occurred. */ +#define ADSP_ENORESOURCE 0x00000007 /* Unable to allocate resource(s). */ +#define ADSP_EHANDLE 0x00000008 /* Invalid handle. */ +#define ADSP_EALREADY 0x00000009 /* Operation is already processed. */ +#define ADSP_ENOTREADY 0x0000000A /* Operation not ready to be processed*/ +#define ADSP_EPENDING 0x0000000B /* Operation is pending completion*/ +#define ADSP_EBUSY 0x0000000C /* Operation could not be accepted or + processed. */ +#define ADSP_EABORTED 0x0000000D /* Operation aborted due to an error. */ +#define ADSP_EPREEMPTED 0x0000000E /* Operation preempted by higher priority*/ +#define ADSP_ECONTINUE 0x0000000F /* Operation requests intervention + to complete. */ +#define ADSP_EIMMEDIATE 0x00000010 /* Operation requests immediate + intervention to complete. */ +#define ADSP_ENOTIMPL 0x00000011 /* Operation is not implemented. */ +#define ADSP_ENEEDMORE 0x00000012 /* Operation needs more data or resources*/ + +/* SRS TRUMEDIA start */ +#define SRS_ID_GLOBAL 0x00000001 +#define SRS_ID_WOWHD 0x00000002 +#define SRS_ID_CSHP 0x00000003 +#define SRS_ID_HPF 0x00000004 +#define SRS_ID_PEQ 0x00000005 +#define SRS_ID_HL 0x00000006 + +#define SRS_CMD_UPLOAD 0x7FFF0000 +#define SRS_PARAM_INDEX_MASK 0x80000000 +#define SRS_PARAM_OFFSET_MASK 0x3FFF0000 +#define SRS_PARAM_VALUE_MASK 0x0000FFFF + +struct srs_trumedia_params_GLOBAL { + uint8_t v1; + uint8_t v2; + uint8_t v3; + uint8_t v4; + uint8_t v5; + uint8_t v6; + uint8_t v7; + uint8_t v8; +} __packed; + +struct srs_trumedia_params_WOWHD { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v5; + uint16_t v6; + uint16_t v7; + uint16_t v8; + uint16_t v____A1; + uint32_t v9; + uint16_t v10; + uint16_t v11; + uint32_t v12[16]; +} __packed; + +struct srs_trumedia_params_CSHP { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v5; + uint16_t v6; + uint16_t v____A1; + uint32_t v7; + uint16_t v8; + uint16_t v9; + uint32_t v10[16]; +} __packed; + +struct srs_trumedia_params_HPF { + uint32_t v1; + uint32_t v2[26]; +} __packed; + +struct srs_trumedia_params_PEQ { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v4; + uint16_t v____A1; + uint32_t v5[26]; + uint32_t v6[26]; +} __packed; + +struct srs_trumedia_params_HL { + uint16_t v1; + uint16_t v2; + uint16_t v3; + uint16_t v____A1; + int32_t v4; + uint32_t v5; + uint16_t v6; + uint16_t v____A2; + uint32_t v7; +} __packed; + +struct srs_trumedia_params { + struct srs_trumedia_params_GLOBAL global; + struct srs_trumedia_params_WOWHD wowhd; + struct srs_trumedia_params_CSHP cshp; + struct srs_trumedia_params_HPF hpf; + struct srs_trumedia_params_PEQ peq; + struct srs_trumedia_params_HL hl; +} __packed; +int srs_trumedia_open(int port_id, int srs_tech_id, void *srs_params); +/* SRS TruMedia end */ + +#endif /*_APR_AUDIO_H_*/ diff --git a/include/sound/cs8427.h b/include/sound/cs8427.h index f862cfff5f6a..2004ec3378f6 100644 --- a/include/sound/cs8427.h +++ b/include/sound/cs8427.h @@ -108,6 +108,7 @@ #define CS8427_SIDEL (1<<2) /* Delay of SDIN data relative to ILRCK for left-justified data formats, 0 = first ISCLK period, 1 = second ISCLK period */ #define CS8427_SISPOL (1<<1) /* ICLK clock polarity, 0 = rising edge of ISCLK, 1 = falling edge of ISCLK */ #define CS8427_SILRPOL (1<<0) /* ILRCK clock polarity, 0 = SDIN data left channel when ILRCK is high, 1 = SDIN right when ILRCK is high */ +#define CS8427_BITWIDTH_MASK 0xCF /* CS8427_REG_SERIALOUTPUT */ #define CS8427_SOMS (1<<7) /* 0 = slave, 1 = master mode */ @@ -186,6 +187,31 @@ #define CS8427_VERSHIFT 0 #define CS8427_VER8427A 0x71 +/* possible address cs8427 can take + * based on the below combinations the upper four bits of 7bit + * address will be fixed for 0010b, abd lower 3 bits will decide + * the address combination based on the AD0 and AD1 and EMPH(AD2) + * Hardware pin configuration to cs8427 chip + */ +#define CS8427_ADDR0 0x10 +#define CS8427_ADDR1 0x11 +#define CS8427_ADDR2 0x12 +#define CS8427_ADDR3 0x13 +#define CS8427_ADDR4 0x14 +#define CS8427_ADDR5 0x15 +#define CS8427_ADDR6 0x16 +#define CS8427_ADDR7 0x17 + +#define CHANNEL_STATUS_SIZE 24 + +struct cs8427_platform_data { + int irq; + int irq_base; + int num_irqs; + int reset_gpio; + int (*enable) (int enable); +}; + struct snd_pcm_substream; int snd_cs8427_create(struct snd_i2c_bus *bus, unsigned char addr, @@ -197,5 +223,4 @@ int snd_cs8427_iec958_build(struct snd_i2c_device *cs8427, struct snd_pcm_substream *capture_substream); int snd_cs8427_iec958_active(struct snd_i2c_device *cs8427, int active); int snd_cs8427_iec958_pcm(struct snd_i2c_device *cs8427, unsigned int rate); - #endif /* __SOUND_CS8427_H */ diff --git a/include/sound/dai.h b/include/sound/dai.h new file mode 100644 index 000000000000..031fe3b80a5a --- /dev/null +++ b/include/sound/dai.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __DAI_H__ +#define __DAI_H__ + +struct dai_dma_params { + u8 *buffer; + uint32_t src_start; + uint32_t bus_id; + int buffer_size; + int period_size; + int channels; +}; + +enum { + DAI_SPKR = 0, + DAI_MIC, + DAI_MI2S, + DAI_SEC_SPKR, + DAI_SEC_MIC, +}; + +/* Function Prototypes */ +int dai_open(uint32_t dma_ch); +void dai_close(uint32_t dma_ch); +int dai_start(uint32_t dma_ch); +int dai_stop(uint32_t dma_ch); +int dai_set_params(uint32_t dma_ch, struct dai_dma_params *params); +uint32_t dai_get_dma_pos(uint32_t dma_ch); +void register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback) (int intrSrc, void *private_data), + void *private_data); +void unregister_dma_irq_handler(int dma_ch); +void dai_set_master_mode(uint32_t dma_ch, int mode); +int dai_start_hdmi(uint32_t dma_ch); +int wait_for_dma_cnt_stop(uint32_t dma_ch); +void dai_stop_hdmi(uint32_t dma_ch); + +#endif diff --git a/include/sound/msm-dai-q6-v2.h b/include/sound/msm-dai-q6-v2.h new file mode 100644 index 000000000000..cae213af0118 --- /dev/null +++ b/include/sound/msm-dai-q6-v2.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_DAI_Q6_PDATA_H__ + +#define __MSM_DAI_Q6_PDATA_H__ + +#define MSM_MI2S_SD0 (1 << 0) +#define MSM_MI2S_SD1 (1 << 1) +#define MSM_MI2S_SD2 (1 << 2) +#define MSM_MI2S_SD3 (1 << 3) +#define MSM_MI2S_CAP_RX 0 +#define MSM_MI2S_CAP_TX 1 + +struct msm_dai_auxpcm_pdata { + const char *clk; + u16 mode; + u16 sync; + u16 frame; + u16 quant; + /* modify slot to arr[4] to specify + * the slot number for each channel + * in multichannel scenario */ + u16 slot; + u16 data; + int pcm_clk_rate; +}; + +struct msm_i2s_data { + u32 capability; /* RX or TX */ + u16 sd_lines; +}; +#endif diff --git a/include/sound/msm-dai-q6.h b/include/sound/msm-dai-q6.h new file mode 100644 index 000000000000..a39d3dc08d00 --- /dev/null +++ b/include/sound/msm-dai-q6.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_DAI_Q6_PDATA_H__ + +#define __MSM_DAI_Q6_PDATA_H__ + +#define MSM_MI2S_SD0 (1 << 0) +#define MSM_MI2S_SD1 (1 << 1) +#define MSM_MI2S_SD2 (1 << 2) +#define MSM_MI2S_SD3 (1 << 3) +#define MSM_MI2S_CAP_RX 0 +#define MSM_MI2S_CAP_TX 1 + +struct msm_dai_auxpcm_config { + u16 mode; + u16 sync; + u16 frame; + u16 quant; + u16 slot; + u16 data; + int pcm_clk_rate; +}; + +struct msm_mi2s_pdata { + u16 rx_sd_lines; + u16 tx_sd_lines; +}; + +struct msm_dai_auxpcm_pdata { + const char *clk; + struct msm_dai_auxpcm_config mode_8k; + struct msm_dai_auxpcm_config mode_16k; +}; + +#endif diff --git a/include/sound/omap-abe-dsp.h b/include/sound/omap-abe-dsp.h new file mode 100644 index 000000000000..60c405d48c61 --- /dev/null +++ b/include/sound/omap-abe-dsp.h @@ -0,0 +1,19 @@ +/* + * omap-aess -- OMAP4 ABE DSP + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _OMAP4_ABE_DSP_H +#define _OMAP4_ABE_DSP_H + +struct omap4_abe_dsp_pdata { + /* Return context loss count due to PM states changing */ + int (*get_context_loss_count)(struct device *dev); +}; + +#endif diff --git a/include/sound/q6adm-v2.h b/include/sound/q6adm-v2.h new file mode 100644 index 000000000000..a800f9ac87a3 --- /dev/null +++ b/include/sound/q6adm-v2.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ADM_V2_H__ +#define __Q6_ADM_V2_H__ + + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define ADM_PATH_NONLIVE_REC 0x3 +#include + +#define Q6_AFE_MAX_PORTS 32 + +/* multiple copp per stream. */ +struct route_payload { + unsigned int copp_ids[Q6_AFE_MAX_PORTS]; + unsigned short num_copps; + unsigned int session_id; +}; + +int adm_open(int port, int path, int rate, int mode, int topology); + +int adm_multi_ch_copp_open(int port, int path, int rate, int mode, + int topology); + +int adm_memory_map_regions(int port_id, uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt); + +int adm_memory_unmap_regions(int port_id, uint32_t *buf_add, uint32_t *bufsz, + uint32_t bufcnt); + +int adm_close(int port); + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id); + +int adm_connect_afe_port(int mode, int session_id, int port_id); + +int adm_get_copp_id(int port_id); + +#endif /* __Q6_ADM_V2_H__ */ diff --git a/include/sound/q6adm.h b/include/sound/q6adm.h new file mode 100644 index 000000000000..7f549fda6378 --- /dev/null +++ b/include/sound/q6adm.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ADM_H__ +#define __Q6_ADM_H__ +#include + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define ADM_PATH_NONLIVE_REC 0x3 + +/* multiple copp per stream. */ +struct route_payload { + unsigned int copp_ids[AFE_MAX_PORTS]; + unsigned short num_copps; + unsigned int session_id; +}; + +int adm_open(int port, int path, int rate, int mode, int topology); + +int adm_multi_ch_copp_open(int port, int path, int rate, int mode, + int topology); + +int adm_memory_map_regions(uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt); + +int adm_memory_unmap_regions(uint32_t *buf_add, uint32_t *bufsz, + uint32_t bufcnt); + +int adm_close(int port); + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id); + +int adm_connect_afe_port(int mode, int session_id, int port_id); + +#ifdef CONFIG_RTAC +int adm_get_copp_id(int port_id); +#endif + +#endif /* __Q6_ADM_H__ */ diff --git a/include/sound/q6afe-v2.h b/include/sound/q6afe-v2.h new file mode 100644 index 000000000000..e0dc3257dda7 --- /dev/null +++ b/include/sound/q6afe-v2.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6AFE_V2_H__ +#define __Q6AFE_V2_H__ +#include + +#define MSM_AFE_MONO 0 +#define MSM_AFE_MONO_RIGHT 1 +#define MSM_AFE_MONO_LEFT 2 +#define MSM_AFE_STEREO 3 +#define MSM_AFE_4CHANNELS 4 +#define MSM_AFE_6CHANNELS 6 +#define MSM_AFE_8CHANNELS 8 + +#define MSM_AFE_I2S_FORMAT_LPCM 0 +#define MSM_AFE_I2S_FORMAT_COMPR 1 +#define MSM_AFE_I2S_FORMAT_IEC60958_LPCM 2 +#define MSM_AFE_I2S_FORMAT_IEC60958_COMPR 3 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 + +#define RT_PROXY_DAI_001_RX 0xE0 +#define RT_PROXY_DAI_001_TX 0xF0 +#define RT_PROXY_DAI_002_RX 0xF1 +#define RT_PROXY_DAI_002_TX 0xE1 +#define VIRTUAL_ID_TO_PORTID(val) ((val & 0xF) | 0x2000) + +enum { + IDX_PRIMARY_I2S_RX = 0, + IDX_PRIMARY_I2S_TX = 1, + IDX_PCM_RX = 2, + IDX_PCM_TX = 3, + IDX_SECONDARY_I2S_RX = 4, + IDX_SECONDARY_I2S_TX = 5, + IDX_MI2S_RX = 6, + IDX_MI2S_TX = 7, + IDX_HDMI_RX = 8, + IDX_RSVD_2 = 9, + IDX_RSVD_3 = 10, + IDX_DIGI_MIC_TX = 11, + IDX_VOICE_RECORD_RX = 12, + IDX_VOICE_RECORD_TX = 13, + IDX_VOICE_PLAYBACK_TX = 14, + IDX_SLIMBUS_0_RX = 15, + IDX_SLIMBUS_0_TX = 16, + IDX_SLIMBUS_1_RX = 17, + IDX_SLIMBUS_1_TX = 18, + IDX_SLIMBUS_2_RX = 19, + IDX_SLIMBUS_2_TX = 20, + IDX_SLIMBUS_3_RX = 21, + IDX_SLIMBUS_3_TX = 22, + IDX_SLIMBUS_4_RX = 23, + IDX_SLIMBUS_4_TX = 24, + IDX_INT_BT_SCO_RX = 25, + IDX_INT_BT_SCO_TX = 26, + IDX_INT_BT_A2DP_RX = 27, + IDX_INT_FM_RX = 28, + IDX_INT_FM_TX = 29, + IDX_RT_PROXY_PORT_001_RX = 30, + IDX_RT_PROXY_PORT_001_TX = 31, + AFE_MAX_PORTS +}; + +int afe_open(u16 port_id, union afe_port_config *afe_config, int rate); +int afe_close(int port_id); +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port); +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain); +int afe_loopback_gain(u16 port_id, u16 volume); +int afe_validate_port(u16 port_id); +int afe_start_pseudo_port(u16 port_id); +int afe_stop_pseudo_port(u16 port_id); +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_map_nowait(int port_id, u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_unmap(u32 dma_addr_p); +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p); + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data); +int afe_unregister_get_events(u16 port_id); +int afe_rt_proxy_port_write(u32 buf_addr_p, u32 mem_map_handle, int bytes); +int afe_rt_proxy_port_read(u32 buf_addr_p, u32 mem_map_handle, int bytes); +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate); +int afe_port_stop_nowait(int port_id); +int afe_apply_gain(u16 port_id, u16 gain); +int afe_q6_interface_prepare(void); +int afe_get_port_type(u16 port_id); +/* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ +int afe_convert_virtual_to_portid(u16 port_id); + +int afe_pseudo_port_start_nowait(u16 port_id); +int afe_pseudo_port_stop_nowait(u16 port_id); +#endif /* __Q6AFE_V2_H__ */ diff --git a/include/sound/q6afe.h b/include/sound/q6afe.h new file mode 100644 index 000000000000..69a137f31834 --- /dev/null +++ b/include/sound/q6afe.h @@ -0,0 +1,111 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ +#include + +#define MSM_AFE_MONO 0 +#define MSM_AFE_MONO_RIGHT 1 +#define MSM_AFE_MONO_LEFT 2 +#define MSM_AFE_STEREO 3 +#define MSM_AFE_4CHANNELS 4 +#define MSM_AFE_6CHANNELS 6 +#define MSM_AFE_8CHANNELS 8 + +#define MSM_AFE_I2S_FORMAT_LPCM 0 +#define MSM_AFE_I2S_FORMAT_COMPR 1 +#define MSM_AFE_I2S_FORMAT_IEC60958_LPCM 2 +#define MSM_AFE_I2S_FORMAT_IEC60958_COMPR 3 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 + +#define RT_PROXY_DAI_001_RX 0xE0 +#define RT_PROXY_DAI_001_TX 0xF0 +#define RT_PROXY_DAI_002_RX 0xF1 +#define RT_PROXY_DAI_002_TX 0xE1 +#define VIRTUAL_ID_TO_PORTID(val) ((val & 0xF) | 0x2000) + +enum { + IDX_PRIMARY_I2S_RX = 0, + IDX_PRIMARY_I2S_TX = 1, + IDX_PCM_RX = 2, + IDX_PCM_TX = 3, + IDX_SECONDARY_I2S_RX = 4, + IDX_SECONDARY_I2S_TX = 5, + IDX_MI2S_RX = 6, + IDX_MI2S_TX = 7, + IDX_HDMI_RX = 8, + IDX_RSVD_2 = 9, + IDX_RSVD_3 = 10, + IDX_DIGI_MIC_TX = 11, + IDX_VOICE_RECORD_RX = 12, + IDX_VOICE_RECORD_TX = 13, + IDX_VOICE_PLAYBACK_TX = 14, + IDX_SLIMBUS_0_RX = 15, + IDX_SLIMBUS_0_TX = 16, + IDX_SLIMBUS_1_RX = 17, + IDX_SLIMBUS_1_TX = 18, + IDX_SLIMBUS_2_RX = 19, + IDX_SLIMBUS_2_TX = 20, + IDX_SLIMBUS_3_RX = 21, + IDX_SLIMBUS_3_TX = 22, + IDX_SLIMBUS_4_RX = 23, + IDX_SLIMBUS_4_TX = 24, + IDX_INT_BT_SCO_RX = 25, + IDX_INT_BT_SCO_TX = 26, + IDX_INT_BT_A2DP_RX = 27, + IDX_INT_FM_RX = 28, + IDX_INT_FM_TX = 29, + IDX_RT_PROXY_PORT_001_RX = 30, + IDX_RT_PROXY_PORT_001_TX = 31, + IDX_SECONDARY_PCM_RX = 32, + IDX_SECONDARY_PCM_TX = 33, + AFE_MAX_PORTS +}; + +int afe_open(u16 port_id, union afe_port_config *afe_config, int rate); +int afe_close(int port_id); +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port); +int afe_loopback_cfg(u16 enable, u16 dst_port, u16 src_port, u16 mode); +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain); +int afe_loopback_gain(u16 port_id, u16 volume); +int afe_validate_port(u16 port_id); +int afe_get_port_index(u16 port_id); +int afe_start_pseudo_port(u16 port_id); +int afe_stop_pseudo_port(u16 port_id); +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_map_nowait(u32 dma_addr_p, u32 dma_buf_sz); +int afe_cmd_memory_unmap(u32 dma_addr_p); +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p); + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data); +int afe_unregister_get_events(u16 port_id); +int afe_rt_proxy_port_write(u32 buf_addr_p, int bytes); +int afe_rt_proxy_port_read(u32 buf_addr_p, int bytes); +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate); +int afe_port_stop_nowait(int port_id); +int afe_apply_gain(u16 port_id, u16 gain); +int afe_q6_interface_prepare(void); +int afe_get_port_type(u16 port_id); +/* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ +int afe_convert_virtual_to_portid(u16 port_id); + +int afe_pseudo_port_start_nowait(u16 port_id); +int afe_pseudo_port_stop_nowait(u16 port_id); +#endif /* __Q6AFE_H__ */ diff --git a/include/sound/q6asm-v2.h b/include/sound/q6asm-v2.h new file mode 100644 index 000000000000..c76e6addfd0c --- /dev/null +++ b/include/sound/q6asm-v2.h @@ -0,0 +1,303 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ASM_V2_H__ +#define __Q6_ASM_V2_H__ + +#include +#include +#include +#include +#include + +#define IN 0x000 +#define OUT 0x001 +#define CH_MODE_MONO 0x001 +#define CH_MODE_STEREO 0x002 + +#define FORMAT_LINEAR_PCM 0x0000 +#define FORMAT_DTMF 0x0001 +#define FORMAT_ADPCM 0x0002 +#define FORMAT_YADPCM 0x0003 +#define FORMAT_MP3 0x0004 +#define FORMAT_MPEG4_AAC 0x0005 +#define FORMAT_AMRNB 0x0006 +#define FORMAT_AMRWB 0x0007 +#define FORMAT_V13K 0x0008 +#define FORMAT_EVRC 0x0009 +#define FORMAT_EVRCB 0x000a +#define FORMAT_EVRCWB 0x000b +#define FORMAT_MIDI 0x000c +#define FORMAT_SBC 0x000d +#define FORMAT_WMA_V10PRO 0x000e +#define FORMAT_WMA_V9 0x000f +#define FORMAT_AMR_WB_PLUS 0x0010 +#define FORMAT_MPEG4_MULTI_AAC 0x0011 +#define FORMAT_MULTI_CHANNEL_LINEAR_PCM 0x0012 + +#define ENCDEC_SBCBITRATE 0x0001 +#define ENCDEC_IMMEDIATE_DECODE 0x0002 +#define ENCDEC_CFG_BLK 0x0003 + +#define CMD_PAUSE 0x0001 +#define CMD_FLUSH 0x0002 +#define CMD_EOS 0x0003 +#define CMD_CLOSE 0x0004 +#define CMD_OUT_FLUSH 0x0005 + +/* bit 0:1 represents priority of stream */ +#define STREAM_PRIORITY_NORMAL 0x0000 +#define STREAM_PRIORITY_LOW 0x0001 +#define STREAM_PRIORITY_HIGH 0x0002 + +/* bit 4 represents META enable of encoded data buffer */ +#define BUFFER_META_ENABLE 0x0010 + +/* Enable Sample_Rate/Channel_Mode notification event from Decoder */ +#define SR_CM_NOTIFY_ENABLE 0x0004 + +#define ASYNC_IO_MODE 0x0002 +#define SYNC_IO_MODE 0x0001 +#define NO_TIMESTAMP 0xFF00 +#define SET_TIMESTAMP 0x0000 + +#define SOFT_PAUSE_ENABLE 1 +#define SOFT_PAUSE_DISABLE 0 + +#define SESSION_MAX 0x08 + +#define SOFT_PAUSE_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_PAUSE_STEP 2000 /* Step value 2ms or 2000us */ +enum { + SOFT_PAUSE_CURVE_LINEAR = 0, + SOFT_PAUSE_CURVE_EXP, + SOFT_PAUSE_CURVE_LOG, +}; + +#define SOFT_VOLUME_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_VOLUME_STEP 2000 /* Step value 2ms or 2000us */ +enum { + SOFT_VOLUME_CURVE_LINEAR = 0, + SOFT_VOLUME_CURVE_EXP, + SOFT_VOLUME_CURVE_LOG, +}; + +typedef void (*app_cb)(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t used; + uint32_t size;/* size of buffer */ + uint32_t actual_size; /* actual number of bytes read by DSP */ + struct ion_handle *handle; + struct ion_client *client; +}; + +struct audio_aio_write_param { + unsigned long paddr; + uint32_t len; + uint32_t uid; + uint32_t lsw_ts; + uint32_t msw_ts; + uint32_t flags; +}; + +struct audio_aio_read_param { + unsigned long paddr; + uint32_t len; + uint32_t uid; +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t max_buf_cnt; + uint32_t dsp_buf; + uint32_t cpu_buf; + struct list_head mem_map_handle; + uint32_t tmp_hdl; + /* read or write locks */ + struct mutex lock; + spinlock_t dsp_lock; +}; + +struct audio_client { + int session; + app_cb cb; + atomic_t cmd_state; + /* Relative or absolute TS */ + uint32_t time_flag; + void *priv; + uint32_t io_mode; + uint64_t time_stamp; + struct apr_svc *apr; + struct apr_svc *mmap_apr; + struct mutex cmd_lock; + /* idx:1 out port, 0: in port*/ + struct audio_port_data port[2]; + wait_queue_head_t cmd_wait; +}; + +void q6asm_audio_client_free(struct audio_client *ac); + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv); + +struct audio_client *q6asm_get_audio_client(int session_id); + +int q6asm_audio_client_buf_alloc(unsigned int dir/* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir + /* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac); + +int q6asm_open_read(struct audio_client *ac, uint32_t format + /*, uint16_t bits_per_sample*/); + +int q6asm_open_write(struct audio_client *ac, uint32_t format + /*, uint16_t bits_per_sample*/); + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format); + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param); + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param); + +int q6asm_read(struct audio_client *ac); +int q6asm_read_nolock(struct audio_client *ac); + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, + int dir, uint32_t bufsz, uint32_t bufcnt); + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, + int dir); + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable); + +int q6asm_cmd(struct audio_client *ac, int cmd); + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd); + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *idx); + +void *q6asm_is_cpu_buf_avail_nolock(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *idx); + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac); + +/* File format specific configurations to be added below */ + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, + uint32_t mode, uint32_t format); + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels); + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps); + +int q6asm_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right); + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable); + +int q6asm_enc_cfg_blk_amrwb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable); + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg); + +int q6asm_media_format_block_multi_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg); + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg); + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg); + +/* PP specific */ +int q6asm_equalizer(struct audio_client *ac, void *eq); + +/* Send Volume Command */ +int q6asm_set_volume(struct audio_client *ac, int volume); + +/* Set SoftPause Params */ +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *param); + +/* Set Softvolume Params */ +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *param); + +/* Send left-right channel gain */ +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain); + +/* Enable Mute/unmute flag */ +int q6asm_set_mute(struct audio_client *ac, int muteflag); + +uint64_t q6asm_get_session_time(struct audio_client *ac); + +/* Client can set the IO mode to either AIO/SIO mode */ +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode); + +/* Get Service ID for APR communication */ +int q6asm_get_apr_service_id(int session_id); + +/* Common format block without any payload +*/ +int q6asm_media_format_block(struct audio_client *ac, uint32_t format); + +#endif /* __Q6_ASM_H__ */ diff --git a/include/sound/q6asm.h b/include/sound/q6asm.h new file mode 100644 index 000000000000..79bed8e3773a --- /dev/null +++ b/include/sound/q6asm.h @@ -0,0 +1,320 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ + +#include +#include +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION +#include +#endif + +#define IN 0x000 +#define OUT 0x001 +#define CH_MODE_MONO 0x001 +#define CH_MODE_STEREO 0x002 + +#define FORMAT_LINEAR_PCM 0x0000 +#define FORMAT_DTMF 0x0001 +#define FORMAT_ADPCM 0x0002 +#define FORMAT_YADPCM 0x0003 +#define FORMAT_MP3 0x0004 +#define FORMAT_MPEG4_AAC 0x0005 +#define FORMAT_AMRNB 0x0006 +#define FORMAT_AMRWB 0x0007 +#define FORMAT_V13K 0x0008 +#define FORMAT_EVRC 0x0009 +#define FORMAT_EVRCB 0x000a +#define FORMAT_EVRCWB 0x000b +#define FORMAT_MIDI 0x000c +#define FORMAT_SBC 0x000d +#define FORMAT_WMA_V10PRO 0x000e +#define FORMAT_WMA_V9 0x000f +#define FORMAT_AMR_WB_PLUS 0x0010 +#define FORMAT_MPEG4_MULTI_AAC 0x0011 +#define FORMAT_MULTI_CHANNEL_LINEAR_PCM 0x0012 +#define FORMAT_AC3 0x0013 +#define FORMAT_DTS 0x0014 +#define FORMAT_EAC3 0x0015 +#define FORMAT_ATRAC 0x0016 +#define FORMAT_MAT 0x0017 +#define FORMAT_AAC 0x0018 + +#define ENCDEC_SBCBITRATE 0x0001 +#define ENCDEC_IMMEDIATE_DECODE 0x0002 +#define ENCDEC_CFG_BLK 0x0003 + +#define CMD_PAUSE 0x0001 +#define CMD_FLUSH 0x0002 +#define CMD_EOS 0x0003 +#define CMD_CLOSE 0x0004 +#define CMD_OUT_FLUSH 0x0005 + +/* bit 0:1 represents priority of stream */ +#define STREAM_PRIORITY_NORMAL 0x0000 +#define STREAM_PRIORITY_LOW 0x0001 +#define STREAM_PRIORITY_HIGH 0x0002 + +/* bit 4 represents META enable of encoded data buffer */ +#define BUFFER_META_ENABLE 0x0010 + +/* Enable Sample_Rate/Channel_Mode notification event from Decoder */ +#define SR_CM_NOTIFY_ENABLE 0x0004 + +#define ASYNC_IO_MODE 0x0002 +#define SYNC_IO_MODE 0x0001 +#define NO_TIMESTAMP 0xFF00 +#define SET_TIMESTAMP 0x0000 + +#define SOFT_PAUSE_ENABLE 1 +#define SOFT_PAUSE_DISABLE 0 + +#define SESSION_MAX 0x08 + +#define SOFT_PAUSE_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_PAUSE_STEP_LINEAR 0 /* Step value 0ms or 0us */ +#define SOFT_PAUSE_STEP 2000 /* Step value 2000ms or 2000us */ +enum { + SOFT_PAUSE_CURVE_LINEAR = 0, + SOFT_PAUSE_CURVE_EXP, + SOFT_PAUSE_CURVE_LOG, +}; + +#define SOFT_VOLUME_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_VOLUME_STEP_LINEAR 0 /* Step value 0ms or 0us */ +#define SOFT_VOLUME_STEP 2000 /* Step value 2000ms or 2000us */ +enum { + SOFT_VOLUME_CURVE_LINEAR = 0, + SOFT_VOLUME_CURVE_EXP, + SOFT_VOLUME_CURVE_LOG, +}; + +typedef void (*app_cb)(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t used; + uint32_t size;/* size of buffer */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + struct ion_handle *handle; + struct ion_client *client; +#else + void *mem_buffer; +#endif +}; + +struct audio_aio_write_param { + unsigned long paddr; + uint32_t uid; + uint32_t len; + uint32_t msw_ts; + uint32_t lsw_ts; + uint32_t flags; +}; + +struct audio_aio_read_param { + unsigned long paddr; + uint32_t len; + uint32_t uid; +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t max_buf_cnt; + uint32_t dsp_buf; + uint32_t cpu_buf; + /* read or write locks */ + struct mutex lock; + spinlock_t dsp_lock; +}; + +struct audio_client { + int session; + /* idx:1 out port, 0: in port*/ + struct audio_port_data port[2]; + + struct apr_svc *apr; + struct mutex cmd_lock; + + atomic_t cmd_state; + atomic_t time_flag; + wait_queue_head_t cmd_wait; + wait_queue_head_t time_wait; + + app_cb cb; + void *priv; + uint32_t io_mode; + uint64_t time_stamp; +}; + +void q6asm_audio_client_free(struct audio_client *ac); + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv); + +struct audio_client *q6asm_get_audio_client(int session_id); + +int q6asm_audio_client_buf_alloc(unsigned int dir/* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir + /* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac); + +int q6asm_open_read(struct audio_client *ac, uint32_t format); + +int q6asm_open_write(struct audio_client *ac, uint32_t format); + +int q6asm_open_write_compressed(struct audio_client *ac, uint32_t format); + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format); + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param); + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param); + +int q6asm_read(struct audio_client *ac); +int q6asm_read_nolock(struct audio_client *ac); + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, + int dir, uint32_t bufsz, uint32_t bufcnt); + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, + int dir); + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable); + +int q6asm_cmd(struct audio_client *ac, int cmd); + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd); + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *idx); + +void *q6asm_is_cpu_buf_avail_nolock(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *idx); + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac); + +/* File format specific configurations to be added below */ + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, + uint32_t mode, uint32_t format); + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_enc_cfg_blk_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps); + +int q6asm_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right); + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels); + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable); + +int q6asm_enc_cfg_blk_amrwb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable); + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg); + +int q6asm_media_format_block_multi_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg); + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg); + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg); + +/* PP specific */ +int q6asm_equalizer(struct audio_client *ac, void *eq); + +/* Send Volume Command */ +int q6asm_set_volume(struct audio_client *ac, int volume); + +/* Set SoftPause Params */ +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *param); + +/* Set Softvolume Params */ +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *param); + +/* Send left-right channel gain */ +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain); + +/* Enable Mute/unmute flag */ +int q6asm_set_mute(struct audio_client *ac, int muteflag); + +uint64_t q6asm_get_session_time(struct audio_client *ac); + +/* Client can set the IO mode to either AIO/SIO mode */ +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode); + +#ifdef CONFIG_RTAC +/* Get Service ID for APR communication */ +int q6asm_get_apr_service_id(int session_id); +#endif + +/* Common format block without any payload +*/ +int q6asm_media_format_block(struct audio_client *ac, uint32_t format); + +#endif /* __Q6_ASM_H__ */ diff --git a/include/sound/q6audio-v2.h b/include/sound/q6audio-v2.h new file mode 100644 index 000000000000..38c3ab28a73a --- /dev/null +++ b/include/sound/q6audio-v2.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _Q6_AUDIO_H_ +#define _Q6_AUDIO_H_ + +#include + +int q6audio_get_port_index(u16 port_id); + +int q6audio_convert_virtual_to_portid(u16 port_id); + +int q6audio_validate_port(u16 port_id); + +int q6audio_get_port_id(u16 port_id); + +#endif diff --git a/include/uapi/sound/Kbuild b/include/uapi/sound/Kbuild index 0f7d279ebde3..6c2b11e14d31 100644 --- a/include/uapi/sound/Kbuild +++ b/include/uapi/sound/Kbuild @@ -9,3 +9,4 @@ header-y += hdsp.h header-y += hdspm.h header-y += sb16_csp.h header-y += sfnt_info.h +header-y += tlv.h diff --git a/sound/core/Kconfig b/sound/core/Kconfig index b413ed05e74d..87dcaeef7742 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -149,6 +149,7 @@ config SND_SEQ_RTCTIMER_DEFAULT config SND_DYNAMIC_MINORS bool "Dynamic device file minor numbers" + default y if SND_OMAP_SOC_ABE_DSP help If you say Y here, the minor numbers of ALSA device files in /dev/snd/ are allocated dynamically. This allows you to have diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 9e675c76436c..adb1b52eac14 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -46,6 +46,7 @@ source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" source "sound/soc/mid-x86/Kconfig" +source "sound/soc/msm/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 197b6ae54c8d..fca9f589ab26 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += mid-x86/ +obj-$(CONFIG_SND_SOC) += msm/ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2f45f00e31b0..be5d400bffc9 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,7 +49,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C - select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C @@ -79,7 +78,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM0010 if SPI_MASTER select SND_SOC_WM1250_EV1 if I2C select SND_SOC_WM2000 if I2C - select SND_SOC_WM2200 if I2C select SND_SOC_WM5100 if I2C select SND_SOC_WM5102 if MFD_WM5102 select SND_SOC_WM5110 if MFD_WM5110 @@ -125,6 +123,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS + select SND_SOC_TIMPANI if MARIMBA_CORE help Normally ASoC codec drivers are only built if a machine driver which uses them is also built since they are only usable with a machine @@ -357,6 +356,15 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate +config SND_SOC_WCD9304 + tristate + +config SND_SOC_WCD9310 + tristate + +config SND_SOC_CS8427 + tristate + config SND_SOC_WL1273 tristate @@ -511,9 +519,6 @@ config SND_SOC_WM9713 config SND_SOC_LM4857 tristate -config SND_SOC_MAX9768 - tristate - config SND_SOC_MAX9877 tristate @@ -525,3 +530,6 @@ config SND_SOC_ML26124 config SND_SOC_TPA6130A2 tristate + +config SND_SOC_MSM_STUB + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index b9e41c9a1f4c..5c36c537b2ac 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -66,6 +66,9 @@ snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o +snd-soc-wcd9304-objs := wcd9304.o wcd9304-tables.o +snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o +snd-soc-cs8427-objs := cs8427.o snd-soc-wl1273-objs := wl1273.o snd-soc-wm-adsp-objs := wm_adsp.o snd-soc-wm0010-objs := wm0010.o @@ -119,6 +122,8 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-timpani-objs := timpani.o +snd-soc-msm-stub-objs := msm_stub.o # Amp snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o @@ -190,6 +195,9 @@ obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WCD9304) += snd-soc-wcd9304.o +obj-$(CONFIG_SND_SOC_WCD9310) += snd-soc-wcd9310.o +obj-$(CONFIG_SND_SOC_CS8427) += snd-soc-cs8427.o obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o @@ -242,6 +250,7 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_MSM_STUB) += snd-soc-msm-stub.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/cs8427.c b/sound/soc/codecs/cs8427.c new file mode 100644 index 000000000000..19d5f0a38e12 --- /dev/null +++ b/sound/soc/codecs/cs8427.c @@ -0,0 +1,904 @@ +/* + * Routines for control of the CS8427 via i2c bus + * IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic + * Copyright (c) by Jaroslav Kysela + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CS8427_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000) + +#define CS8427_FORMATS (SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FORMAT_S16_LE |\ + SNDRV_PCM_FORMAT_S20_3LE) + +struct cs8427_stream { + struct snd_pcm_substream *substream; + char hw_status[CHANNEL_STATUS_SIZE]; /* hardware status */ + char def_status[CHANNEL_STATUS_SIZE]; /* default status */ + char pcm_status[CHANNEL_STATUS_SIZE]; /* PCM private status */ + char hw_udata[32]; + struct snd_kcontrol *pcm_ctl; +}; + +struct cs8427 { + struct i2c_client *client; + struct i2c_msg xfer_msg[2]; + unsigned char regmap[0x14]; /* map of first 1 + 13 registers */ + unsigned int reset_timeout; + struct cs8427_stream playback; +}; + +static int cs8427_i2c_write_device(struct cs8427 *cs8427_i2c, + u16 reg, u8 *value, u32 bytes) +{ + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + u8 data[bytes + 1]; + + if (cs8427_i2c->client == NULL) { + pr_err("%s: failed to get device info\n", __func__); + return -ENODEV; + } + reg_addr = (u8)reg; + msg = &cs8427_i2c->xfer_msg[0]; + msg->addr = cs8427_i2c->client->addr; + msg->len = bytes + 1; + msg->flags = 0; + data[0] = reg_addr; + data[1] = *value; + msg->buf = data; + ret = i2c_transfer(cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 1); + /* Try again if the write fails + * checking with ebusy and number of bytes executed + * for write ret value should be 1 + */ + if ((ret != 1) || (ret == -EBUSY)) { + ret = i2c_transfer( + cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 1); + if ((ret != 1) || (ret < 0)) { + dev_err(&cs8427_i2c->client->dev, + "failed to write the" + " device reg %d\n", reg); + return ret; + } + } + return 0; +} + +static int cs8427_i2c_write(struct cs8427 *chip, unsigned short reg, + int bytes, void *src) +{ + return cs8427_i2c_write_device(chip, reg, src, bytes); +} +static int cs8427_i2c_read_device(struct cs8427 *cs8427_i2c, + unsigned short reg, + int bytes, unsigned char *dest) +{ + struct i2c_msg *msg; + int ret = 0; + u8 reg_addr = 0; + u8 i = 0; + + if (cs8427_i2c->client == NULL) { + pr_err("%s: failed to get device info\n", __func__); + return -ENODEV; + } + for (i = 0; i < bytes; i++) { + reg_addr = (u8)reg++; + msg = &cs8427_i2c->xfer_msg[0]; + msg->addr = cs8427_i2c->client->addr; + msg->len = 1; + msg->flags = 0; + msg->buf = ®_addr; + + msg = &cs8427_i2c->xfer_msg[1]; + msg->addr = cs8427_i2c->client->addr; + msg->len = 1; + msg->flags = I2C_M_RD; + msg->buf = dest++; + ret = i2c_transfer(cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 2); + + /* Try again if read fails first time + checking with ebusy and number of bytes executed + for read ret value should be 2*/ + if ((ret != 2) || (ret == -EBUSY)) { + ret = i2c_transfer( + cs8427_i2c->client->adapter, + cs8427_i2c->xfer_msg, 2); + if ((ret != 2) || (ret < 0)) { + dev_err(&cs8427_i2c->client->dev, + "failed to read cs8427" + " register %d\n", reg); + return ret; + } + } + } + return 0; +} + +static int cs8427_i2c_read(struct cs8427 *chip, + unsigned short reg, + int bytes, void *dest) +{ + return cs8427_i2c_read_device(chip, reg, + bytes, dest); +} + +static int cs8427_i2c_sendbytes(struct cs8427 *chip, + char *reg_addr, char *data, + int bytes) +{ + u32 ret = 0; + u8 i = 0; + + if (!chip) { + pr_err("%s, invalid device info\n", __func__); + return -ENODEV; + } + if (!data) { + dev_err(&chip->client->dev, "%s:" + "invalid data pointer\n", __func__); + return -EINVAL; + } + for (i = 0; i < bytes; i++) { + ret = cs8427_i2c_write_device(chip, (*reg_addr + i), + &data[i], 1); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to send the data to" + " cs8427 chip\n", __func__); + break; + } + } + return i; +} + +/* + * Reset the chip using run bit, also lock PLL using ILRCK and + * put back AES3INPUT. This workaround is described in latest + * CS8427 datasheet, otherwise TXDSERIAL will not work. + */ +static void snd_cs8427_reset(struct cs8427 *chip) +{ + unsigned long end_time; + int data, aes3input = 0; + unsigned char val = 0; + + if (snd_BUG_ON(!chip)) + return; + if ((chip->regmap[CS8427_REG_CLOCKSOURCE] & CS8427_RXDAES3INPUT) == + CS8427_RXDAES3INPUT) /* AES3 bit is set */ + aes3input = 1; + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~(CS8427_RUN | CS8427_RXDMASK); + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RUN | CS8427_RXDILRCK; + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + end_time = jiffies + chip->reset_timeout; + while (time_after_eq(end_time, jiffies)) { + data = cs8427_i2c_read(chip, CS8427_REG_RECVERRORS, + 1, &val); + if (!(val & CS8427_UNLOCK)) + break; + schedule_timeout_uninterruptible(1); + } + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~CS8427_RXDMASK; + if (aes3input) + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RXDAES3INPUT; + cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, + 1, &chip->regmap[CS8427_REG_CLOCKSOURCE]); +} + +static int snd_cs8427_in_status_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int snd_cs8427_in_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char val = 0; + int err = 0; + + err = cs8427_i2c_read(chip, kcontrol->private_value, 1, &val); + if (err < 0) + return err; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_cs8427_qsubcode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 10; + return 0; +} + +static int snd_cs8427_qsubcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char reg = CS8427_REG_QSUBCODE; + int err; + unsigned char val[20]; + + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + + err = cs8427_i2c_write(chip, reg, 1, &val[0]); + if (err != 1) { + dev_err(&chip->client->dev, "unable to send register" + " 0x%x byte to CS8427\n", reg); + return err < 0 ? err : -EIO; + } + err = cs8427_i2c_read(chip, *ucontrol->value.bytes.data, 10, &val); + if (err != 10) { + dev_err(&chip->client->dev, "unable to read" + " Q-subcode bytes from CS8427\n"); + return err < 0 ? err : -EIO; + } + return 0; +} + +static int snd_cs8427_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cs8427_select_corudata(struct cs8427 *cs8427_i2c, int udata) +{ + struct cs8427 *chip = cs8427_i2c; + int err; + + udata = udata ? CS8427_BSEL : 0; + if (udata != (chip->regmap[CS8427_REG_CSDATABUF] & udata)) { + chip->regmap[CS8427_REG_CSDATABUF] &= ~CS8427_BSEL; + chip->regmap[CS8427_REG_CSDATABUF] |= udata; + err = cs8427_i2c_write(cs8427_i2c, CS8427_REG_CSDATABUF, + 1, &chip->regmap[CS8427_REG_CSDATABUF]); + if (err < 0) + return err; + } + return 0; +} + +static int snd_cs8427_send_corudata(struct cs8427 *obj, + int udata, + unsigned char *ndata, + int count) +{ + struct cs8427 *chip = obj; + char *hw_data = udata ? + chip->playback.hw_udata : chip->playback.hw_status; + char data[32]; + int err, idx; + unsigned char addr = 0; + int ret = 0; + + if (!memcmp(hw_data, ndata, count)) + return 0; + err = snd_cs8427_select_corudata(chip, udata); + if (err < 0) + return err; + memcpy(hw_data, ndata, count); + if (udata) { + memset(data, 0, sizeof(data)); + if (memcmp(hw_data, data, count) == 0) { + chip->regmap[CS8427_REG_UDATABUF] &= ~CS8427_UBMMASK; + chip->regmap[CS8427_REG_UDATABUF] |= CS8427_UBMZEROS | + CS8427_EFTUI; + err = cs8427_i2c_write(chip, CS8427_REG_UDATABUF, + 1, &chip->regmap[CS8427_REG_UDATABUF]); + return err < 0 ? err : 0; + } + } + idx = 0; + memcpy(data, ndata, CHANNEL_STATUS_SIZE); + /* address from where the bufferhas to write*/ + addr = 0x20; + ret = cs8427_i2c_sendbytes(chip, &addr, data, count); + if (ret != count) + return -EIO; + return 1; +} + +static int snd_cs8427_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + + memcpy(ucontrol->value.iec958.status, + chip->playback.def_status, CHANNEL_STATUS_SIZE); + return 0; +} + +static int snd_cs8427_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs8427 *chip = kcontrol->private_data; + unsigned char *status; + int err, change; + + if (!chip) { + pr_err("%s: invalid device info\n", __func__); + return -ENODEV; + } + status = kcontrol->private_value ? + chip->playback.pcm_status : chip->playback.def_status; + + change = memcmp(ucontrol->value.iec958.status, status, + CHANNEL_STATUS_SIZE) != 0; + + if (!change) { + memcpy(status, ucontrol->value.iec958.status, + CHANNEL_STATUS_SIZE); + err = snd_cs8427_send_corudata(chip, 0, status, + CHANNEL_STATUS_SIZE); + if (err < 0) + change = err; + } + return change; +} + +static int snd_cs8427_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_cs8427_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, CHANNEL_STATUS_SIZE); + return 0; +} + +static struct snd_kcontrol_new snd_cs8427_iec958_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Input Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 15, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Error Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 16, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = snd_cs8427_spdif_mask_info, + .get = snd_cs8427_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, + DEFAULT), + .info = snd_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 0 + }, + { + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE), + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), + .info = snd_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 1 + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_qsubcode_info, + .name = "IEC958 Q-subcode Capture Default", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_qsubcode_get + } +}; + +static int cs8427_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs8427 *chip = dev_get_drvdata(codec->dev); + int ret = 0; + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + chip->regmap[CS8427_REG_SERIALINPUT] &= CS8427_BITWIDTH_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES16; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES20; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + + break; + case SNDRV_PCM_FORMAT_S24_LE: + chip->regmap[CS8427_REG_SERIALINPUT] |= CS8427_SIRES24; + ret = cs8427_i2c_write(chip, CS8427_REG_SERIALINPUT, 1, + &chip->regmap[CS8427_REG_SERIALINPUT]); + break; + default: + pr_err("invalid format\n"); + break; + } + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + return ret; +} + +static int snd_cs8427_iec958_register_kcontrol(struct cs8427 *cs8427, + struct snd_card *card) +{ + struct cs8427 *chip = cs8427; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + for (idx = 0; idx < ARRAY_SIZE(snd_cs8427_iec958_controls); idx++) { + kctl = snd_ctl_new1(&snd_cs8427_iec958_controls[idx], chip); + if (kctl == NULL) + return -ENOMEM; + err = snd_ctl_add(card, kctl); + if (err < 0) { + dev_err(&chip->client->dev, + "failed to add the kcontrol\n"); + return err; + } + } + return err; +} + +static int cs8427_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + /* + * we need to make the pll lock for the I2S tranfers + * reset the cs8427 chip for this. + */ + snd_cs8427_reset(chip); + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + + return 0; +} + +static void cs8427_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return; + } + dev_dbg(&chip->client->dev, + "%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); +} + +static int cs8427_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs8427 *chip = dev_get_drvdata(dai->codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "%s\n", __func__); + return 0; +} + +static struct snd_soc_dai_ops cs8427_dai_ops = { + .startup = cs8427_startup, + .shutdown = cs8427_shutdown, + .hw_params = cs8427_hw_params, + .set_fmt = cs8427_set_dai_fmt, +}; + +static struct snd_soc_dai_driver cs8427_dai[] = { + { + .name = "spdif_rx", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .rates = CS8427_RATES, + .formats = CS8427_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &cs8427_dai_ops, + }, +}; + + +static unsigned int cs8427_soc_i2c_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct cs8427 *chip = dev_get_drvdata(codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "cs8427 soc i2c read\n"); + return 0; +} + +static int cs8427_soc_i2c_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct cs8427 *chip = dev_get_drvdata(codec->dev); + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, "cs8427 soc i2c write\n"); + return 0; +} + +static int cs8427_soc_probe(struct snd_soc_codec *codec) +{ + int ret = 0; + struct cs8427 *chip; + codec->control_data = dev_get_drvdata(codec->dev); + chip = codec->control_data; + + if (chip == NULL) { + pr_err("invalid device private data\n"); + return -ENODEV; + } + snd_cs8427_iec958_register_kcontrol(chip, codec->card->snd_card); + dev_set_drvdata(codec->dev, chip); + return ret; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs8427 = { + .read = cs8427_soc_i2c_read, + .write = cs8427_soc_i2c_write, + .probe = cs8427_soc_probe, +}; + +int poweron_cs8427(struct cs8427 *chip) +{ + struct cs8427_platform_data *pdata = chip->client->dev.platform_data; + int ret = 0; + + /*enable the 100KHz level shifter*/ + if (pdata->enable) { + ret = pdata->enable(1); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to enable the level shifter\n"); + return ret; + } + } + + ret = gpio_request(pdata->reset_gpio, "cs8427 reset"); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to request the gpio %d\n", + pdata->reset_gpio); + return ret; + } + /*bring the chip out of reset*/ + gpio_direction_output(pdata->reset_gpio, 1); + msleep(20); + gpio_direction_output(pdata->reset_gpio, 0); + msleep(20); + gpio_direction_output(pdata->reset_gpio, 1); + msleep(20); + return ret; +} + +static int cs8427_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static unsigned char initvals1[] = { + CS8427_REG_CONTROL1 | CS8427_REG_AUTOINC, + /* CS8427_REG_CONTROL1: RMCK to OMCK, valid PCM audio, disable mutes, + * TCBL=output + */ + CS8427_SWCLK | CS8427_TCBLDIR, + /* CS8427_REG_CONTROL2: hold last valid audio sample, RMCK=256*Fs, + * normal stereo operation + */ + 0x08, + /* CS8427_REG_DATAFLOW: + * AES3 Transmitter data source => Serial Audio input port + * Serial audio output port data source => reserved + */ + CS8427_TXDSERIAL, + /* CS8427_REG_CLOCKSOURCE: Run off, CMCK=256*Fs, + * output time base = OMCK, input time base = recovered input clock, + * recovered input clock source is ILRCK changed to AES3INPUT + * (workaround, see snd_cs8427_reset) + */ + CS8427_RXDILRCK | CS8427_OUTC, + /* CS8427_REG_SERIALINPUT: Serial audio input port data format = I2S, + * 24-bit, 64*Fsi + */ + CS8427_SIDEL | CS8427_SILRPOL | CS8427_SORES16, + /* CS8427_REG_SERIALOUTPUT: Serial audio output port data format + * = I2S, 24-bit, 64*Fsi + */ + CS8427_SODEL | CS8427_SOLRPOL | CS8427_SIRES16, + }; + static unsigned char initvals2[] = { + CS8427_REG_RECVERRMASK | CS8427_REG_AUTOINC, + /* CS8427_REG_RECVERRMASK: unmask the input PLL clock, V, confidence, + * biphase, parity status bits + * CS8427_UNLOCK | CS8427_V | CS8427_CONF | CS8427_BIP | CS8427_PAR, + */ + 0xff, /* set everything */ + /* CS8427_REG_CSDATABUF: + * Registers 32-55 window to CS buffer + * Inhibit D->E transfers from overwriting first 5 bytes of CS data. + * Inhibit D->E transfers (all) of CS data. + * Allow E->F transfer of CS data. + * One byte mode; both A/B channels get same written CB data. + * A channel info is output to chip's EMPH* pin. + */ + CS8427_CBMR | CS8427_DETCI, + /* CS8427_REG_UDATABUF: + * Use internal buffer to transmit User (U) data. + * Chip's U pin is an output. + * Transmit all O's for user data. + * Inhibit D->E transfers. + * Inhibit E->F transfers. + */ + CS8427_UD | CS8427_EFTUI | CS8427_DETUI, + }; + int err; + unsigned char buf[CHANNEL_STATUS_SIZE]; + unsigned char val = 0; + char addr = 0; + unsigned int reset_timeout = 100; + int ret = 0; + struct cs8427 *chip; + + if (!client) { + pr_err("%s: invalid device info\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct cs8427), GFP_KERNEL); + if (chip == NULL) { + dev_err(&client->dev, + "%s: error, allocation failed\n", __func__); + return -ENOMEM; + } + + chip->client = client; + + dev_set_drvdata(&chip->client->dev, chip); + + ret = poweron_cs8427(chip); + + if (ret) { + dev_err(&chip->client->dev, + "failed to bring chip out of reset\n"); + return -ENODEV; + } + + err = cs8427_i2c_read(chip, CS8427_REG_ID_AND_VER, 1, &val); + if (err < 0) { + /* give second chance */ + dev_err(&chip->client->dev, + "failed to read cs8427 trying once again\n"); + err = cs8427_i2c_read(chip, CS8427_REG_ID_AND_VER, + 1, &val); + if (err < 0) { + dev_err(&chip->client->dev, + "failed to read version number\n"); + return -ENODEV; + } + dev_dbg(&chip->client->dev, + "version number read = %x\n", val); + } + if (val != CS8427_VER8427A) { + dev_err(&chip->client->dev, + "unable to find CS8427 signature " + "(expected 0x%x, read 0x%x),\n", + CS8427_VER8427A, val); + dev_err(&chip->client->dev, + " initialization is not completed\n"); + return -EFAULT; + } + val = 0; + /* turn off run bit while making changes to configuration */ + err = cs8427_i2c_write(chip, CS8427_REG_CLOCKSOURCE, 1, &val); + if (err < 0) + goto __fail; + /* send initial values */ + memcpy(chip->regmap + (initvals1[0] & 0x7f), initvals1 + 1, 6); + addr = 1; + err = cs8427_i2c_sendbytes(chip, &addr, &initvals1[1], 6); + if (err != 6) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* Turn off CS8427 interrupt stuff that is not used in hardware */ + memset(buf, 0, 7); + /* from address 9 to 15 */ + addr = 9; + err = cs8427_i2c_sendbytes(chip, &addr, buf, 7); + if (err != 7) + goto __fail; + /* send transfer initialization sequence */ + addr = 0x11; + memcpy(chip->regmap + (initvals2[0] & 0x7f), initvals2 + 1, 3); + err = cs8427_i2c_sendbytes(chip, &addr, &initvals2[1], 3); + if (err != 3) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* write default channel status bytes */ + put_unaligned_le32(SNDRV_PCM_DEFAULT_CON_SPDIF, buf); + memset(buf + 4, 0, CHANNEL_STATUS_SIZE - 4); + if (snd_cs8427_send_corudata(chip, 0, buf, CHANNEL_STATUS_SIZE) < 0) + goto __fail; + memcpy(chip->playback.def_status, buf, CHANNEL_STATUS_SIZE); + memcpy(chip->playback.pcm_status, buf, CHANNEL_STATUS_SIZE); + + /* turn on run bit and rock'n'roll */ + if (reset_timeout < 1) + reset_timeout = 1; + chip->reset_timeout = reset_timeout; + snd_cs8427_reset(chip); + + ret = snd_soc_register_codec(&chip->client->dev, &soc_codec_dev_cs8427, + cs8427_dai, ARRAY_SIZE(cs8427_dai)); + + return 0; + +__fail: + kfree(chip); + return err < 0 ? err : -EIO; +} + +static int cs8427_remove(struct i2c_client *client) +{ + struct cs8427 *chip; + struct cs8427_platform_data *pdata; + chip = dev_get_drvdata(&client->dev); + if (!chip) { + pr_err("invalid device info\n"); + return -ENODEV; + } + pdata = chip->client->dev.platform_data; + gpio_free(pdata->reset_gpio); + if (pdata->enable) + pdata->enable(0); + kfree(chip); + return 0; +} + +static struct i2c_device_id cs8427_id_table[] = { + {"cs8427", CS8427_ADDR0}, + {"cs8427", CS8427_ADDR2}, + {"cs8427", CS8427_ADDR3}, + {"cs8427", CS8427_ADDR4}, + {"cs8427", CS8427_ADDR5}, + {"cs8427", CS8427_ADDR6}, + {"cs8427", CS8427_ADDR7}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs8427_id_table); + +static struct i2c_driver cs8427_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "cs8427-spdif", + }, + .id_table = cs8427_id_table, + .probe = cs8427_i2c_probe, + .remove = cs8427_remove, +}; + +static int __init cs8427_module_init(void) +{ + int ret = 0; + ret = i2c_add_driver(&cs8427_i2c_driver); + if (ret != 0) + pr_err("failed to add the I2C driver\n"); + return ret; +} + +static void __exit cs8427_module_exit(void) +{ + pr_info("module exit\n"); +} + +module_init(cs8427_module_init) +module_exit(cs8427_module_exit) + +MODULE_DESCRIPTION("CS8427 interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/msm_stub.c b/sound/soc/codecs/msm_stub.c new file mode 100644 index 000000000000..496e0ee2487a --- /dev/null +++ b/sound/soc/codecs/msm_stub.c @@ -0,0 +1,80 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include + +/* A dummy driver useful only to advertise hardware parameters */ +static struct snd_soc_dai_driver msm_stub_dais[] = { + { + .name = "msm-stub-rx", + .playback = { /* Support maximum range */ + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "msm-stub-tx", + .capture = { /* Support maximum range */ + .stream_name = "Record", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static struct snd_soc_codec_driver soc_msm_stub = {}; + +static int msm_stub_dev_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &soc_msm_stub, msm_stub_dais, ARRAY_SIZE(msm_stub_dais)); +} + +static int msm_stub_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver msm_stub_driver = { + .driver = { + .name = "msm-stub-codec", + .owner = THIS_MODULE, + }, + .probe = msm_stub_dev_probe, + .remove = msm_stub_dev_remove, +}; + +static int __init msm_stub_init(void) +{ + return platform_driver_register(&msm_stub_driver); +} +module_init(msm_stub_init); + +static void __exit msm_stub_exit(void) +{ + platform_driver_unregister(&msm_stub_driver); +} +module_exit(msm_stub_exit); + +MODULE_DESCRIPTION("Generic MSM CODEC driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/timpani.c b/sound/soc/codecs/timpani.c new file mode 100644 index 000000000000..f80c0fcf4cb1 --- /dev/null +++ b/sound/soc/codecs/timpani.c @@ -0,0 +1,482 @@ +/* Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* Debug purpose */ +#include +#include +#include +/* End of debug purpose */ + +#define ADIE_CODEC_MAX 2 + +struct adie_codec_register { + u8 reg; + u8 mask; + u8 val; +}; + +static struct adie_codec_register dmic_on[] = { + {0x80, 0x05, 0x05}, + {0x80, 0x05, 0x00}, + {0x83, 0x0C, 0x00}, + {0x8A, 0xF0, 0x30}, + {0x86, 0xFF, 0xAC}, + {0x87, 0xFF, 0xAC}, + {0x8A, 0xF0, 0xF0}, + {0x82, 0x1F, 0x1E}, + {0x83, 0x0C, 0x0C}, + {0x92, 0x3F, 0x21}, + {0x94, 0x3F, 0x24}, + {0xA3, 0x39, 0x01}, + {0xA8, 0x0F, 0x00}, + {0xAB, 0x3F, 0x00}, + {0x86, 0xFF, 0x00}, + {0x87, 0xFF, 0x00}, + {0x8A, 0xF0, 0xC0}, +}; + +static struct adie_codec_register dmic_off[] = { + {0x8A, 0xF0, 0xF0}, + {0x83, 0x0C, 0x00}, + {0x92, 0xFF, 0x00}, + {0x94, 0xFF, 0x1B}, +}; + +static struct adie_codec_register spk_on[] = { + {0x80, 0x02, 0x02}, + {0x80, 0x02, 0x00}, + {0x83, 0x03, 0x00}, + {0x8A, 0x0F, 0x03}, + {0xA3, 0x02, 0x02}, + {0x84, 0xFF, 0x00}, + {0x85, 0xFF, 0x00}, + {0x8A, 0x0F, 0x0C}, + {0x81, 0xFF, 0x0E}, + {0x83, 0x03, 0x03}, + {0x24, 0x6F, 0x6C}, + {0xB7, 0x01, 0x01}, + {0x31, 0x01, 0x01}, + {0x32, 0xF8, 0x08}, + {0x32, 0xF8, 0x48}, + {0x32, 0xF8, 0xF8}, + {0xE0, 0xFE, 0xAC}, + {0xE1, 0xFE, 0xAC}, + {0x3A, 0x24, 0x24}, + {0xE0, 0xFE, 0x3C}, + {0xE1, 0xFE, 0x3C}, + {0xE0, 0xFE, 0x1C}, + {0xE1, 0xFE, 0x1C}, + {0xE0, 0xFE, 0x10}, + {0xE1, 0xFE, 0x10}, +}; + +static struct adie_codec_register spk_off[] = { + {0x8A, 0x0F, 0x0F}, + {0xE0, 0xFE, 0x1C}, + {0xE1, 0xFE, 0x1C}, + {0xE0, 0xFE, 0x3C}, + {0xE1, 0xFE, 0x3C}, + {0xE0, 0xFC, 0xAC}, + {0xE1, 0xFC, 0xAC}, + {0x32, 0xF8, 0x00}, + {0x31, 0x05, 0x00}, + {0x3A, 0x24, 0x00}, +}; + +static struct adie_codec_register spk_mute[] = { + {0x84, 0xFF, 0xAC}, + {0x85, 0xFF, 0xAC}, + {0x8A, 0x0F, 0x0C}, +}; + +static struct adie_codec_register spk_unmute[] = { + {0x84, 0xFF, 0x00}, + {0x85, 0xFF, 0x00}, + {0x8A, 0x0F, 0x0C}, +}; + +struct adie_codec_path { + int rate; /* sample rate of path */ + u32 reg_owner; +}; + +struct timpani_drv_data { /* member undecided */ + struct snd_soc_codec codec; + struct adie_codec_path path[ADIE_CODEC_MAX]; + u32 ref_cnt; + struct marimba_codec_platform_data *codec_pdata; +}; + +static struct snd_soc_codec *timpani_codec; + +enum /* regaccess blk id */ +{ + RA_BLOCK_RX1 = 0, + RA_BLOCK_RX2, + RA_BLOCK_TX1, + RA_BLOCK_TX2, + RA_BLOCK_LB, + RA_BLOCK_SHARED_RX_LB, + RA_BLOCK_SHARED_TX, + RA_BLOCK_TXFE1, + RA_BLOCK_TXFE2, + RA_BLOCK_PA_COMMON, + RA_BLOCK_PA_EAR, + RA_BLOCK_PA_HPH, + RA_BLOCK_PA_LINE, + RA_BLOCK_PA_AUX, + RA_BLOCK_ADC, + RA_BLOCK_DMIC, + RA_BLOCK_TX_I2S, + RA_BLOCK_DRV, + RA_BLOCK_TEST, + RA_BLOCK_RESERVED, + RA_BLOCK_NUM, +}; + +enum /* regaccess onwer ID */ +{ + RA_OWNER_NONE = 0, + RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, + RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, + RA_OWNER_PATH_LB, + RA_OWNER_DRV, + RA_OWNER_NUM, +}; + +struct reg_acc_blk_cfg { + u8 valid_owners[RA_OWNER_NUM]; +}; + +struct timpani_regaccess { + u8 reg_addr; + u8 blk_mask[RA_BLOCK_NUM]; + u8 reg_mask; + u8 reg_default; +}; + +static unsigned int timpani_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct marimba *pdrv = codec->control_data; + int rc; + u8 val; + + rc = marimba_read(pdrv, reg, &val, 1); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + return 0; + } + return val; +} + +static int timpani_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct marimba *pdrv = codec->control_data; + int rc; + + rc = marimba_write_bit_mask(pdrv, reg, (u8 *)&value, 1, 0xFF); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + return -EIO; + } + pr_debug("%s: write reg %x val %x\n", __func__, reg, value); + return 0; +} + +static void timpani_codec_bring_up(struct snd_soc_codec *codec) +{ + struct timpani_drv_data *timpani = snd_soc_codec_get_drvdata(codec); + int rc; + + if (timpani->codec_pdata && + timpani->codec_pdata->marimba_codec_power) { + if (timpani->ref_cnt) + return; + /* Codec power up sequence */ + rc = timpani->codec_pdata->marimba_codec_power(1); + if (rc) + pr_err("%s: could not power up timpani " + "codec\n", __func__); + else { + timpani_codec_write(codec, 0xFF, 0x08); + timpani_codec_write(codec, 0xFF, 0x0A); + timpani_codec_write(codec, 0xFF, 0x0E); + timpani_codec_write(codec, 0xFF, 0x07); + timpani_codec_write(codec, 0xFF, 0x17); + timpani_codec_write(codec, TIMPANI_A_MREF, 0x22); + msleep(15); + timpani->ref_cnt++; + } + } +} + +static void timpani_codec_bring_down(struct snd_soc_codec *codec) +{ + struct timpani_drv_data *timpani = snd_soc_codec_get_drvdata(codec); + int rc; + + if (timpani->codec_pdata && + timpani->codec_pdata->marimba_codec_power) { + timpani->ref_cnt--; + if (timpani->ref_cnt >= 1) + return; + timpani_codec_write(codec, TIMPANI_A_MREF, TIMPANI_MREF_POR); + timpani_codec_write(codec, 0xFF, 0x07); + timpani_codec_write(codec, 0xFF, 0x06); + timpani_codec_write(codec, 0xFF, 0x0E); + timpani_codec_write(codec, 0xFF, 0x08); + rc = timpani->codec_pdata->marimba_codec_power(0); + if (rc) + pr_err("%s: could not power down timpani " + "codec\n", __func__); + } +} + +static void timpani_dmic_config(struct snd_soc_codec *codec, int on) +{ + struct adie_codec_register *regs; + int regs_sz, i; + + if (on) { + regs = dmic_on; + regs_sz = ARRAY_SIZE(dmic_on); + } else { + regs = dmic_off; + regs_sz = ARRAY_SIZE(dmic_off); + } + + for (i = 0; i < regs_sz; i++) + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); +} + +static void timpani_spk_config(struct snd_soc_codec *codec, int on) +{ + struct adie_codec_register *regs; + int regs_sz, i; + + if (on) { + regs = spk_on; + regs_sz = ARRAY_SIZE(spk_on); + } else { + regs = spk_off; + regs_sz = ARRAY_SIZE(spk_off); + } + + for (i = 0; i < regs_sz; i++) + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); +} + +static int timpani_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + pr_info("%s()\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_info("%s: playback\n", __func__); + timpani_codec_bring_up(codec); + timpani_spk_config(codec, 1); + } else { + pr_info("%s: Capture\n", __func__); + timpani_codec_bring_up(codec); + timpani_dmic_config(codec, 1); + } + return 0; +} + +static void timpani_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + pr_info("%s()\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + timpani_codec_bring_down(codec); + timpani_spk_config(codec, 0); + } else { + timpani_codec_bring_down(codec); + timpani_dmic_config(codec, 0); + } + return; +} + +int digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct adie_codec_register *regs; + int regs_sz, i; + + if (mute) { + regs = spk_mute; + regs_sz = ARRAY_SIZE(spk_mute); + } else { + regs = spk_unmute; + regs_sz = ARRAY_SIZE(spk_unmute); + } + + for (i = 0; i < regs_sz; i++) { + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); + msleep(10); + } + + return 0; +} + +static struct snd_soc_dai_ops timpani_dai_ops = { + .startup = timpani_startup, + .shutdown = timpani_shutdown, +}; + +struct snd_soc_dai timpani_codec_dai[] = { + { + .name = "TIMPANI Rx", + .playback = { + .stream_name = "Handset Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &timpani_dai_ops, + }, + { + .name = "TIMPANI Tx", + .capture = { + .stream_name = "Handset Capture", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &timpani_dai_ops, + } +}; + +static int timpani_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!timpani_codec) { + dev_err(&pdev->dev, "core driver not yet probed\n"); + return -ENODEV; + } + + socdev->card->codec = timpani_codec; + codec = timpani_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + dev_err(codec->dev, "failed to create pcms\n"); + return ret; +} + +/* power down chip */ +static int timpani_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_timpani = { + .probe = timpani_soc_probe, + .remove = timpani_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_timpani); + +static int timpani_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_codec *codec; + struct timpani_drv_data *priv; + + pr_info("%s()\n", __func__); + priv = kzalloc(sizeof(struct timpani_drv_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + codec = &priv->codec; + snd_soc_codec_set_drvdata(codec, priv); + priv->codec_pdata = pdev->dev.platform_data; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "TIMPANI"; + codec->owner = THIS_MODULE; + codec->read = timpani_codec_read; + codec->write = timpani_codec_write; + codec->dai = timpani_codec_dai; + codec->num_dai = ARRAY_SIZE(timpani_codec_dai); + codec->control_data = platform_get_drvdata(pdev); + timpani_codec = codec; + + snd_soc_register_dais(timpani_codec_dai, ARRAY_SIZE(timpani_codec_dai)); + snd_soc_register_codec(codec); + + return 0; +} + +static struct platform_driver timpani_codec_driver = { + .probe = timpani_codec_probe, + .driver = { + .name = "timpani_codec", + .owner = THIS_MODULE, + }, +}; + +static int __init timpani_codec_init(void) +{ + return platform_driver_register(&timpani_codec_driver); +} + +static void __exit timpani_codec_exit(void) +{ + platform_driver_unregister(&timpani_codec_driver); +} + +module_init(timpani_codec_init); +module_exit(timpani_codec_exit); + +MODULE_DESCRIPTION("Timpani codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/timpani.h b/sound/soc/codecs/timpani.h new file mode 100644 index 000000000000..bfa0b8b644aa --- /dev/null +++ b/sound/soc/codecs/timpani.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define NUM_I2S 2 +extern struct snd_soc_dai timpani_codec_dai[NUM_I2S]; +extern struct snd_soc_codec_device soc_codec_dev_timpani; diff --git a/sound/soc/codecs/wcd9304-tables.c b/sound/soc/codecs/wcd9304-tables.c new file mode 100644 index 000000000000..9b79702f7b56 --- /dev/null +++ b/sound/soc/codecs/wcd9304-tables.c @@ -0,0 +1,722 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "wcd9304.h" + +const u8 sitar_reg_defaults[SITAR_CACHE_SIZE] = { + [WCD9XXX_A_CHIP_CTL] = WCD9XXX_A_CHIP_CTL__POR, + [WCD9XXX_A_CHIP_STATUS] = WCD9XXX_A_CHIP_STATUS__POR, + [WCD9XXX_A_CHIP_ID_BYTE_0] = WCD9XXX_A_CHIP_ID_BYTE_0__POR, + [WCD9XXX_A_CHIP_ID_BYTE_1] = WCD9XXX_A_CHIP_ID_BYTE_1__POR, + [WCD9XXX_A_CHIP_ID_BYTE_2] = WCD9XXX_A_CHIP_ID_BYTE_2__POR, + [WCD9XXX_A_CHIP_ID_BYTE_3] = WCD9XXX_A_CHIP_ID_BYTE_3__POR, + [WCD9XXX_A_CHIP_VERSION] = WCD9XXX_A_CHIP_VERSION__POR, + [WCD9XXX_A_SB_VERSION] = WCD9XXX_A_SB_VERSION__POR, + [WCD9XXX_A_SLAVE_ID_1] = WCD9XXX_A_SLAVE_ID_1__POR, + [WCD9XXX_A_SLAVE_ID_2] = WCD9XXX_A_SLAVE_ID_2__POR, + [WCD9XXX_A_SLAVE_ID_3] = WCD9XXX_A_SLAVE_ID_3__POR, + [SITAR_A_PIN_CTL_OE0] = SITAR_A_PIN_CTL_OE0__POR, + [SITAR_A_PIN_CTL_OE1] = SITAR_A_PIN_CTL_OE1__POR, + [SITAR_A_PIN_CTL_DATA0] = SITAR_A_PIN_CTL_DATA0__POR, + [SITAR_A_PIN_CTL_DATA1] = SITAR_A_PIN_CTL_DATA1__POR, + [SITAR_A_HDRIVE_GENERIC] = SITAR_A_HDRIVE_GENERIC__POR, + [SITAR_A_HDRIVE_OVERRIDE] = SITAR_A_HDRIVE_OVERRIDE__POR, + [SITAR_A_ANA_CSR_WAIT_STATE] = SITAR_A_ANA_CSR_WAIT_STATE__POR, + [SITAR_A_PROCESS_MONITOR_CTL0] = SITAR_A_PROCESS_MONITOR_CTL0__POR, + [SITAR_A_PROCESS_MONITOR_CTL1] = SITAR_A_PROCESS_MONITOR_CTL1__POR, + [SITAR_A_PROCESS_MONITOR_CTL2] = SITAR_A_PROCESS_MONITOR_CTL2__POR, + [SITAR_A_PROCESS_MONITOR_CTL3] = SITAR_A_PROCESS_MONITOR_CTL3__POR, + [SITAR_A_QFUSE_CTL] = SITAR_A_QFUSE_CTL__POR, + [SITAR_A_QFUSE_STATUS] = SITAR_A_QFUSE_STATUS__POR, + [SITAR_A_QFUSE_DATA_OUT0] = SITAR_A_QFUSE_DATA_OUT0__POR, + [SITAR_A_QFUSE_DATA_OUT1] = SITAR_A_QFUSE_DATA_OUT1__POR, + [SITAR_A_QFUSE_DATA_OUT2] = SITAR_A_QFUSE_DATA_OUT2__POR, + [SITAR_A_QFUSE_DATA_OUT3] = SITAR_A_QFUSE_DATA_OUT3__POR, + [SITAR_A_CDC_CTL] = SITAR_A_CDC_CTL__POR, + [SITAR_A_LEAKAGE_CTL] = SITAR_A_LEAKAGE_CTL__POR, + [SITAR_A_INTR_MODE] = SITAR_A_INTR_MODE__POR, + [SITAR_A_INTR_MASK0] = SITAR_A_INTR_MASK0__POR, + [SITAR_A_INTR_MASK1] = SITAR_A_INTR_MASK1__POR, + [SITAR_A_INTR_MASK2] = SITAR_A_INTR_MASK2__POR, + [SITAR_A_INTR_STATUS0] = SITAR_A_INTR_STATUS0__POR, + [SITAR_A_INTR_STATUS1] = SITAR_A_INTR_STATUS1__POR, + [SITAR_A_INTR_STATUS2] = SITAR_A_INTR_STATUS2__POR, + [SITAR_A_INTR_CLEAR0] = SITAR_A_INTR_CLEAR0__POR, + [SITAR_A_INTR_CLEAR1] = SITAR_A_INTR_CLEAR1__POR, + [SITAR_A_INTR_CLEAR2] = SITAR_A_INTR_CLEAR2__POR, + [SITAR_A_INTR_LEVEL0] = SITAR_A_INTR_LEVEL0__POR, + [SITAR_A_INTR_LEVEL1] = SITAR_A_INTR_LEVEL1__POR, + [SITAR_A_INTR_LEVEL2] = SITAR_A_INTR_LEVEL2__POR, + [SITAR_A_INTR_TEST0] = SITAR_A_INTR_TEST0__POR, + [SITAR_A_INTR_TEST1] = SITAR_A_INTR_TEST1__POR, + [SITAR_A_INTR_TEST2] = SITAR_A_INTR_TEST2__POR, + [SITAR_A_INTR_SET0] = SITAR_A_INTR_SET0__POR, + [SITAR_A_INTR_SET1] = SITAR_A_INTR_SET1__POR, + [SITAR_A_INTR_SET2] = SITAR_A_INTR_SET2__POR, + [SITAR_A_CDC_TX_I2S_SCK_MODE] = SITAR_A_CDC_TX_I2S_SCK_MODE__POR, + [SITAR_A_CDC_TX_I2S_WS_MODE] = SITAR_A_CDC_TX_I2S_WS_MODE__POR, + [SITAR_A_CDC_DMIC_DATA0_MODE] = SITAR_A_CDC_DMIC_DATA0_MODE__POR, + [SITAR_A_CDC_DMIC_CLK0_MODE] = SITAR_A_CDC_DMIC_CLK0_MODE__POR, + [SITAR_A_CDC_DMIC_DATA1_MODE] = SITAR_A_CDC_DMIC_DATA1_MODE__POR, + [SITAR_A_CDC_DMIC_CLK1_MODE] = SITAR_A_CDC_DMIC_CLK1_MODE__POR, + [SITAR_A_CDC_TX_I2S_SD0_MODE] = SITAR_A_CDC_TX_I2S_SD0_MODE__POR, + [SITAR_A_CDC_INTR_MODE] = SITAR_A_CDC_INTR_MODE__POR, + [SITAR_A_CDC_RX_I2S_SD0_MODE] = SITAR_A_CDC_RX_I2S_SD0_MODE__POR, + [SITAR_A_CDC_RX_I2S_SD1_MODE] = SITAR_A_CDC_RX_I2S_SD1_MODE__POR, + [SITAR_A_BIAS_REF_CTL] = SITAR_A_BIAS_REF_CTL__POR, + [SITAR_A_BIAS_CENTRAL_BG_CTL] = SITAR_A_BIAS_CENTRAL_BG_CTL__POR, + [SITAR_A_BIAS_PRECHRG_CTL] = SITAR_A_BIAS_PRECHRG_CTL__POR, + [SITAR_A_BIAS_CURR_CTL_1] = SITAR_A_BIAS_CURR_CTL_1__POR, + [SITAR_A_BIAS_CURR_CTL_2] = SITAR_A_BIAS_CURR_CTL_2__POR, + [SITAR_A_BIAS_OSC_BG_CTL] = SITAR_A_BIAS_OSC_BG_CTL__POR, + [SITAR_A_CLK_BUFF_EN1] = SITAR_A_CLK_BUFF_EN1__POR, + [SITAR_A_CLK_BUFF_EN2] = SITAR_A_CLK_BUFF_EN2__POR, + [SITAR_A_LDO_H_MODE_1] = SITAR_A_LDO_H_MODE_1__POR, + [SITAR_A_LDO_H_MODE_2] = SITAR_A_LDO_H_MODE_2__POR, + [SITAR_A_LDO_H_LOOP_CTL] = SITAR_A_LDO_H_LOOP_CTL__POR, + [SITAR_A_LDO_H_COMP_1] = SITAR_A_LDO_H_COMP_1__POR, + [SITAR_A_LDO_H_COMP_2] = SITAR_A_LDO_H_COMP_2__POR, + [SITAR_A_LDO_H_BIAS_1] = SITAR_A_LDO_H_BIAS_1__POR, + [SITAR_A_LDO_H_BIAS_2] = SITAR_A_LDO_H_BIAS_2__POR, + [SITAR_A_LDO_H_BIAS_3] = SITAR_A_LDO_H_BIAS_3__POR, + [SITAR_A_MICB_CFILT_1_CTL] = SITAR_A_MICB_CFILT_1_CTL__POR, + [SITAR_A_MICB_CFILT_1_VAL] = SITAR_A_MICB_CFILT_1_VAL__POR, + [SITAR_A_MICB_CFILT_1_PRECHRG] = SITAR_A_MICB_CFILT_1_PRECHRG__POR, + [SITAR_A_MICB_1_CTL] = SITAR_A_MICB_1_CTL__POR, + [SITAR_A_MICB_1_INT_RBIAS] = SITAR_A_MICB_1_INT_RBIAS__POR, + [SITAR_A_MICB_1_MBHC] = SITAR_A_MICB_1_MBHC__POR, + [SITAR_A_MICB_CFILT_2_CTL] = SITAR_A_MICB_CFILT_2_CTL__POR, + [SITAR_A_MICB_CFILT_2_VAL] = SITAR_A_MICB_CFILT_2_VAL__POR, + [SITAR_A_MICB_CFILT_2_PRECHRG] = SITAR_A_MICB_CFILT_2_PRECHRG__POR, + [SITAR_A_MICB_2_CTL] = SITAR_A_MICB_2_CTL__POR, + [SITAR_A_MICB_2_INT_RBIAS] = SITAR_A_MICB_2_INT_RBIAS__POR, + [SITAR_A_MICB_2_MBHC] = SITAR_A_MICB_2_MBHC__POR, + [SITAR_A_TX_COM_BIAS] = SITAR_A_TX_COM_BIAS__POR, + [SITAR_A_MBHC_SCALING_MUX_1] = SITAR_A_MBHC_SCALING_MUX_1__POR, + [SITAR_A_MBHC_SCALING_MUX_2] = SITAR_A_MBHC_SCALING_MUX_2__POR, + [SITAR_A_TX_SUP_SWITCH_CTRL_1] = SITAR_A_TX_SUP_SWITCH_CTRL_1__POR, + [SITAR_A_TX_SUP_SWITCH_CTRL_2] = SITAR_A_TX_SUP_SWITCH_CTRL_2__POR, + [SITAR_A_TX_1_2_EN] = SITAR_A_TX_1_2_EN__POR, + [SITAR_A_TX_1_2_TEST_EN] = SITAR_A_TX_1_2_TEST_EN__POR, + [SITAR_A_TX_1_2_ADC_CH1] = SITAR_A_TX_1_2_ADC_CH1__POR, + [SITAR_A_TX_1_2_ADC_CH2] = SITAR_A_TX_1_2_ADC_CH2__POR, + [SITAR_A_TX_1_2_ATEST_REFCTRL] = SITAR_A_TX_1_2_ATEST_REFCTRL__POR, + [SITAR_A_TX_1_2_TEST_CTL] = SITAR_A_TX_1_2_TEST_CTL__POR, + [SITAR_A_TX_1_2_TEST_BLOCK_EN] = SITAR_A_TX_1_2_TEST_BLOCK_EN__POR, + [SITAR_A_TX_1_2_TXFE_CLKDIV] = SITAR_A_TX_1_2_TXFE_CLKDIV__POR, + [SITAR_A_TX_1_2_SAR_ERR_CH1] = SITAR_A_TX_1_2_SAR_ERR_CH1__POR, + [SITAR_A_TX_1_2_SAR_ERR_CH2] = SITAR_A_TX_1_2_SAR_ERR_CH2__POR, + [SITAR_A_TX_3_EN] = SITAR_A_TX_3_EN__POR, + [SITAR_A_TX_3_TEST_EN] = SITAR_A_TX_3_TEST_EN__POR, + [SITAR_A_TX_3_ADC] = SITAR_A_TX_3_ADC__POR, + [SITAR_A_TX_3_MBHC_ATEST_REFCTRL] = + SITAR_A_TX_3_MBHC_ATEST_REFCTRL__POR, + [SITAR_A_TX_3_TEST_CTL] = SITAR_A_TX_3_TEST_CTL__POR, + [SITAR_A_TX_3_TEST_BLOCK_EN] = SITAR_A_TX_3_TEST_BLOCK_EN__POR, + [SITAR_A_TX_3_TXFE_CKDIV] = SITAR_A_TX_3_TXFE_CKDIV__POR, + [SITAR_A_TX_3_SAR_ERR] = SITAR_A_TX_3_SAR_ERR__POR, + [SITAR_A_TX_4_MBHC_EN] = SITAR_A_TX_4_MBHC_EN__POR, + [SITAR_A_TX_4_MBHC_ADC] = SITAR_A_TX_4_MBHC_ADC__POR, + [SITAR_A_TX_4_MBHC_TEST_CTL] = SITAR_A_TX_4_MBHC_TEST_CTL__POR, + [SITAR_A_TX_4_MBHC_SAR_ERR] = SITAR_A_TX_4_MBHC_SAR_ERR__POR, + [SITAR_A_TX_4_TXFE_CLKDIV] = SITAR_A_TX_4_TXFE_CLKDIV__POR, + [SITAR_A_AUX_COM_CTL] = SITAR_A_AUX_COM_CTL__POR, + [SITAR_A_AUX_COM_ATEST] = SITAR_A_AUX_COM_ATEST__POR, + [SITAR_A_AUX_L_EN] = SITAR_A_AUX_L_EN__POR, + [SITAR_A_AUX_L_GAIN] = SITAR_A_AUX_L_GAIN__POR, + [SITAR_A_AUX_L_PA_CONN] = SITAR_A_AUX_L_PA_CONN__POR, + [SITAR_A_AUX_L_PA_CONN_INV] = SITAR_A_AUX_L_PA_CONN_INV__POR, + [SITAR_A_AUX_R_EN] = SITAR_A_AUX_R_EN__POR, + [SITAR_A_AUX_R_GAIN] = SITAR_A_AUX_R_GAIN__POR, + [SITAR_A_AUX_R_PA_CONN] = SITAR_A_AUX_R_PA_CONN__POR, + [SITAR_A_AUX_R_PA_CONN_INV] = SITAR_A_AUX_R_PA_CONN_INV__POR, + [SITAR_A_CP_EN] = SITAR_A_CP_EN__POR, + [SITAR_A_CP_CLK] = SITAR_A_CP_CLK__POR, + [SITAR_A_CP_STATIC] = SITAR_A_CP_STATIC__POR, + [SITAR_A_CP_DCC1] = SITAR_A_CP_DCC1__POR, + [SITAR_A_CP_DCC3] = SITAR_A_CP_DCC3__POR, + [SITAR_A_CP_ATEST] = SITAR_A_CP_ATEST__POR, + [SITAR_A_CP_DTEST] = SITAR_A_CP_DTEST__POR, + [SITAR_A_RX_COM_TIMER_DIV] = SITAR_A_RX_COM_TIMER_DIV__POR, + [SITAR_A_RX_COM_OCP_CTL] = SITAR_A_RX_COM_OCP_CTL__POR, + [SITAR_A_RX_COM_OCP_COUNT] = SITAR_A_RX_COM_OCP_COUNT__POR, + [SITAR_A_RX_COM_DAC_CTL] = SITAR_A_RX_COM_DAC_CTL__POR, + [SITAR_A_RX_COM_BIAS] = SITAR_A_RX_COM_BIAS__POR, + [SITAR_A_RX_HPH_BIAS_PA] = SITAR_A_RX_HPH_BIAS_PA__POR, + [SITAR_A_RX_HPH_BIAS_LDO] = SITAR_A_RX_HPH_BIAS_LDO__POR, + [SITAR_A_RX_HPH_BIAS_CNP] = SITAR_A_RX_HPH_BIAS_CNP__POR, + [SITAR_A_RX_HPH_BIAS_WG] = SITAR_A_RX_HPH_BIAS_WG__POR, + [SITAR_A_RX_HPH_OCP_CTL] = SITAR_A_RX_HPH_OCP_CTL__POR, + [SITAR_A_RX_HPH_CNP_EN] = SITAR_A_RX_HPH_CNP_EN__POR, + [SITAR_A_RX_HPH_CNP_WG_CTL] = SITAR_A_RX_HPH_CNP_WG_CTL__POR, + [SITAR_A_RX_HPH_CNP_WG_TIME] = SITAR_A_RX_HPH_CNP_WG_TIME__POR, + [SITAR_A_RX_HPH_L_GAIN] = SITAR_A_RX_HPH_L_GAIN__POR, + [SITAR_A_RX_HPH_L_TEST] = SITAR_A_RX_HPH_L_TEST__POR, + [SITAR_A_RX_HPH_L_PA_CTL] = SITAR_A_RX_HPH_L_PA_CTL__POR, + [SITAR_A_RX_HPH_L_DAC_CTL] = SITAR_A_RX_HPH_L_DAC_CTL__POR, + [SITAR_A_RX_HPH_L_ATEST] = SITAR_A_RX_HPH_L_ATEST__POR, + [SITAR_A_RX_HPH_L_STATUS] = SITAR_A_RX_HPH_L_STATUS__POR, + [SITAR_A_RX_HPH_R_GAIN] = SITAR_A_RX_HPH_R_GAIN__POR, + [SITAR_A_RX_HPH_R_TEST] = SITAR_A_RX_HPH_R_TEST__POR, + [SITAR_A_RX_HPH_R_PA_CTL] = SITAR_A_RX_HPH_R_PA_CTL__POR, + [SITAR_A_RX_HPH_R_DAC_CTL] = SITAR_A_RX_HPH_R_DAC_CTL__POR, + [SITAR_A_RX_HPH_R_ATEST] = SITAR_A_RX_HPH_R_ATEST__POR, + [SITAR_A_RX_HPH_R_STATUS] = SITAR_A_RX_HPH_R_STATUS__POR, + [SITAR_A_RX_EAR_BIAS_PA] = SITAR_A_RX_EAR_BIAS_PA__POR, + [SITAR_A_RX_EAR_BIAS_CMBUFF] = SITAR_A_RX_EAR_BIAS_CMBUFF__POR, + [SITAR_A_RX_EAR_EN] = SITAR_A_RX_EAR_EN__POR, + [SITAR_A_RX_EAR_GAIN] = SITAR_A_RX_EAR_GAIN__POR, + [SITAR_A_RX_EAR_CMBUFF] = SITAR_A_RX_EAR_CMBUFF__POR, + [SITAR_A_RX_EAR_ICTL] = SITAR_A_RX_EAR_ICTL__POR, + [SITAR_A_RX_EAR_CCOMP] = SITAR_A_RX_EAR_CCOMP__POR, + [SITAR_A_RX_EAR_VCM] = SITAR_A_RX_EAR_VCM__POR, + [SITAR_A_RX_EAR_CNP] = SITAR_A_RX_EAR_CNP__POR, + [SITAR_A_RX_EAR_ATEST] = SITAR_A_RX_EAR_ATEST__POR, + [SITAR_A_RX_EAR_STATUS] = SITAR_A_RX_EAR_STATUS__POR, + [SITAR_A_RX_LINE_BIAS_PA] = SITAR_A_RX_LINE_BIAS_PA__POR, + [SITAR_A_RX_LINE_BIAS_LDO] = SITAR_A_RX_LINE_BIAS_LDO__POR, + [SITAR_A_RX_LINE_BIAS_CNP1] = SITAR_A_RX_LINE_BIAS_CNP1__POR, + [SITAR_A_RX_LINE_COM] = SITAR_A_RX_LINE_COM__POR, + [SITAR_A_RX_LINE_CNP_EN] = SITAR_A_RX_LINE_CNP_EN__POR, + [SITAR_A_RX_LINE_CNP_WG_CTL] = SITAR_A_RX_LINE_CNP_WG_CTL__POR, + [SITAR_A_RX_LINE_CNP_WG_TIME] = SITAR_A_RX_LINE_CNP_WG_TIME__POR, + [SITAR_A_RX_LINE_1_GAIN] = SITAR_A_RX_LINE_1_GAIN__POR, + [SITAR_A_RX_LINE_1_TEST] = SITAR_A_RX_LINE_1_TEST__POR, + [SITAR_A_RX_LINE_1_DAC_CTL] = SITAR_A_RX_LINE_1_DAC_CTL__POR, + [SITAR_A_RX_LINE_1_STATUS] = SITAR_A_RX_LINE_1_STATUS__POR, + [SITAR_A_RX_LINE_2_GAIN] = SITAR_A_RX_LINE_2_GAIN__POR, + [SITAR_A_RX_LINE_2_TEST] = SITAR_A_RX_LINE_2_TEST__POR, + [SITAR_A_RX_LINE_2_DAC_CTL] = SITAR_A_RX_LINE_2_DAC_CTL__POR, + [SITAR_A_RX_LINE_2_STATUS] = SITAR_A_RX_LINE_2_STATUS__POR, + [SITAR_A_RX_LINE_BIAS_CNP2] = SITAR_A_RX_LINE_BIAS_CNP2__POR, + [SITAR_A_RX_LINE_OCP_CTL] = SITAR_A_RX_LINE_OCP_CTL__POR, + [SITAR_A_RX_LINE_1_PA_CTL] = SITAR_A_RX_LINE_1_PA_CTL__POR, + [SITAR_A_RX_LINE_2_PA_CTL] = SITAR_A_RX_LINE_2_PA_CTL__POR, + [SITAR_A_RX_LINE_CNP_DBG] = SITAR_A_RX_LINE_CNP_DBG__POR, + [SITAR_A_MBHC_HPH] = SITAR_A_MBHC_HPH__POR, + [SITAR_A_RC_OSC_FREQ] = SITAR_A_RC_OSC_FREQ__POR, + [SITAR_A_RC_OSC_TEST] = SITAR_A_RC_OSC_TEST__POR, + [SITAR_A_RC_OSC_STATUS] = SITAR_A_RC_OSC_STATUS__POR, + [SITAR_A_RC_OSC_TUNER] = SITAR_A_RC_OSC_TUNER__POR, + [SITAR_A_CDC_ANC1_CTL] = SITAR_A_CDC_ANC1_CTL__POR, + [SITAR_A_CDC_ANC1_SHIFT] = SITAR_A_CDC_ANC1_SHIFT__POR, + [SITAR_A_CDC_ANC1_IIR_B1_CTL] = SITAR_A_CDC_ANC1_IIR_B1_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B2_CTL] = SITAR_A_CDC_ANC1_IIR_B2_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B3_CTL] = SITAR_A_CDC_ANC1_IIR_B3_CTL__POR, + [SITAR_A_CDC_ANC1_IIR_B4_CTL] = SITAR_A_CDC_ANC1_IIR_B4_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B1_CTL] = SITAR_A_CDC_ANC1_LPF_B1_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B2_CTL] = SITAR_A_CDC_ANC1_LPF_B2_CTL__POR, + [SITAR_A_CDC_ANC1_LPF_B3_CTL] = SITAR_A_CDC_ANC1_LPF_B3_CTL__POR, + [SITAR_A_CDC_ANC1_SPARE] = SITAR_A_CDC_ANC1_SPARE__POR, + [SITAR_A_CDC_ANC1_SMLPF_CTL] = SITAR_A_CDC_ANC1_SMLPF_CTL__POR, + [SITAR_A_CDC_ANC1_DCFLT_CTL] = SITAR_A_CDC_ANC1_DCFLT_CTL__POR, + [SITAR_A_CDC_TX1_VOL_CTL_TIMER] = SITAR_A_CDC_TX1_VOL_CTL_TIMER__POR, + [SITAR_A_CDC_TX1_VOL_CTL_GAIN] = SITAR_A_CDC_TX1_VOL_CTL_GAIN__POR, + [SITAR_A_CDC_TX1_VOL_CTL_CFG] = SITAR_A_CDC_TX1_VOL_CTL_CFG__POR, + [SITAR_A_CDC_TX1_MUX_CTL] = SITAR_A_CDC_TX1_MUX_CTL__POR, + [SITAR_A_CDC_TX1_CLK_FS_CTL] = SITAR_A_CDC_TX1_CLK_FS_CTL__POR, + [SITAR_A_CDC_TX1_DMIC_CTL] = SITAR_A_CDC_TX1_DMIC_CTL__POR, + [SITAR_A_CDC_SRC1_PDA_CFG] = SITAR_A_CDC_SRC1_PDA_CFG__POR, + [SITAR_A_CDC_SRC1_FS_CTL] = SITAR_A_CDC_SRC1_FS_CTL__POR, + [SITAR_A_CDC_RX1_B1_CTL] = SITAR_A_CDC_RX1_B1_CTL__POR, + [SITAR_A_CDC_RX1_B2_CTL] = SITAR_A_CDC_RX1_B2_CTL__POR, + [SITAR_A_CDC_RX1_B3_CTL] = SITAR_A_CDC_RX1_B3_CTL__POR, + [SITAR_A_CDC_RX1_B4_CTL] = SITAR_A_CDC_RX1_B4_CTL__POR, + [SITAR_A_CDC_RX1_B5_CTL] = SITAR_A_CDC_RX1_B5_CTL__POR, + [SITAR_A_CDC_RX1_B6_CTL] = SITAR_A_CDC_RX1_B6_CTL__POR, + [SITAR_A_CDC_RX1_VOL_CTL_B1_CTL] = SITAR_A_CDC_RX1_VOL_CTL_B1_CTL__POR, + [SITAR_A_CDC_RX1_VOL_CTL_B2_CTL] = SITAR_A_CDC_RX1_VOL_CTL_B2_CTL__POR, + [SITAR_A_CDC_CLK_ANC_RESET_CTL] = SITAR_A_CDC_CLK_ANC_RESET_CTL__POR, + [SITAR_A_CDC_CLK_RX_RESET_CTL] = SITAR_A_CDC_CLK_RX_RESET_CTL__POR, + [SITAR_A_CDC_CLK_TX_RESET_B1_CTL] = + SITAR_A_CDC_CLK_TX_RESET_B1_CTL__POR, + [SITAR_A_CDC_CLK_TX_RESET_B2_CTL] = + SITAR_A_CDC_CLK_TX_RESET_B2_CTL__POR, + [SITAR_A_CDC_CLK_DMIC_CTL] = SITAR_A_CDC_CLK_DMIC_CTL__POR, + [SITAR_A_CDC_CLK_RX_I2S_CTL] = SITAR_A_CDC_CLK_RX_I2S_CTL__POR, + [SITAR_A_CDC_CLK_TX_I2S_CTL] = SITAR_A_CDC_CLK_TX_I2S_CTL__POR, + [SITAR_A_CDC_CLK_OTHR_RESET_CTL] = SITAR_A_CDC_CLK_OTHR_RESET_CTL__POR, + [SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL] = + SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR, + [SITAR_A_CDC_CLK_OTHR_CTL] = SITAR_A_CDC_CLK_OTHR_CTL__POR, + [SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL] = + SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL__POR, + [SITAR_A_CDC_CLK_ANC_CLK_EN_CTL] = SITAR_A_CDC_CLK_ANC_CLK_EN_CTL__POR, + [SITAR_A_CDC_CLK_RX_B1_CTL] = SITAR_A_CDC_CLK_RX_B1_CTL__POR, + [SITAR_A_CDC_CLK_RX_B2_CTL] = SITAR_A_CDC_CLK_RX_B2_CTL__POR, + [SITAR_A_CDC_CLK_MCLK_CTL] = SITAR_A_CDC_CLK_MCLK_CTL__POR, + [SITAR_A_CDC_CLK_PDM_CTL] = SITAR_A_CDC_CLK_PDM_CTL__POR, + [SITAR_A_CDC_CLK_SD_CTL] = SITAR_A_CDC_CLK_SD_CTL__POR, + [SITAR_A_CDC_CLK_LP_CTL] = SITAR_A_CDC_CLK_LP_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = + SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR, + [SITAR_A_CDC_CLSG_GAIN_THRESH_CTL] = + SITAR_A_CDC_CLSG_GAIN_THRESH_CTL__POR, + [SITAR_A_CDC_CLSG_TIMER_B1_CFG] = SITAR_A_CDC_CLSG_TIMER_B1_CFG__POR, + [SITAR_A_CDC_CLSG_TIMER_B2_CFG] = SITAR_A_CDC_CLSG_TIMER_B2_CFG__POR, + [SITAR_A_CDC_CLSG_CTL] = SITAR_A_CDC_CLSG_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B1_CTL] = SITAR_A_CDC_IIR1_GAIN_B1_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B2_CTL] = SITAR_A_CDC_IIR1_GAIN_B2_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B3_CTL] = SITAR_A_CDC_IIR1_GAIN_B3_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B4_CTL] = SITAR_A_CDC_IIR1_GAIN_B4_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B5_CTL] = SITAR_A_CDC_IIR1_GAIN_B5_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B6_CTL] = SITAR_A_CDC_IIR1_GAIN_B6_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B7_CTL] = SITAR_A_CDC_IIR1_GAIN_B7_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_B8_CTL] = SITAR_A_CDC_IIR1_GAIN_B8_CTL__POR, + [SITAR_A_CDC_IIR1_CTL] = SITAR_A_CDC_IIR1_CTL__POR, + [SITAR_A_CDC_IIR1_GAIN_TIMER_CTL] = + SITAR_A_CDC_IIR1_GAIN_TIMER_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B1_CTL] = SITAR_A_CDC_IIR1_COEF_B1_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B2_CTL] = SITAR_A_CDC_IIR1_COEF_B2_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B3_CTL] = SITAR_A_CDC_IIR1_COEF_B3_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B4_CTL] = SITAR_A_CDC_IIR1_COEF_B4_CTL__POR, + [SITAR_A_CDC_IIR1_COEF_B5_CTL] = SITAR_A_CDC_IIR1_COEF_B5_CTL__POR, + [SITAR_A_CDC_TOP_GAIN_UPDATE] = SITAR_A_CDC_TOP_GAIN_UPDATE__POR, + [SITAR_A_CDC_TOP_RDAC_DOUT_CTL] = SITAR_A_CDC_TOP_RDAC_DOUT_CTL__POR, + [SITAR_A_CDC_DEBUG_B1_CTL] = SITAR_A_CDC_DEBUG_B1_CTL__POR, + [SITAR_A_CDC_DEBUG_B2_CTL] = SITAR_A_CDC_DEBUG_B2_CTL__POR, + [SITAR_A_CDC_DEBUG_B3_CTL] = SITAR_A_CDC_DEBUG_B3_CTL__POR, + [SITAR_A_CDC_DEBUG_B4_CTL] = SITAR_A_CDC_DEBUG_B4_CTL__POR, + [SITAR_A_CDC_DEBUG_B5_CTL] = SITAR_A_CDC_DEBUG_B5_CTL__POR, + [SITAR_A_CDC_DEBUG_B6_CTL] = SITAR_A_CDC_DEBUG_B6_CTL__POR, + [SITAR_A_CDC_DEBUG_B7_CTL] = SITAR_A_CDC_DEBUG_B7_CTL__POR, + [SITAR_A_CDC_COMP1_B1_CTL] = SITAR_A_CDC_COMP1_B1_CTL__POR, + [SITAR_A_CDC_COMP1_B2_CTL] = SITAR_A_CDC_COMP1_B2_CTL__POR, + [SITAR_A_CDC_COMP1_B3_CTL] = SITAR_A_CDC_COMP1_B3_CTL__POR, + [SITAR_A_CDC_COMP1_B4_CTL] = SITAR_A_CDC_COMP1_B4_CTL__POR, + [SITAR_A_CDC_COMP1_B5_CTL] = SITAR_A_CDC_COMP1_B5_CTL__POR, + [SITAR_A_CDC_COMP1_B6_CTL] = SITAR_A_CDC_COMP1_B6_CTL__POR, + [SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS] = + SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [SITAR_A_CDC_COMP1_FS_CFG] = SITAR_A_CDC_COMP1_FS_CFG__POR, + [SITAR_A_CDC_CONN_RX1_B1_CTL] = SITAR_A_CDC_CONN_RX1_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX1_B2_CTL] = SITAR_A_CDC_CONN_RX1_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX1_B3_CTL] = SITAR_A_CDC_CONN_RX1_B3_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B1_CTL] = SITAR_A_CDC_CONN_RX2_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B2_CTL] = SITAR_A_CDC_CONN_RX2_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX2_B3_CTL] = SITAR_A_CDC_CONN_RX2_B3_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B1_CTL] = SITAR_A_CDC_CONN_RX3_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B2_CTL] = SITAR_A_CDC_CONN_RX3_B2_CTL__POR, + [SITAR_A_CDC_CONN_RX3_B3_CTL] = SITAR_A_CDC_CONN_RX3_B3_CTL__POR, + [SITAR_A_CDC_CONN_ANC_B1_CTL] = SITAR_A_CDC_CONN_ANC_B1_CTL__POR, + [SITAR_A_CDC_CONN_ANC_B2_CTL] = SITAR_A_CDC_CONN_ANC_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_B1_CTL] = SITAR_A_CDC_CONN_TX_B1_CTL__POR, + [SITAR_A_CDC_CONN_TX_B2_CTL] = SITAR_A_CDC_CONN_TX_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B1_CTL] = SITAR_A_CDC_CONN_EQ1_B1_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B2_CTL] = SITAR_A_CDC_CONN_EQ1_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B3_CTL] = SITAR_A_CDC_CONN_EQ1_B3_CTL__POR, + [SITAR_A_CDC_CONN_EQ1_B4_CTL] = SITAR_A_CDC_CONN_EQ1_B4_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B1_CTL] = SITAR_A_CDC_CONN_EQ2_B1_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B2_CTL] = SITAR_A_CDC_CONN_EQ2_B2_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B3_CTL] = SITAR_A_CDC_CONN_EQ2_B3_CTL__POR, + [SITAR_A_CDC_CONN_EQ2_B4_CTL] = SITAR_A_CDC_CONN_EQ2_B4_CTL__POR, + [SITAR_A_CDC_CONN_SRC1_B1_CTL] = SITAR_A_CDC_CONN_SRC1_B1_CTL__POR, + [SITAR_A_CDC_CONN_SRC1_B2_CTL] = SITAR_A_CDC_CONN_SRC1_B2_CTL__POR, + [SITAR_A_CDC_CONN_SRC2_B1_CTL] = SITAR_A_CDC_CONN_SRC2_B1_CTL__POR, + [SITAR_A_CDC_CONN_SRC2_B2_CTL] = SITAR_A_CDC_CONN_SRC2_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B1_CTL] = SITAR_A_CDC_CONN_TX_SB_B1_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B2_CTL] = SITAR_A_CDC_CONN_TX_SB_B2_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B3_CTL] = SITAR_A_CDC_CONN_TX_SB_B3_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B4_CTL] = SITAR_A_CDC_CONN_TX_SB_B4_CTL__POR, + [SITAR_A_CDC_CONN_TX_SB_B5_CTL] = SITAR_A_CDC_CONN_TX_SB_B5_CTL__POR, + [SITAR_A_CDC_CONN_RX_SB_B1_CTL] = SITAR_A_CDC_CONN_RX_SB_B1_CTL__POR, + [SITAR_A_CDC_CONN_RX_SB_B2_CTL] = SITAR_A_CDC_CONN_RX_SB_B2_CTL__POR, + [SITAR_A_CDC_CONN_CLSG_CTL] = SITAR_A_CDC_CONN_CLSG_CTL__POR, + [SITAR_A_CDC_CONN_SPARE] = SITAR_A_CDC_CONN_SPARE__POR, + [SITAR_A_CDC_MBHC_EN_CTL] = SITAR_A_CDC_MBHC_EN_CTL__POR, + [SITAR_A_CDC_MBHC_FIR_B1_CFG] = SITAR_A_CDC_MBHC_FIR_B1_CFG__POR, + [SITAR_A_CDC_MBHC_FIR_B2_CFG] = SITAR_A_CDC_MBHC_FIR_B2_CFG__POR, + [SITAR_A_CDC_MBHC_TIMER_B1_CTL] = SITAR_A_CDC_MBHC_TIMER_B1_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B2_CTL] = SITAR_A_CDC_MBHC_TIMER_B2_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B3_CTL] = SITAR_A_CDC_MBHC_TIMER_B3_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B4_CTL] = SITAR_A_CDC_MBHC_TIMER_B4_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B5_CTL] = SITAR_A_CDC_MBHC_TIMER_B5_CTL__POR, + [SITAR_A_CDC_MBHC_TIMER_B6_CTL] = SITAR_A_CDC_MBHC_TIMER_B6_CTL__POR, + [SITAR_A_CDC_MBHC_B1_STATUS] = SITAR_A_CDC_MBHC_B1_STATUS__POR, + [SITAR_A_CDC_MBHC_B2_STATUS] = SITAR_A_CDC_MBHC_B2_STATUS__POR, + [SITAR_A_CDC_MBHC_B3_STATUS] = SITAR_A_CDC_MBHC_B3_STATUS__POR, + [SITAR_A_CDC_MBHC_B4_STATUS] = SITAR_A_CDC_MBHC_B4_STATUS__POR, + [SITAR_A_CDC_MBHC_B5_STATUS] = SITAR_A_CDC_MBHC_B5_STATUS__POR, + [SITAR_A_CDC_MBHC_B1_CTL] = SITAR_A_CDC_MBHC_B1_CTL__POR, + [SITAR_A_CDC_MBHC_B2_CTL] = SITAR_A_CDC_MBHC_B2_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B1_CTL] = SITAR_A_CDC_MBHC_VOLT_B1_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B2_CTL] = SITAR_A_CDC_MBHC_VOLT_B2_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B3_CTL] = SITAR_A_CDC_MBHC_VOLT_B3_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B4_CTL] = SITAR_A_CDC_MBHC_VOLT_B4_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B5_CTL] = SITAR_A_CDC_MBHC_VOLT_B5_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B6_CTL] = SITAR_A_CDC_MBHC_VOLT_B6_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B7_CTL] = SITAR_A_CDC_MBHC_VOLT_B7_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B8_CTL] = SITAR_A_CDC_MBHC_VOLT_B8_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B9_CTL] = SITAR_A_CDC_MBHC_VOLT_B9_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B10_CTL] = SITAR_A_CDC_MBHC_VOLT_B10_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B11_CTL] = SITAR_A_CDC_MBHC_VOLT_B11_CTL__POR, + [SITAR_A_CDC_MBHC_VOLT_B12_CTL] = SITAR_A_CDC_MBHC_VOLT_B12_CTL__POR, + [SITAR_A_CDC_MBHC_CLK_CTL] = SITAR_A_CDC_MBHC_CLK_CTL__POR, + [SITAR_A_CDC_MBHC_INT_CTL] = SITAR_A_CDC_MBHC_INT_CTL__POR, + [SITAR_A_CDC_MBHC_DEBUG_CTL] = SITAR_A_CDC_MBHC_DEBUG_CTL__POR, + [SITAR_A_CDC_MBHC_SPARE] = SITAR_A_CDC_MBHC_SPARE__POR, +}; + +const u8 sitar_reg_readable[SITAR_CACHE_SIZE] = { + [WCD9XXX_A_CHIP_CTL] = 1, + [WCD9XXX_A_CHIP_STATUS] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_0] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_1] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_2] = 1, + [WCD9XXX_A_CHIP_ID_BYTE_3] = 1, + [WCD9XXX_A_CHIP_VERSION] = 1, + [WCD9XXX_A_SB_VERSION] = 1, + [WCD9XXX_A_SLAVE_ID_1] = 1, + [WCD9XXX_A_SLAVE_ID_2] = 1, + [WCD9XXX_A_SLAVE_ID_3] = 1, + [SITAR_A_PIN_CTL_OE0] = 1, + [SITAR_A_PIN_CTL_OE1] = 1, + [SITAR_A_PIN_CTL_DATA0] = 1, + [SITAR_A_PIN_CTL_DATA1] = 1, + [SITAR_A_HDRIVE_GENERIC] = 1, + [SITAR_A_HDRIVE_OVERRIDE] = 1, + [SITAR_A_ANA_CSR_WAIT_STATE] = 1, + [SITAR_A_PROCESS_MONITOR_CTL0] = 1, + [SITAR_A_PROCESS_MONITOR_CTL1] = 1, + [SITAR_A_PROCESS_MONITOR_CTL2] = 1, + [SITAR_A_PROCESS_MONITOR_CTL3] = 1, + [SITAR_A_QFUSE_CTL] = 1, + [SITAR_A_QFUSE_STATUS] = 1, + [SITAR_A_QFUSE_DATA_OUT0] = 1, + [SITAR_A_QFUSE_DATA_OUT1] = 1, + [SITAR_A_QFUSE_DATA_OUT2] = 1, + [SITAR_A_QFUSE_DATA_OUT3] = 1, + [SITAR_A_CDC_CTL] = 1, + [SITAR_A_LEAKAGE_CTL] = 1, + [SITAR_A_INTR_MODE] = 1, + [SITAR_A_INTR_MASK0] = 1, + [SITAR_A_INTR_MASK1] = 1, + [SITAR_A_INTR_MASK2] = 1, + [SITAR_A_INTR_STATUS0] = 1, + [SITAR_A_INTR_STATUS1] = 1, + [SITAR_A_INTR_STATUS2] = 1, + [SITAR_A_INTR_LEVEL0] = 1, + [SITAR_A_INTR_LEVEL1] = 1, + [SITAR_A_INTR_LEVEL2] = 1, + [SITAR_A_INTR_TEST0] = 1, + [SITAR_A_INTR_TEST1] = 1, + [SITAR_A_INTR_TEST2] = 1, + [SITAR_A_INTR_SET0] = 1, + [SITAR_A_INTR_SET1] = 1, + [SITAR_A_INTR_SET2] = 1, + [SITAR_A_CDC_TX_I2S_SCK_MODE] = 1, + [SITAR_A_CDC_TX_I2S_WS_MODE] = 1, + [SITAR_A_CDC_DMIC_DATA0_MODE] = 1, + [SITAR_A_CDC_DMIC_CLK0_MODE] = 1, + [SITAR_A_CDC_DMIC_DATA1_MODE] = 1, + [SITAR_A_CDC_DMIC_CLK1_MODE] = 1, + [SITAR_A_CDC_TX_I2S_SD0_MODE] = 1, + [SITAR_A_CDC_INTR_MODE] = 1, + [SITAR_A_CDC_RX_I2S_SD0_MODE] = 1, + [SITAR_A_CDC_RX_I2S_SD1_MODE] = 1, + [SITAR_A_BIAS_REF_CTL] = 1, + [SITAR_A_BIAS_CENTRAL_BG_CTL] = 1, + [SITAR_A_BIAS_PRECHRG_CTL] = 1, + [SITAR_A_BIAS_CURR_CTL_1] = 1, + [SITAR_A_BIAS_CURR_CTL_2] = 1, + [SITAR_A_BIAS_OSC_BG_CTL] = 1, + [SITAR_A_CLK_BUFF_EN1] = 1, + [SITAR_A_CLK_BUFF_EN2] = 1, + [SITAR_A_LDO_H_MODE_1] = 1, + [SITAR_A_LDO_H_MODE_2] = 1, + [SITAR_A_LDO_H_LOOP_CTL] = 1, + [SITAR_A_LDO_H_COMP_1] = 1, + [SITAR_A_LDO_H_COMP_2] = 1, + [SITAR_A_LDO_H_BIAS_1] = 1, + [SITAR_A_LDO_H_BIAS_2] = 1, + [SITAR_A_LDO_H_BIAS_3] = 1, + [SITAR_A_MICB_CFILT_1_CTL] = 1, + [SITAR_A_MICB_CFILT_1_VAL] = 1, + [SITAR_A_MICB_CFILT_1_PRECHRG] = 1, + [SITAR_A_MICB_1_CTL] = 1, + [SITAR_A_MICB_1_INT_RBIAS] = 1, + [SITAR_A_MICB_1_MBHC] = 1, + [SITAR_A_MICB_CFILT_2_CTL] = 1, + [SITAR_A_MICB_CFILT_2_VAL] = 1, + [SITAR_A_MICB_CFILT_2_PRECHRG] = 1, + [SITAR_A_MICB_2_CTL] = 1, + [SITAR_A_MICB_2_INT_RBIAS] = 1, + [SITAR_A_MICB_2_MBHC] = 1, + [SITAR_A_TX_COM_BIAS] = 1, + [SITAR_A_MBHC_SCALING_MUX_1] = 1, + [SITAR_A_MBHC_SCALING_MUX_2] = 1, + [SITAR_A_TX_SUP_SWITCH_CTRL_1] = 1, + [SITAR_A_TX_SUP_SWITCH_CTRL_2] = 1, + [SITAR_A_TX_1_2_EN] = 1, + [SITAR_A_TX_1_2_TEST_EN] = 1, + [SITAR_A_TX_1_2_ADC_CH1] = 1, + [SITAR_A_TX_1_2_ADC_CH2] = 1, + [SITAR_A_TX_1_2_ATEST_REFCTRL] = 1, + [SITAR_A_TX_1_2_TEST_CTL] = 1, + [SITAR_A_TX_1_2_TEST_BLOCK_EN] = 1, + [SITAR_A_TX_1_2_TXFE_CLKDIV] = 1, + [SITAR_A_TX_1_2_SAR_ERR_CH1] = 1, + [SITAR_A_TX_1_2_SAR_ERR_CH2] = 1, + [SITAR_A_TX_3_EN] = 1, + [SITAR_A_TX_3_TEST_EN] = 1, + [SITAR_A_TX_3_ADC] = 1, + [SITAR_A_TX_3_MBHC_ATEST_REFCTRL] = 1, + [SITAR_A_TX_3_TEST_CTL] = 1, + [SITAR_A_TX_3_TEST_BLOCK_EN] = 1, + [SITAR_A_TX_3_TXFE_CKDIV] = 1, + [SITAR_A_TX_3_SAR_ERR] = 1, + [SITAR_A_TX_4_MBHC_EN] = 1, + [SITAR_A_TX_4_MBHC_ADC] = 1, + [SITAR_A_TX_4_MBHC_TEST_CTL] = 1, + [SITAR_A_TX_4_MBHC_SAR_ERR] = 1, + [SITAR_A_TX_4_TXFE_CLKDIV] = 1, + [SITAR_A_AUX_COM_CTL] = 1, + [SITAR_A_AUX_COM_ATEST] = 1, + [SITAR_A_AUX_L_EN] = 1, + [SITAR_A_AUX_L_GAIN] = 1, + [SITAR_A_AUX_L_PA_CONN] = 1, + [SITAR_A_AUX_L_PA_CONN_INV] = 1, + [SITAR_A_AUX_R_EN] = 1, + [SITAR_A_AUX_R_GAIN] = 1, + [SITAR_A_AUX_R_PA_CONN] = 1, + [SITAR_A_AUX_R_PA_CONN_INV] = 1, + [SITAR_A_CP_EN] = 1, + [SITAR_A_CP_CLK] = 1, + [SITAR_A_CP_STATIC] = 1, + [SITAR_A_CP_DCC1] = 1, + [SITAR_A_CP_DCC3] = 1, + [SITAR_A_CP_ATEST] = 1, + [SITAR_A_CP_DTEST] = 1, + [SITAR_A_RX_COM_TIMER_DIV] = 1, + [SITAR_A_RX_COM_OCP_CTL] = 1, + [SITAR_A_RX_COM_OCP_COUNT] = 1, + [SITAR_A_RX_COM_DAC_CTL] = 1, + [SITAR_A_RX_COM_BIAS] = 1, + [SITAR_A_RX_HPH_BIAS_PA] = 1, + [SITAR_A_RX_HPH_BIAS_LDO] = 1, + [SITAR_A_RX_HPH_BIAS_CNP] = 1, + [SITAR_A_RX_HPH_BIAS_WG] = 1, + [SITAR_A_RX_HPH_OCP_CTL] = 1, + [SITAR_A_RX_HPH_CNP_EN] = 1, + [SITAR_A_RX_HPH_CNP_WG_CTL] = 1, + [SITAR_A_RX_HPH_CNP_WG_TIME] = 1, + [SITAR_A_RX_HPH_L_GAIN] = 1, + [SITAR_A_RX_HPH_L_TEST] = 1, + [SITAR_A_RX_HPH_L_PA_CTL] = 1, + [SITAR_A_RX_HPH_L_DAC_CTL] = 1, + [SITAR_A_RX_HPH_L_ATEST] = 1, + [SITAR_A_RX_HPH_L_STATUS] = 1, + [SITAR_A_RX_HPH_R_GAIN] = 1, + [SITAR_A_RX_HPH_R_TEST] = 1, + [SITAR_A_RX_HPH_R_PA_CTL] = 1, + [SITAR_A_RX_HPH_R_DAC_CTL] = 1, + [SITAR_A_RX_HPH_R_ATEST] = 1, + [SITAR_A_RX_HPH_R_STATUS] = 1, + [SITAR_A_RX_EAR_BIAS_PA] = 1, + [SITAR_A_RX_EAR_BIAS_CMBUFF] = 1, + [SITAR_A_RX_EAR_EN] = 1, + [SITAR_A_RX_EAR_GAIN] = 1, + [SITAR_A_RX_EAR_CMBUFF] = 1, + [SITAR_A_RX_EAR_ICTL] = 1, + [SITAR_A_RX_EAR_CCOMP] = 1, + [SITAR_A_RX_EAR_VCM] = 1, + [SITAR_A_RX_EAR_CNP] = 1, + [SITAR_A_RX_EAR_ATEST] = 1, + [SITAR_A_RX_EAR_STATUS] = 1, + [SITAR_A_RX_LINE_BIAS_PA] = 1, + [SITAR_A_RX_LINE_BIAS_LDO] = 1, + [SITAR_A_RX_LINE_BIAS_CNP1] = 1, + [SITAR_A_RX_LINE_COM] = 1, + [SITAR_A_RX_LINE_CNP_EN] = 1, + [SITAR_A_RX_LINE_CNP_WG_CTL] = 1, + [SITAR_A_RX_LINE_CNP_WG_TIME] = 1, + [SITAR_A_RX_LINE_1_GAIN] = 1, + [SITAR_A_RX_LINE_1_TEST] = 1, + [SITAR_A_RX_LINE_1_DAC_CTL] = 1, + [SITAR_A_RX_LINE_1_STATUS] = 1, + [SITAR_A_RX_LINE_2_GAIN] = 1, + [SITAR_A_RX_LINE_2_TEST] = 1, + [SITAR_A_RX_LINE_2_DAC_CTL] = 1, + [SITAR_A_RX_LINE_2_STATUS] = 1, + [SITAR_A_RX_LINE_BIAS_CNP2] = 1, + [SITAR_A_RX_LINE_OCP_CTL] = 1, + [SITAR_A_RX_LINE_1_PA_CTL] = 1, + [SITAR_A_RX_LINE_2_PA_CTL] = 1, + [SITAR_A_RX_LINE_CNP_DBG] = 1, + [SITAR_A_MBHC_HPH] = 1, + [SITAR_A_RC_OSC_FREQ] = 1, + [SITAR_A_RC_OSC_TEST] = 1, + [SITAR_A_RC_OSC_STATUS] = 1, + [SITAR_A_RC_OSC_TUNER] = 1, + [SITAR_A_CDC_ANC1_CTL] = 1, + [SITAR_A_CDC_ANC1_SHIFT] = 1, + [SITAR_A_CDC_ANC1_IIR_B1_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B2_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B3_CTL] = 1, + [SITAR_A_CDC_ANC1_IIR_B4_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B1_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B2_CTL] = 1, + [SITAR_A_CDC_ANC1_LPF_B3_CTL] = 1, + [SITAR_A_CDC_ANC1_SPARE] = 1, + [SITAR_A_CDC_ANC1_SMLPF_CTL] = 1, + [SITAR_A_CDC_ANC1_DCFLT_CTL] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_TIMER] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_GAIN] = 1, + [SITAR_A_CDC_TX1_VOL_CTL_CFG] = 1, + [SITAR_A_CDC_TX1_MUX_CTL] = 1, + [SITAR_A_CDC_TX1_CLK_FS_CTL] = 1, + [SITAR_A_CDC_TX1_DMIC_CTL] = 1, + [SITAR_A_CDC_SRC1_PDA_CFG] = 1, + [SITAR_A_CDC_SRC1_FS_CTL] = 1, + [SITAR_A_CDC_RX1_B1_CTL] = 1, + [SITAR_A_CDC_RX1_B2_CTL] = 1, + [SITAR_A_CDC_RX1_B3_CTL] = 1, + [SITAR_A_CDC_RX1_B4_CTL] = 1, + [SITAR_A_CDC_RX1_B5_CTL] = 1, + [SITAR_A_CDC_RX2_B5_CTL] = 1, + [SITAR_A_CDC_RX3_B5_CTL] = 1, + [SITAR_A_CDC_RX1_B6_CTL] = 1, + [SITAR_A_CDC_RX1_VOL_CTL_B1_CTL] = 1, + [SITAR_A_CDC_RX1_VOL_CTL_B2_CTL] = 1, + [SITAR_A_CDC_CLK_ANC_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_RX_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_TX_RESET_B1_CTL] = 1, + [SITAR_A_CDC_CLK_TX_RESET_B2_CTL] = 1, + [SITAR_A_CDC_CLK_DMIC_CTL] = 1, + [SITAR_A_CDC_CLK_RX_I2S_CTL] = 1, + [SITAR_A_CDC_CLK_TX_I2S_CTL] = 1, + [SITAR_A_CDC_CLK_OTHR_RESET_CTL] = 1, + [SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL] = 1, + [SITAR_A_CDC_CLK_OTHR_CTL] = 1, + [SITAR_A_CDC_CLK_RDAC_CLK_EN_CTL] = 1, + [SITAR_A_CDC_CLK_ANC_CLK_EN_CTL] = 1, + [SITAR_A_CDC_CLK_RX_B1_CTL] = 1, + [SITAR_A_CDC_CLK_RX_B2_CTL] = 1, + [SITAR_A_CDC_CLK_MCLK_CTL] = 1, + [SITAR_A_CDC_CLK_PDM_CTL] = 1, + [SITAR_A_CDC_CLK_SD_CTL] = 1, + [SITAR_A_CDC_CLK_LP_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = 1, + [SITAR_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = 1, + [SITAR_A_CDC_CLSG_GAIN_THRESH_CTL] = 1, + [SITAR_A_CDC_CLSG_TIMER_B1_CFG] = 1, + [SITAR_A_CDC_CLSG_TIMER_B2_CFG] = 1, + [SITAR_A_CDC_CLSG_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B1_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B2_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B3_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B4_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B5_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B6_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B7_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_B8_CTL] = 1, + [SITAR_A_CDC_IIR1_CTL] = 1, + [SITAR_A_CDC_IIR1_GAIN_TIMER_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B1_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B2_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B3_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B4_CTL] = 1, + [SITAR_A_CDC_IIR1_COEF_B5_CTL] = 1, + [SITAR_A_CDC_TOP_GAIN_UPDATE] = 1, + [SITAR_A_CDC_TOP_RDAC_DOUT_CTL] = 1, + [SITAR_A_CDC_DEBUG_B1_CTL] = 1, + [SITAR_A_CDC_DEBUG_B2_CTL] = 1, + [SITAR_A_CDC_DEBUG_B3_CTL] = 1, + [SITAR_A_CDC_DEBUG_B4_CTL] = 1, + [SITAR_A_CDC_DEBUG_B5_CTL] = 1, + [SITAR_A_CDC_DEBUG_B6_CTL] = 1, + [SITAR_A_CDC_DEBUG_B7_CTL] = 1, + [SITAR_A_CDC_COMP1_B1_CTL] = 1, + [SITAR_A_CDC_COMP1_B2_CTL] = 1, + [SITAR_A_CDC_COMP1_B3_CTL] = 1, + [SITAR_A_CDC_COMP1_B4_CTL] = 1, + [SITAR_A_CDC_COMP1_B5_CTL] = 1, + [SITAR_A_CDC_COMP1_B6_CTL] = 1, + [SITAR_A_CDC_COMP1_SHUT_DOWN_STATUS] = 1, + [SITAR_A_CDC_COMP1_FS_CFG] = 1, + [SITAR_A_CDC_CONN_RX1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX1_B3_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX2_B3_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B2_CTL] = 1, + [SITAR_A_CDC_CONN_RX3_B3_CTL] = 1, + [SITAR_A_CDC_CONN_ANC_B1_CTL] = 1, + [SITAR_A_CDC_CONN_ANC_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_B1_CTL] = 1, + [SITAR_A_CDC_CONN_TX_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B3_CTL] = 1, + [SITAR_A_CDC_CONN_EQ1_B4_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B3_CTL] = 1, + [SITAR_A_CDC_CONN_EQ2_B4_CTL] = 1, + [SITAR_A_CDC_CONN_SRC1_B1_CTL] = 1, + [SITAR_A_CDC_CONN_SRC1_B2_CTL] = 1, + [SITAR_A_CDC_CONN_SRC2_B1_CTL] = 1, + [SITAR_A_CDC_CONN_SRC2_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B1_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B2_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B3_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B4_CTL] = 1, + [SITAR_A_CDC_CONN_TX_SB_B5_CTL] = 1, + [SITAR_A_CDC_CONN_RX_SB_B1_CTL] = 1, + [SITAR_A_CDC_CONN_RX_SB_B2_CTL] = 1, + [SITAR_A_CDC_CONN_CLSG_CTL] = 1, + [SITAR_A_CDC_CONN_SPARE] = 1, + [SITAR_A_CDC_MBHC_EN_CTL] = 1, + [SITAR_A_CDC_MBHC_FIR_B1_CFG] = 1, + [SITAR_A_CDC_MBHC_FIR_B2_CFG] = 1, + [SITAR_A_CDC_MBHC_TIMER_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B3_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B4_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B5_CTL] = 1, + [SITAR_A_CDC_MBHC_TIMER_B6_CTL] = 1, + [SITAR_A_CDC_MBHC_B1_STATUS] = 1, + [SITAR_A_CDC_MBHC_B2_STATUS] = 1, + [SITAR_A_CDC_MBHC_B3_STATUS] = 1, + [SITAR_A_CDC_MBHC_B4_STATUS] = 1, + [SITAR_A_CDC_MBHC_B5_STATUS] = 1, + [SITAR_A_CDC_MBHC_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B1_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B2_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B3_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B4_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B5_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B6_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B7_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B8_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B9_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B10_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B11_CTL] = 1, + [SITAR_A_CDC_MBHC_VOLT_B12_CTL] = 1, + [SITAR_A_CDC_MBHC_CLK_CTL] = 1, + [SITAR_A_CDC_MBHC_INT_CTL] = 1, + [SITAR_A_CDC_MBHC_DEBUG_CTL] = 1, + [SITAR_A_CDC_MBHC_SPARE] = 1, +}; diff --git a/sound/soc/codecs/wcd9304.c b/sound/soc/codecs/wcd9304.c new file mode 100644 index 000000000000..bb9f93cabaf4 --- /dev/null +++ b/sound/soc/codecs/wcd9304.c @@ -0,0 +1,5013 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9304.h" + +#define WCD9304_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_48000) + +#define NUM_DECIMATORS 4 +#define NUM_INTERPOLATORS 3 +#define BITS_PER_REG 8 +#define SITAR_CFILT_FAST_MODE 0x00 +#define SITAR_CFILT_SLOW_MODE 0x40 +#define MBHC_FW_READ_ATTEMPTS 15 +#define MBHC_FW_READ_TIMEOUT 2000000 + +#define SITAR_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR) + +#define SITAR_I2S_MASTER_MODE_MASK 0x08 + +#define SITAR_OCP_ATTEMPT 1 + +#define AIF1_PB 1 +#define AIF1_CAP 2 +#define NUM_CODEC_DAIS 2 + +struct sitar_codec_dai_data { + u32 rate; + u32 *ch_num; + u32 ch_act; + u32 ch_tot; +}; + +#define SITAR_MCLK_RATE_12288KHZ 12288000 +#define SITAR_MCLK_RATE_9600KHZ 9600000 + +#define SITAR_FAKE_INS_THRESHOLD_MS 2500 +#define SITAR_FAKE_REMOVAL_MIN_PERIOD_MS 50 +#define SITAR_MBHC_BUTTON_MIN 0x8000 +#define SITAR_GPIO_IRQ_DEBOUNCE_TIME_US 5000 + +#define SITAR_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0) +#define SITAR_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0) + +#define MBHC_NUM_DCE_PLUG_DETECT 3 +#define SITAR_MBHC_FAKE_INSERT_LOW 10 +#define SITAR_MBHC_FAKE_INSERT_HIGH 80 +#define SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV 500 +#define SITAR_HS_DETECT_PLUG_TIME_MS (5 * 1000) +#define SITAR_HS_DETECT_PLUG_INERVAL_MS 100 +#define NUM_ATTEMPTS_TO_REPORT 5 +#define SITAR_MBHC_STATUS_REL_DETECTION 0x0C +#define SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS 200 + +static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0); +static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); +static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); +static struct snd_soc_dai_driver sitar_dai[]; +static int sitar_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +static int sitar_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +enum sitar_bandgap_type { + SITAR_BANDGAP_OFF = 0, + SITAR_BANDGAP_AUDIO_MODE, + SITAR_BANDGAP_MBHC_MODE, +}; + +struct mbhc_micbias_regs { + u16 cfilt_val; + u16 cfilt_ctl; + u16 mbhc_reg; + u16 int_rbias; + u16 ctl_reg; + u8 cfilt_sel; +}; + +/* Codec supports 2 IIR filters */ +enum { + IIR1 = 0, + IIR2, + IIR_MAX, +}; +/* Codec supports 5 bands */ +enum { + BAND1 = 0, + BAND2, + BAND3, + BAND4, + BAND5, + BAND_MAX, +}; + +/* Flags to track of PA and DAC state. + * PA and DAC should be tracked separately as AUXPGA loopback requires + * only PA to be turned on without DAC being on. */ +enum sitar_priv_ack_flags { + SITAR_HPHL_PA_OFF_ACK = 0, + SITAR_HPHR_PA_OFF_ACK, + SITAR_HPHL_DAC_OFF_ACK, + SITAR_HPHR_DAC_OFF_ACK +}; + +/* Data used by MBHC */ +struct mbhc_internal_cal_data { + u16 dce_z; + u16 dce_mb; + u16 sta_z; + u16 sta_mb; + u32 t_sta_dce; + u32 t_dce; + u32 t_sta; + u32 micb_mv; + u16 v_ins_hu; + u16 v_ins_h; + u16 v_b1_hu; + u16 v_b1_h; + u16 v_b1_huc; + u16 v_brh; + u16 v_brl; + u16 v_no_mic; + u8 npoll; + u8 nbounce_wait; +}; + +enum sitar_mbhc_plug_type { + PLUG_TYPE_INVALID = -1, + PLUG_TYPE_NONE, + PLUG_TYPE_HEADSET, + PLUG_TYPE_HEADPHONE, + PLUG_TYPE_HIGH_HPH, +}; + +enum sitar_mbhc_state { + MBHC_STATE_NONE = -1, + MBHC_STATE_POTENTIAL, + MBHC_STATE_POTENTIAL_RECOVERY, + MBHC_STATE_RELEASE, +}; + +struct sitar_priv { + struct snd_soc_codec *codec; + u32 mclk_freq; + u32 adc_count; + u32 cfilt1_cnt; + u32 cfilt2_cnt; + u32 cfilt3_cnt; + u32 rx_bias_count; + enum sitar_bandgap_type bandgap_type; + bool mclk_enabled; + bool clock_active; + bool config_mode_active; + bool mbhc_polling_active; + unsigned long mbhc_fake_ins_start; + int buttons_pressed; + + enum sitar_micbias_num micbias; + /* void* calibration contains: + * struct sitar_mbhc_general_cfg generic; + * struct sitar_mbhc_plug_detect_cfg plug_det; + * struct sitar_mbhc_plug_type_cfg plug_type; + * struct sitar_mbhc_btn_detect_cfg btn_det; + * struct sitar_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + struct mbhc_internal_cal_data mbhc_data; + + struct wcd9xxx_pdata *pdata; + u32 anc_slot; + + bool no_mic_headset_override; + + struct mbhc_micbias_regs mbhc_bias_regs; + u8 cfilt_k_value; + bool mbhc_micbias_switched; + + /* track PA/DAC state */ + unsigned long hph_pa_dac_state; + + /*track sitar interface type*/ + u8 intf_type; + + u32 hph_status; /* track headhpone status */ + /* define separate work for left and right headphone OCP to avoid + * additional checking on which OCP event to report so no locking + * to ensure synchronization is required + */ + struct work_struct hphlocp_work; /* reporting left hph ocp off */ + struct work_struct hphrocp_work; /* reporting right hph ocp off */ + + u8 hphlocp_cnt; /* headphone left ocp retry */ + u8 hphrocp_cnt; /* headphone right ocp retry */ + + /* Callback function to enable MCLK */ + int (*mclk_cb) (struct snd_soc_codec*, int); + + /* Work to perform MBHC Firmware Read */ + struct delayed_work mbhc_firmware_dwork; + const struct firmware *mbhc_fw; + + /* num of slim ports required */ + struct sitar_codec_dai_data dai[NUM_CODEC_DAIS]; + + /* Currently, only used for mbhc purpose, to protect + * concurrent execution of mbhc threaded irq handlers and + * kill race between DAPM and MBHC.But can serve as a + * general lock to protect codec resource + */ + struct mutex codec_resource_lock; + + struct sitar_mbhc_config mbhc_cfg; + bool in_gpio_handler; + u8 current_plug; + bool lpi_enabled; + enum sitar_mbhc_state mbhc_state; + struct work_struct hs_correct_plug_work; + bool hs_detect_work_stop; + struct delayed_work mbhc_btn_dwork; + unsigned long mbhc_last_resume; /* in jiffies */ +}; + +#ifdef CONFIG_DEBUG_FS +struct sitar_priv *debug_sitar_priv; +#endif + +static int sitar_get_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = sitar->anc_slot; + return 0; +} + +static int sitar_put_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + sitar->anc_slot = ucontrol->value.integer.value[0]; + return 0; +} + +static int sitar_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + ear_pa_gain = snd_soc_read(codec, SITAR_A_RX_EAR_GAIN); + + ear_pa_gain = ear_pa_gain >> 5; + + if (ear_pa_gain == 0x00) { + ucontrol->value.integer.value[0] = 0; + } else if (ear_pa_gain == 0x04) { + ucontrol->value.integer.value[0] = 1; + } else { + pr_err("%s: ERROR: Unsupported Ear Gain = 0x%x\n", + __func__, ear_pa_gain); + return -EINVAL; + } + + pr_debug("%s: ear_pa_gain = 0x%x\n", __func__, ear_pa_gain); + + return 0; +} + +static int sitar_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s: ucontrol->value.integer.value[0] = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + ear_pa_gain = 0x00; + break; + case 1: + ear_pa_gain = 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, SITAR_A_RX_EAR_GAIN, ear_pa_gain); + return 0; +} + +static int sitar_get_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + snd_soc_read(codec, (SITAR_A_CDC_IIR1_CTL + 16 * iir_idx)) & + (1 << band_idx); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0]); + return 0; +} + +static int sitar_put_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + + /* Mask first 5 bits, 6-8 are reserved */ + snd_soc_update_bits(codec, (SITAR_A_CDC_IIR1_CTL + 16 * iir_idx), + (1 << band_idx), (value << band_idx)); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, value); + return 0; +} +static uint32_t get_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx) +{ + /* Address does not automatically update if reading */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + 0x1F, band_idx * BAND_MAX + coeff_idx); + + /* Mask bits top 2 bits since they are reserved */ + return ((snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx)) << 24) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx)) << 16) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx)) << 8) | + (snd_soc_read(codec, + (SITAR_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx)))) & + 0x3FFFFFFF; +} + +static int sitar_get_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + get_iir_band_coeff(codec, iir_idx, band_idx, 0); + ucontrol->value.integer.value[1] = + get_iir_band_coeff(codec, iir_idx, band_idx, 1); + ucontrol->value.integer.value[2] = + get_iir_band_coeff(codec, iir_idx, band_idx, 2); + ucontrol->value.integer.value[3] = + get_iir_band_coeff(codec, iir_idx, band_idx, 3); + ucontrol->value.integer.value[4] = + get_iir_band_coeff(codec, iir_idx, band_idx, 4); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[1], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[2], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[3], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[4]); + return 0; +} + +static void set_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx, uint32_t value) +{ + /* Mask top 3 bits, 6-8 are reserved */ + /* Update address manually each time */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + 0x1F, band_idx * BAND_MAX + coeff_idx); + + /* Mask top 2 bits, 7-8 are reserved */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx), + 0x3F, (value >> 24) & 0x3F); + + /* Isolate 8bits at a time */ + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx), + 0xFF, (value >> 16) & 0xFF); + + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx), + 0xFF, (value >> 8) & 0xFF); + + snd_soc_update_bits(codec, + (SITAR_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx), + 0xFF, value & 0xFF); +} + +static int sitar_put_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + set_iir_band_coeff(codec, iir_idx, band_idx, 0, + ucontrol->value.integer.value[0]); + set_iir_band_coeff(codec, iir_idx, band_idx, 1, + ucontrol->value.integer.value[1]); + set_iir_band_coeff(codec, iir_idx, band_idx, 2, + ucontrol->value.integer.value[2]); + set_iir_band_coeff(codec, iir_idx, band_idx, 3, + ucontrol->value.integer.value[3]); + set_iir_band_coeff(codec, iir_idx, band_idx, 4, + ucontrol->value.integer.value[4]); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 0), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 1), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 2), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 3), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 4)); + return 0; +} + +static const char *sitar_ear_pa_gain_text[] = {"POS_6_DB", "POS_2_DB"}; +static const struct soc_enum sitar_ear_pa_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(2, sitar_ear_pa_gain_text), +}; + +/*cut of frequency for high pass filter*/ +static const char *cf_text[] = { + "MIN_3DB_4Hz", "MIN_3DB_75Hz", "MIN_3DB_150Hz" +}; + +static const struct soc_enum cf_dec1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TX1_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_rxmix1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_RX1_B4_CTL, 1, 3, cf_text); + +static const struct snd_kcontrol_new sitar_snd_controls[] = { + + SOC_ENUM_EXT("EAR PA Gain", sitar_ear_pa_gain_enum[0], + sitar_pa_gain_get, sitar_pa_gain_put), + + SOC_SINGLE_TLV("LINEOUT1 Volume", SITAR_A_RX_LINE_1_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT2 Volume", SITAR_A_RX_LINE_2_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_TLV("HPHL Volume", SITAR_A_RX_HPH_L_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("HPHR Volume", SITAR_A_RX_HPH_R_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_S8_TLV("RX1 Digital Volume", SITAR_A_CDC_RX1_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX2 Digital Volume", SITAR_A_CDC_RX2_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX3 Digital Volume", SITAR_A_CDC_RX3_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + + SOC_SINGLE_S8_TLV("DEC1 Volume", SITAR_A_CDC_TX1_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC2 Volume", SITAR_A_CDC_TX2_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC3 Volume", SITAR_A_CDC_TX3_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC4 Volume", SITAR_A_CDC_TX4_VOL_CTL_GAIN, -84, 40, + digital_gain), + + SOC_SINGLE_S8_TLV("IIR1 INP1 Volume", SITAR_A_CDC_IIR1_GAIN_B1_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP2 Volume", SITAR_A_CDC_IIR1_GAIN_B2_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP3 Volume", SITAR_A_CDC_IIR1_GAIN_B3_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP4 Volume", SITAR_A_CDC_IIR1_GAIN_B4_CTL, -84, + 40, digital_gain), + SOC_SINGLE_TLV("ADC1 Volume", SITAR_A_TX_1_2_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", SITAR_A_TX_1_2_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", SITAR_A_TX_3_EN, 5, 3, 0, analog_gain), + + SOC_SINGLE("MICBIAS1 CAPLESS Switch", SITAR_A_MICB_1_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS2 CAPLESS Switch", SITAR_A_MICB_2_CTL, 4, 1, 1), + + SOC_SINGLE_EXT("ANC Slot", SND_SOC_NOPM, 0, 0, 100, sitar_get_anc_slot, + sitar_put_anc_slot), + + SOC_ENUM("TX1 HPF cut off", cf_dec1_enum), + + SOC_SINGLE("TX1 HPF Switch", SITAR_A_CDC_TX1_MUX_CTL, 3, 1, 0), + + SOC_SINGLE("RX1 HPF Switch", SITAR_A_CDC_RX1_B5_CTL, 2, 1, 0), + + SOC_ENUM("RX1 HPF cut off", cf_rxmix1_enum), + + SOC_SINGLE_EXT("IIR1 Enable Band1", IIR1, BAND1, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band2", IIR1, BAND2, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band3", IIR1, BAND3, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band4", IIR1, BAND4, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band5", IIR1, BAND5, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band1", IIR2, BAND1, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band2", IIR2, BAND2, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band3", IIR2, BAND3, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band4", IIR2, BAND4, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band5", IIR2, BAND5, 1, 0, + sitar_get_iir_enable_audio_mixer, sitar_put_iir_enable_audio_mixer), + + SOC_SINGLE_MULTI_EXT("IIR1 Band1", IIR1, BAND1, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band2", IIR1, BAND2, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band3", IIR1, BAND3, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band4", IIR1, BAND4, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band5", IIR1, BAND5, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band1", IIR2, BAND1, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band2", IIR2, BAND2, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band3", IIR2, BAND3, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band4", IIR2, BAND4, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band5", IIR2, BAND5, 255, 0, 5, + sitar_get_iir_band_audio_mixer, sitar_put_iir_band_audio_mixer), +}; + +static const char *rx_mix1_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2", "RX1", "RX2", "RX3", "RX4", + "RX5" +}; + +static const char *rx_dac1_text[] = { + "ZERO", "RX1", "RX2" +}; + +static const char *rx_dac2_text[] = { + "ZERO", "RX1", +}; + +static const char *rx_dac3_text[] = { + "ZERO", "RX1", "INV_RX1", "RX2" +}; + +static const char *rx_dac4_text[] = { + "ZERO", "ON" +}; + +static const char *sb_tx1_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1" +}; + +static const char *sb_tx2_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC2" +}; + +static const char *sb_tx3_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC3" +}; + +static const char *sb_tx4_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC4" +}; + +static const char *sb_tx5_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "DEC1", "DEC2", "DEC3", "DEC4" +}; + +static const char *dec1_mux_text[] = { + "ZERO", "DMIC1", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC4", "ANC1_FB", +}; + +static const char *dec2_mux_text[] = { + "ZERO", "DMIC2", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC3", "ANC2_FB", +}; + +static const char *dec3_mux_text[] = { + "ZERO", "DMIC3", "ADC1", "ADC2", "ADC3", "MBADC", "DMIC2", "DMIC4" +}; + +static const char *dec4_mux_text[] = { + "ZERO", "DMIC4", "ADC1", "ADC2", "ADC3", "DMIC3", "DMIC2", "DMIC1" +}; + +static const char const *anc_mux_text[] = { + "ZERO", "ADC1", "ADC2", "ADC3", "RSVD1", "RSVD2", "RSVD3", + "MBADC", "RSVD4", "DMIC1", "DMIC2", "DMIC3", "DMIC4" +}; + +static const char const *anc1_fb_mux_text[] = { + "ZERO", "EAR_HPH_L", "EAR_LINE_1", +}; + +static const char *iir1_inp1_text[] = { + "ZERO", "DEC1", "DEC2", "DEC3", "DEC4", "ZERO", "ZERO", "ZERO", + "ZERO", "ZERO", "ZERO", "RX1", "RX2", "RX3", "RX4", "RX5", +}; + +static const struct soc_enum rx_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX1_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX1_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX2_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX2_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX3_B1_CTL, 0, 10, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_RX3_B1_CTL, 4, 10, rx_mix1_text); + +static const struct soc_enum rx_dac1_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 6, 3, rx_dac1_text); + +static const struct soc_enum rx_dac2_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 4, 2, rx_dac2_text); + +static const struct soc_enum rx_dac3_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 2, 4, rx_dac3_text); + +static const struct soc_enum rx_dac4_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_TOP_RDAC_DOUT_CTL, 0, 2, rx_dac4_text); + +static const struct soc_enum sb_tx5_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B5_CTL, 0, 9, sb_tx5_mux_text); + +static const struct soc_enum sb_tx4_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B4_CTL, 0, 9, sb_tx4_mux_text); + +static const struct soc_enum sb_tx3_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B3_CTL, 0, 9, sb_tx3_mux_text); + +static const struct soc_enum sb_tx2_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B2_CTL, 0, 9, sb_tx2_mux_text); + +static const struct soc_enum sb_tx1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_SB_B1_CTL, 0, 9, sb_tx1_mux_text); + +static const struct soc_enum dec1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B1_CTL, 0, 8, dec1_mux_text); + +static const struct soc_enum dec2_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B1_CTL, 3, 8, dec2_mux_text); + +static const struct soc_enum dec3_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B2_CTL, 0, 8, dec3_mux_text); + +static const struct soc_enum dec4_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_TX_B2_CTL, 3, 8, dec4_mux_text); + +static const struct soc_enum anc1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_ANC_B1_CTL, 0, 13, anc_mux_text); + +static const struct soc_enum anc2_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_ANC_B1_CTL, 4, 13, anc_mux_text); + +static const struct soc_enum anc1_fb_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_ANC_B2_CTL, 0, 3, anc1_fb_mux_text); + +static const struct soc_enum iir1_inp1_mux_enum = + SOC_ENUM_SINGLE(SITAR_A_CDC_CONN_EQ1_B1_CTL, 0, 16, iir1_inp1_text); + +static const struct snd_kcontrol_new rx_mix1_inp1_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP1 Mux", rx_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx_mix1_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP2 Mux", rx_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx2_mix1_inp1_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP1 Mux", rx2_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx2_mix1_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP2 Mux", rx2_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx3_mix1_inp1_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP1 Mux", rx3_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx3_mix1_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP2 Mux", rx3_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx_dac1_mux = + SOC_DAPM_ENUM("RX DAC1 Mux", rx_dac1_enum); + +static const struct snd_kcontrol_new rx_dac2_mux = + SOC_DAPM_ENUM("RX DAC2 Mux", rx_dac2_enum); + +static const struct snd_kcontrol_new rx_dac3_mux = + SOC_DAPM_ENUM("RX DAC3 Mux", rx_dac3_enum); + +static const struct snd_kcontrol_new rx_dac4_mux = + SOC_DAPM_ENUM("RX DAC4 Mux", rx_dac4_enum); + +static const struct snd_kcontrol_new sb_tx5_mux = + SOC_DAPM_ENUM("SLIM TX5 MUX Mux", sb_tx5_mux_enum); + +static const struct snd_kcontrol_new sb_tx4_mux = + SOC_DAPM_ENUM("SLIM TX4 MUX Mux", sb_tx4_mux_enum); + +static const struct snd_kcontrol_new sb_tx3_mux = + SOC_DAPM_ENUM("SLIM TX3 MUX Mux", sb_tx3_mux_enum); + +static const struct snd_kcontrol_new sb_tx2_mux = + SOC_DAPM_ENUM("SLIM TX2 MUX Mux", sb_tx2_mux_enum); + +static const struct snd_kcontrol_new sb_tx1_mux = + SOC_DAPM_ENUM("SLIM TX1 MUX Mux", sb_tx1_mux_enum); + +static const struct snd_kcontrol_new dec1_mux = + SOC_DAPM_ENUM("DEC1 MUX Mux", dec1_mux_enum); + +static const struct snd_kcontrol_new dec2_mux = + SOC_DAPM_ENUM("DEC2 MUX Mux", dec2_mux_enum); + +static const struct snd_kcontrol_new dec3_mux = + SOC_DAPM_ENUM("DEC3 MUX Mux", dec3_mux_enum); + +static const struct snd_kcontrol_new dec4_mux = + SOC_DAPM_ENUM("DEC4 MUX Mux", dec4_mux_enum); + +static const struct snd_kcontrol_new iir1_inp1_mux = + SOC_DAPM_ENUM("IIR1 INP1 Mux", iir1_inp1_mux_enum); + +static const struct snd_kcontrol_new anc1_mux = + SOC_DAPM_ENUM("ANC1 MUX Mux", anc1_mux_enum); + +static const struct snd_kcontrol_new anc2_mux = + SOC_DAPM_ENUM("ANC2 MUX Mux", anc2_mux_enum); + +static const struct snd_kcontrol_new anc1_fb_mux = + SOC_DAPM_ENUM("ANC1 FB MUX Mux", anc1_fb_mux_enum); + +static const struct snd_kcontrol_new dac1_switch[] = { + SOC_DAPM_SINGLE("Switch", SITAR_A_RX_EAR_EN, 5, 1, 0), +}; + +static void sitar_codec_enable_adc_block(struct snd_soc_codec *codec, + int enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s %d\n", __func__, enable); + + if (enable) { + sitar->adc_count++; + snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, 0xE0, 0xE0); + + } else { + sitar->adc_count--; + if (!sitar->adc_count) { + if (!sitar->mbhc_polling_active) + snd_soc_update_bits(codec, SITAR_A_TX_COM_BIAS, + 0xE0, 0x0); + } + } +} + +static int sitar_codec_enable_adc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 adc_reg; + u8 init_bit_shift; + + pr_debug("%s %d\n", __func__, event); + + if (w->reg == SITAR_A_TX_1_2_EN) + adc_reg = SITAR_A_TX_1_2_TEST_CTL; + else if (w->reg == SITAR_A_TX_3_EN) + adc_reg = SITAR_A_TX_3_TEST_CTL; + else { + pr_err("%s: Error, invalid adc register\n", __func__); + return -EINVAL; + } + + if (w->shift == 3) + init_bit_shift = 6; + else if (w->shift == 7) + init_bit_shift = 7; + else { + pr_err("%s: Error, invalid init bit postion adc register\n", + __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + sitar_codec_enable_adc_block(codec, 1); + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, + 1 << init_bit_shift); + break; + case SND_SOC_DAPM_POST_PMU: + + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, 0x00); + + break; + case SND_SOC_DAPM_POST_PMD: + sitar_codec_enable_adc_block(codec, 0); + break; + } + return 0; +} + +static int sitar_lineout_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static int sitar_codec_enable_lineout(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 lineout_gain_reg; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (w->shift) { + case 0: + lineout_gain_reg = SITAR_A_RX_LINE_1_GAIN; + break; + case 1: + lineout_gain_reg = SITAR_A_RX_LINE_2_GAIN; + break; + default: + pr_err("%s: Error, incorrect lineout register value\n", + __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, lineout_gain_reg, 0x10, 0x10); + break; + case SND_SOC_DAPM_POST_PMU: + pr_debug("%s: sleeping 16 ms after %s PA turn on\n", + __func__, w->name); + usleep_range(16000, 16000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, lineout_gain_reg, 0x10, 0x00); + break; + } + return 0; +} + +static int sitar_codec_enable_dmic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 tx_dmic_ctl_reg, tx_mux_ctl_reg; + u8 dmic_clk_sel, dmic_clk_en; + unsigned int dmic; + int ret; + + ret = kstrtouint(strpbrk(w->name, "1234"), 10, &dmic); + if (ret < 0) { + pr_err("%s: Invalid DMIC line on the codec\n", __func__); + return -EINVAL; + } + + switch (dmic) { + case 1: + case 2: + dmic_clk_sel = 0x02; + dmic_clk_en = 0x01; + break; + case 3: + case 4: + dmic_clk_sel = 0x08; + dmic_clk_en = 0x04; + break; + + break; + + default: + pr_err("%s: Invalid DMIC Selection\n", __func__); + return -EINVAL; + } + + tx_mux_ctl_reg = SITAR_A_CDC_TX1_MUX_CTL + 8 * (dmic - 1); + tx_dmic_ctl_reg = SITAR_A_CDC_TX1_DMIC_CTL + 8 * (dmic - 1); + + pr_debug("%s %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x01, 0x01); + + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_sel, dmic_clk_sel); + + snd_soc_update_bits(codec, tx_dmic_ctl_reg, 0x1, 0x1); + + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, dmic_clk_en); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, 0); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x01, 0x00); + break; + } + return 0; +} + +static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + const char *filename; + const struct firmware *fw; + int i; + int ret; + int num_anc_slots; + struct anc_header *anc_head; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u32 anc_writes_size = 0; + int anc_size_remaining; + u32 *anc_ptr; + u16 reg; + u8 mask, val, old_val; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + /* Use the same firmware file as that of WCD9310, + * since the register sequences are same for + * WCD9310 and WCD9304 + */ + filename = "wcd9310/wcd9310_anc.bin"; + + ret = request_firmware(&fw, filename, codec->dev); + if (ret != 0) { + dev_err(codec->dev, "Failed to acquire ANC data: %d\n", + ret); + return -ENODEV; + } + + if (fw->size < sizeof(struct anc_header)) { + dev_err(codec->dev, "Not enough data\n"); + release_firmware(fw); + return -ENOMEM; + } + + /* First number is the number of register writes */ + anc_head = (struct anc_header *)(fw->data); + anc_ptr = (u32 *)((u32)fw->data + sizeof(struct anc_header)); + anc_size_remaining = fw->size - sizeof(struct anc_header); + num_anc_slots = anc_head->num_anc_slots; + + if (sitar->anc_slot >= num_anc_slots) { + dev_err(codec->dev, "Invalid ANC slot selected\n"); + release_firmware(fw); + return -EINVAL; + } + + for (i = 0; i < num_anc_slots; i++) { + + if (anc_size_remaining < SITAR_PACKED_REG_SIZE) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -EINVAL; + } + anc_writes_size = (u32)(*anc_ptr); + anc_size_remaining -= sizeof(u32); + anc_ptr += 1; + + if (anc_writes_size * SITAR_PACKED_REG_SIZE + > anc_size_remaining) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -ENOMEM; + } + + if (sitar->anc_slot == i) + break; + + anc_size_remaining -= (anc_writes_size * + SITAR_PACKED_REG_SIZE); + anc_ptr += anc_writes_size; + } + if (i == num_anc_slots) { + dev_err(codec->dev, "Selected ANC slot not present\n"); + release_firmware(fw); + return -ENOMEM; + } + + for (i = 0; i < anc_writes_size; i++) { + SITAR_CODEC_UNPACK_ENTRY(anc_ptr[i], reg, + mask, val); + old_val = snd_soc_read(codec, reg); + snd_soc_write(codec, reg, (old_val & ~mask) | + (val & mask)); + } + + release_firmware(fw); + + /* For Sitar, it is required to enable both Feed-forward + * and Feed back clocks to enable ANC + */ + snd_soc_write(codec, SITAR_A_CDC_CLK_ANC_CLK_EN_CTL, 0x0F); + + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_write(codec, SITAR_A_CDC_CLK_ANC_RESET_CTL, 0xFF); + snd_soc_write(codec, SITAR_A_CDC_CLK_ANC_CLK_EN_CTL, 0x00); + break; + } + return 0; +} + + +static void sitar_codec_start_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + int mbhc_state = sitar->mbhc_state; + + pr_debug("%s: enter\n", __func__); + if (!sitar->mbhc_polling_active) { + pr_debug("Polling is not active, do not start polling\n"); + return; + } + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + + + if (!sitar->no_mic_headset_override) { + if (mbhc_state == MBHC_STATE_POTENTIAL) { + pr_debug("%s recovering MBHC state macine\n", __func__); + sitar->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY; + /* set to max button press threshold */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B2_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL, + 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL, + 0xFF); + /* set to max */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B6_CTL, + 0x7F); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B5_CTL, + 0xFF); + } + } + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x1); +} + +static void sitar_codec_pause_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + if (!sitar->mbhc_polling_active) { + pr_debug("polling not active, nothing to pause\n"); + return; + } + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + pr_debug("%s: leave\n", __func__); + +} + +static void sitar_codec_switch_cfilt_mode(struct snd_soc_codec *codec, + int mode) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 reg_mode_val, cur_mode_val; + bool mbhc_was_polling = false; + + if (mode) + reg_mode_val = SITAR_CFILT_FAST_MODE; + else + reg_mode_val = SITAR_CFILT_SLOW_MODE; + + cur_mode_val = snd_soc_read(codec, + sitar->mbhc_bias_regs.cfilt_ctl) & 0x40; + + if (cur_mode_val != reg_mode_val) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + if (sitar->mbhc_polling_active) { + sitar_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val); + if (mbhc_was_polling) + sitar_codec_start_hs_polling(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_debug("%s: CFILT mode change (%x to %x)\n", __func__, + cur_mode_val, reg_mode_val); + } else { + pr_err("%s: CFILT Value is already %x\n", + __func__, cur_mode_val); + } +} + +static void sitar_codec_update_cfilt_usage(struct snd_soc_codec *codec, + u8 cfilt_sel, int inc) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u32 *cfilt_cnt_ptr = NULL; + u16 micb_cfilt_reg; + + switch (cfilt_sel) { + case SITAR_CFILT1_SEL: + cfilt_cnt_ptr = &sitar->cfilt1_cnt; + micb_cfilt_reg = SITAR_A_MICB_CFILT_1_CTL; + break; + case SITAR_CFILT2_SEL: + cfilt_cnt_ptr = &sitar->cfilt2_cnt; + micb_cfilt_reg = SITAR_A_MICB_CFILT_2_CTL; + break; + default: + return; /* should not happen */ + } + + if (inc) { + if (!(*cfilt_cnt_ptr)++) { + /* Switch CFILT to slow mode if MBHC CFILT being used */ + if (cfilt_sel == sitar->mbhc_bias_regs.cfilt_sel) + sitar_codec_switch_cfilt_mode(codec, 0); + + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0x80); + } + } else { + /* check if count not zero, decrement + * then check if zero, go ahead disable cfilter + */ + if ((*cfilt_cnt_ptr) && !--(*cfilt_cnt_ptr)) { + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0); + + /* Switch CFILT to fast mode if MBHC CFILT being used */ + if (cfilt_sel == sitar->mbhc_bias_regs.cfilt_sel) + sitar_codec_switch_cfilt_mode(codec, 1); + } + } +} + +static int sitar_find_k_value(unsigned int ldoh_v, unsigned int cfilt_mv) +{ + int rc = -EINVAL; + unsigned min_mv, max_mv; + + switch (ldoh_v) { + case SITAR_LDOH_1P95_V: + min_mv = 160; + max_mv = 1800; + break; + case SITAR_LDOH_2P35_V: + min_mv = 200; + max_mv = 2200; + break; + case SITAR_LDOH_2P75_V: + min_mv = 240; + max_mv = 2600; + break; + case SITAR_LDOH_2P85_V: + min_mv = 250; + max_mv = 2700; + break; + default: + goto done; + } + + if (cfilt_mv < min_mv || cfilt_mv > max_mv) + goto done; + + for (rc = 4; rc <= 44; rc++) { + min_mv = max_mv * (rc) / 44; + if (min_mv >= cfilt_mv) { + rc -= 4; + break; + } + } +done: + return rc; +} + +static bool sitar_is_hph_pa_on(struct snd_soc_codec *codec) +{ + u8 hph_reg_val = 0; + hph_reg_val = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_EN); + + return (hph_reg_val & 0x30) ? true : false; +} + +static bool sitar_is_hph_dac_on(struct snd_soc_codec *codec, int left) +{ + u8 hph_reg_val = 0; + if (left) + hph_reg_val = snd_soc_read(codec, + SITAR_A_RX_HPH_L_DAC_CTL); + else + hph_reg_val = snd_soc_read(codec, + SITAR_A_RX_HPH_R_DAC_CTL); + + return (hph_reg_val & 0xC0) ? true : false; +} + +static void sitar_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + int cfilt_k_val; + bool mbhc_was_polling = false; + + switch (vddio_switch) { + case 1: + if (sitar->mbhc_micbias_switched == 0 && + sitar->mbhc_polling_active) { + + sitar_codec_pause_hs_polling(codec); + /* Enable Mic Bias switch to VDDIO */ + sitar->cfilt_k_value = snd_soc_read(codec, + sitar->mbhc_bias_regs.cfilt_val); + cfilt_k_val = sitar_find_k_value( + sitar->pdata->micbias.ldoh_v, 1800); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x80); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x10, 0x00); + sitar_codec_start_hs_polling(codec); + + sitar->mbhc_micbias_switched = true; + pr_debug("%s: Enabled MBHC Mic bias to VDDIO Switch\n", + __func__); + } + break; + + case 0: + if (sitar->mbhc_micbias_switched) { + if (sitar->mbhc_polling_active) { + sitar_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + /* Disable Mic Bias switch to VDDIO */ + if (sitar->cfilt_k_value != 0) + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.cfilt_val, 0XFC, + sitar->cfilt_k_value); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + snd_soc_update_bits(codec, + sitar->mbhc_bias_regs.mbhc_reg, 0x10, 0x00); + + if (mbhc_was_polling) + sitar_codec_start_hs_polling(codec); + + sitar->mbhc_micbias_switched = false; + pr_debug("%s: Disabled MBHC Mic bias to VDDIO Switch\n", + __func__); + } + break; + } +} + +static int sitar_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u16 micb_int_reg; + int micb_line; + u8 cfilt_sel_val = 0; + char *internal1_text = "Internal1"; + char *internal2_text = "Internal2"; + + pr_debug("%s %d\n", __func__, event); + switch (w->reg) { + case SITAR_A_MICB_1_CTL: + micb_int_reg = SITAR_A_MICB_1_INT_RBIAS; + cfilt_sel_val = sitar->pdata->micbias.bias1_cfilt_sel; + micb_line = SITAR_MICBIAS1; + break; + case SITAR_A_MICB_2_CTL: + micb_int_reg = SITAR_A_MICB_2_INT_RBIAS; + cfilt_sel_val = sitar->pdata->micbias.bias2_cfilt_sel; + micb_line = SITAR_MICBIAS2; + break; + default: + pr_err("%s: Error, invalid micbias register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Decide whether to switch the micbias for MBHC */ + if (w->reg == sitar->mbhc_bias_regs.ctl_reg) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 0); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + + snd_soc_update_bits(codec, w->reg, 0x1E, 0x00); + sitar_codec_update_cfilt_usage(codec, cfilt_sel_val, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0xFF, 0xA4); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x1C, 0x1C); + break; + case SND_SOC_DAPM_POST_PMU: + if (sitar->mbhc_polling_active && + sitar->mbhc_cfg.micbias == micb_line) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_pause_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + break; + case SND_SOC_DAPM_POST_PMD: + + if ((w->reg == sitar->mbhc_bias_regs.ctl_reg) + && sitar_is_hph_pa_on(codec)) + sitar_codec_switch_micbias(codec, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x80, 0x00); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x10, 0x00); + sitar_codec_update_cfilt_usage(codec, cfilt_sel_val, 0); + break; + } + + return 0; +} + +static int sitar_codec_enable_dec(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 dec_reset_reg; + + pr_debug("%s %d\n", __func__, event); + + if (w->reg == SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL) + dec_reset_reg = SITAR_A_CDC_CLK_TX_RESET_B1_CTL; + else { + pr_err("%s: Error, incorrect dec\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, + 1 << w->shift); + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, 0x0); + break; + } + return 0; +} + +static int sitar_codec_reset_interpolator(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 1 << w->shift); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 0x0); + break; + } + return 0; +} + +static int sitar_codec_enable_ldo_h(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + usleep_range(1000, 1000); + pr_debug("LDO_H\n"); + break; + } + return 0; +} + +static void sitar_enable_rx_bias(struct snd_soc_codec *codec, u32 enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (enable) { + sitar->rx_bias_count++; + if (sitar->rx_bias_count == 1) + snd_soc_update_bits(codec, SITAR_A_RX_COM_BIAS, + 0x80, 0x80); + } else { + sitar->rx_bias_count--; + if (!sitar->rx_bias_count) + snd_soc_update_bits(codec, SITAR_A_RX_COM_BIAS, + 0x80, 0x00); + } +} + +static int sitar_codec_enable_rx_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + sitar_enable_rx_bias(codec, 1); + break; + case SND_SOC_DAPM_POST_PMD: + sitar_enable_rx_bias(codec, 0); + break; + } + return 0; +} +static int sitar_hph_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static void sitar_snd_soc_jack_report(struct sitar_priv *sitar, + struct snd_soc_jack *jack, int status, + int mask) +{ + /* XXX: wake_lock_timeout()? */ + snd_soc_jack_report(jack, status, mask); +} + +static void hphocp_off_report(struct sitar_priv *sitar, + u32 jack_status, int irq) +{ + struct snd_soc_codec *codec; + + if (!sitar) { + pr_err("%s: Bad sitar private data\n", __func__); + return; + } + + pr_info("%s: clear ocp status %x\n", __func__, jack_status); + codec = sitar->codec; + if (sitar->hph_status & jack_status) { + sitar->hph_status &= ~jack_status; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, 0x10); + /* reset retry counter as PA is turned off signifying + * start of new OCP detection session + */ + if (SITAR_IRQ_HPH_PA_OCPL_FAULT) + sitar->hphlocp_cnt = 0; + else + sitar->hphrocp_cnt = 0; + wcd9xxx_enable_irq(codec->control_data, irq); + } +} + +static void hphlocp_off_report(struct work_struct *work) +{ + struct sitar_priv *sitar = container_of(work, struct sitar_priv, + hphlocp_work); + hphocp_off_report(sitar, SND_JACK_OC_HPHL, SITAR_IRQ_HPH_PA_OCPL_FAULT); +} + +static void hphrocp_off_report(struct work_struct *work) +{ + struct sitar_priv *sitar = container_of(work, struct sitar_priv, + hphrocp_work); + hphocp_off_report(sitar, SND_JACK_OC_HPHR, SITAR_IRQ_HPH_PA_OCPR_FAULT); +} + +static int sitar_hph_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 mbhc_micb_ctl_val; + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mbhc_micb_ctl_val = snd_soc_read(codec, + sitar->mbhc_bias_regs.ctl_reg); + + if (!(mbhc_micb_ctl_val & 0x80)) { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 1); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + } + + break; + + case SND_SOC_DAPM_POST_PMD: + /* schedule work is required because at the time HPH PA DAPM + * event callback is called by DAPM framework, CODEC dapm mutex + * would have been locked while snd_soc_jack_report also + * attempts to acquire same lock. + */ + if (w->shift == 5) { + clear_bit(SITAR_HPHL_PA_OFF_ACK, + &sitar->hph_pa_dac_state); + clear_bit(SITAR_HPHL_DAC_OFF_ACK, + &sitar->hph_pa_dac_state); + if (sitar->hph_status & SND_JACK_OC_HPHL) + schedule_work(&sitar->hphlocp_work); + } else if (w->shift == 4) { + clear_bit(SITAR_HPHR_PA_OFF_ACK, + &sitar->hph_pa_dac_state); + clear_bit(SITAR_HPHR_DAC_OFF_ACK, + &sitar->hph_pa_dac_state); + if (sitar->hph_status & SND_JACK_OC_HPHR) + schedule_work(&sitar->hphrocp_work); + } + + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_switch_micbias(codec, 0); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + + pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__, + w->name); + usleep_range(10000, 10000); + + break; + } + return 0; +} + +static void sitar_get_mbhc_micbias_regs(struct snd_soc_codec *codec, + struct mbhc_micbias_regs *micbias_regs) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + unsigned int cfilt; + + switch (sitar->mbhc_cfg.micbias) { + case SITAR_MICBIAS1: + cfilt = sitar->pdata->micbias.bias1_cfilt_sel; + micbias_regs->mbhc_reg = SITAR_A_MICB_1_MBHC; + micbias_regs->int_rbias = SITAR_A_MICB_1_INT_RBIAS; + micbias_regs->ctl_reg = SITAR_A_MICB_1_CTL; + break; + case SITAR_MICBIAS2: + cfilt = sitar->pdata->micbias.bias2_cfilt_sel; + micbias_regs->mbhc_reg = SITAR_A_MICB_2_MBHC; + micbias_regs->int_rbias = SITAR_A_MICB_2_INT_RBIAS; + micbias_regs->ctl_reg = SITAR_A_MICB_2_CTL; + break; + default: + /* Should never reach here */ + pr_err("%s: Invalid MIC BIAS for MBHC\n", __func__); + return; + } + + micbias_regs->cfilt_sel = cfilt; + + switch (cfilt) { + case SITAR_CFILT1_SEL: + micbias_regs->cfilt_val = SITAR_A_MICB_CFILT_1_VAL; + micbias_regs->cfilt_ctl = SITAR_A_MICB_CFILT_1_CTL; + sitar->mbhc_data.micb_mv = sitar->pdata->micbias.cfilt1_mv; + break; + case SITAR_CFILT2_SEL: + micbias_regs->cfilt_val = SITAR_A_MICB_CFILT_2_VAL; + micbias_regs->cfilt_ctl = SITAR_A_MICB_CFILT_2_CTL; + sitar->mbhc_data.micb_mv = sitar->pdata->micbias.cfilt2_mv; + break; + } +} + +static int sitar_codec_enable_charge_pump(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_CTL, 0x01, + 0x01); + snd_soc_update_bits(codec, SITAR_A_CDC_CLSG_CTL, 0x08, 0x08); + usleep_range(200, 200); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x10, 0x00); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x10); + usleep_range(20, 20); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x08, 0x08); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x10, 0x10); + snd_soc_update_bits(codec, SITAR_A_CDC_CLSG_CTL, 0x08, 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_OTHR_CTL, 0x01, + 0x00); + snd_soc_update_bits(codec, SITAR_A_CP_STATIC, 0x08, 0x00); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget sitar_dapm_i2s_widgets[] = { + SND_SOC_DAPM_SUPPLY("RX_I2S_CLK", SITAR_A_CDC_CLK_RX_I2S_CTL, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TX_I2S_CLK", SITAR_A_CDC_CLK_TX_I2S_CTL, 4, + 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget sitar_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_PGA("EAR PA", SITAR_A_RX_EAR_EN, 4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("DAC1", SITAR_A_RX_EAR_EN, 6, 0, dac1_switch, + ARRAY_SIZE(dac1_switch)), + SND_SOC_DAPM_SUPPLY("EAR DRIVER", SITAR_A_RX_EAR_EN, 3, 0, NULL, 0), + SND_SOC_DAPM_AIF_IN_E("SLIM RX1", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX2", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN("SLIM RX3", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM RX4", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM RX5", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Headphone */ + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + SND_SOC_DAPM_PGA_E("HPHL", SITAR_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, + sitar_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("HPHR", SITAR_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, + sitar_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("HPHL DAC", NULL, SITAR_A_RX_HPH_L_DAC_CTL, 7, 0, + sitar_hph_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("HPHR DAC", NULL, SITAR_A_RX_HPH_R_DAC_CTL, 7, 0, + sitar_hph_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + + SND_SOC_DAPM_DAC_E("LINEOUT1 DAC", NULL, SITAR_A_RX_LINE_1_DAC_CTL, 7, 0 + , sitar_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT2 DAC", NULL, SITAR_A_RX_LINE_2_DAC_CTL, 7, 0 + , sitar_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("LINEOUT1 PA", SITAR_A_RX_LINE_CNP_EN, 0, 0, NULL, + 0, sitar_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT2 PA", SITAR_A_RX_LINE_CNP_EN, 1, 0, NULL, + 0, sitar_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("RX1 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 0, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER_E("RX2 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 1, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER_E("RX3 MIX1", SITAR_A_CDC_CLK_RX_B1_CTL, 2, 0, NULL, + 0, sitar_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX("DAC1 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac1_mux), + SND_SOC_DAPM_MUX("DAC2 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac2_mux), + SND_SOC_DAPM_MUX("DAC3 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac3_mux), + SND_SOC_DAPM_MUX("DAC4 MUX", SND_SOC_NOPM, 0, 0, + &rx_dac4_mux), + + SND_SOC_DAPM_MIXER("RX1 CHAIN", SITAR_A_CDC_RX1_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 CHAIN", SITAR_A_CDC_RX2_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX3 CHAIN", SITAR_A_CDC_RX3_B6_CTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MUX("RX1 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp2_mux), + + SND_SOC_DAPM_SUPPLY("CP", SITAR_A_CP_EN, 0, 0, + sitar_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("RX_BIAS", SND_SOC_NOPM, 0, 0, + sitar_codec_enable_rx_bias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("LDO_H", SITAR_A_LDO_H_MODE_1, 7, 0, + sitar_codec_enable_ldo_h, SND_SOC_DAPM_POST_PMU), + /* TX */ + + SND_SOC_DAPM_SUPPLY("CDC_CONN", SITAR_A_CDC_CLK_OTHR_CTL, 2, 0, NULL, + 0), + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("AMIC3"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 External", SITAR_A_MICB_1_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal1", SITAR_A_MICB_1_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 External", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal1", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal2", SITAR_A_MICB_2_CTL, 7, 0, + sitar_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("ADC1", NULL, SITAR_A_TX_1_2_EN, 7, 0, + sitar_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2", NULL, SITAR_A_TX_1_2_EN, 3, 0, + sitar_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC3", NULL, SITAR_A_TX_3_EN, 7, 0, + sitar_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC1 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0, 0, + &dec1_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC2 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 1, 0, + &dec2_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC3 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 2, 0, + &dec3_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("DEC4 MUX", SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 3, 0, + &dec4_mux, sitar_codec_enable_dec, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX("ANC1 MUX", SND_SOC_NOPM, 0, 0, &anc1_mux), + SND_SOC_DAPM_MUX("ANC2 MUX", SND_SOC_NOPM, 0, 0, &anc2_mux), + + SND_SOC_DAPM_MIXER_E("ANC", SND_SOC_NOPM, 0, 0, NULL, 0, + sitar_codec_enable_anc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("ANC1 FB MUX", SND_SOC_NOPM, 0, 0, &anc1_fb_mux), + + SND_SOC_DAPM_MUX("SLIM TX1 MUX", SND_SOC_NOPM, 0, 0, &sb_tx1_mux), + SND_SOC_DAPM_MUX("SLIM TX2 MUX", SND_SOC_NOPM, 0, 0, &sb_tx2_mux), + SND_SOC_DAPM_MUX("SLIM TX3 MUX", SND_SOC_NOPM, 0, 0, &sb_tx3_mux), + SND_SOC_DAPM_MUX("SLIM TX4 MUX", SND_SOC_NOPM, 0, 0, &sb_tx4_mux), + SND_SOC_DAPM_MUX("SLIM TX5 MUX", SND_SOC_NOPM, 0, 0, &sb_tx5_mux), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX1", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX2", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX3", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX4", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("SLIM TX5", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, sitar_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Digital Mic Inputs */ + SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC3", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC4", NULL, SND_SOC_NOPM, 0, 0, + sitar_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Sidetone */ + SND_SOC_DAPM_MUX("IIR1 INP1 MUX", SND_SOC_NOPM, 0, 0, &iir1_inp1_mux), + SND_SOC_DAPM_PGA("IIR1", SITAR_A_CDC_CLK_SD_CTL, 0, 0, NULL, 0), + +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Earpiece (RX MIX1) */ + {"EAR", NULL, "EAR PA"}, + {"EAR PA", "NULL", "DAC1"}, + {"DAC1", "Switch", "DAC1 MUX"}, + {"DAC1", NULL, "CP"}, + {"DAC1", NULL, "EAR DRIVER"}, + + {"CP", NULL, "RX_BIAS"}, + + {"LINEOUT1 DAC", NULL, "RX_BIAS"}, + {"LINEOUT2 DAC", NULL, "RX_BIAS"}, + + {"LINEOUT2", NULL, "LINEOUT2 PA"}, + {"LINEOUT2 PA", NULL, "LINEOUT2 DAC"}, + {"LINEOUT2 DAC", NULL, "DAC3 MUX"}, + + {"LINEOUT1", NULL, "LINEOUT1 PA"}, + {"LINEOUT1 PA", NULL, "LINEOUT1 DAC"}, + {"LINEOUT1 DAC", NULL, "DAC2 MUX"}, + + {"ANC1 FB MUX", "EAR_HPH_L", "RX2 MIX1"}, + {"ANC1 FB MUX", "EAR_LINE_1", "RX3 MIX1"}, + {"ANC", NULL, "ANC1 FB MUX"}, + + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL"}, + {"HEADPHONE", NULL, "HPHR"}, + + {"HPHL DAC", NULL, "CP"}, + {"HPHR DAC", NULL, "CP"}, + + {"HPHL", NULL, "HPHL DAC"}, + {"HPHL DAC", "NULL", "DAC4 MUX"}, + {"HPHR", NULL, "HPHR DAC"}, + {"HPHR DAC", NULL, "RX3 MIX1"}, + + {"DAC1 MUX", "RX1", "RX1 CHAIN"}, + {"DAC2 MUX", "RX1", "RX1 CHAIN"}, + + {"DAC3 MUX", "RX1", "RX1 CHAIN"}, + {"DAC3 MUX", "INV_RX1", "RX1 CHAIN"}, + {"DAC3 MUX", "RX2", "RX2 MIX1"}, + + {"DAC4 MUX", "ON", "RX2 MIX1"}, + + {"RX1 CHAIN", NULL, "RX1 MIX1"}, + + {"RX1 MIX1", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP2"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP2"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP1"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP2"}, + + /* ANC */ + {"ANC", NULL, "ANC1 MUX"}, + {"ANC", NULL, "ANC2 MUX"}, + {"ANC1 MUX", "ADC1", "ADC1"}, + {"ANC1 MUX", "ADC2", "ADC2"}, + {"ANC1 MUX", "ADC3", "ADC3"}, + {"ANC2 MUX", "ADC1", "ADC1"}, + {"ANC2 MUX", "ADC2", "ADC2"}, + {"ANC2 MUX", "ADC3", "ADC3"}, + + {"ANC", NULL, "CDC_CONN"}, + + {"RX2 MIX1", NULL, "ANC"}, + {"RX3 MIX1", NULL, "ANC"}, + + /* SLIMBUS Connections */ + + /* Slimbus port 5 is non functional in Sitar 1.0 */ + {"RX1 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP1", "IIR1", "IIR1"}, + {"RX1 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP2", "IIR1", "IIR1"}, + {"RX2 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP1", "IIR1", "IIR1"}, + {"RX2 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP2", "IIR1", "IIR1"}, + {"RX3 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP1", "IIR1", "IIR1"}, + {"RX3 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP2", "IIR1", "IIR1"}, + + + /* TX */ + {"SLIM TX1", NULL, "SLIM TX1 MUX"}, + {"SLIM TX2", NULL, "SLIM TX2 MUX"}, + {"SLIM TX3", NULL, "SLIM TX3 MUX"}, + {"SLIM TX4", NULL, "SLIM TX4 MUX"}, + {"SLIM TX5", NULL, "SLIM TX5 MUX"}, + + {"SLIM TX1 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX2 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX3 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX4 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX5 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX5 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX5 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX5 MUX", "DEC4", "DEC4 MUX"}, + + /* Decimator Inputs */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC1 MUX", "DMIC4", "DMIC4"}, + {"DEC1 MUX", "ADC1", "ADC1"}, + {"DEC1 MUX", "ADC2", "ADC2"}, + {"DEC1 MUX", "ADC3", "ADC3"}, + {"DEC1 MUX", NULL, "CDC_CONN"}, + {"DEC2 MUX", "DMIC2", "DMIC2"}, + {"DEC2 MUX", "DMIC3", "DMIC3"}, + {"DEC2 MUX", "ADC1", "ADC1"}, + {"DEC2 MUX", "ADC2", "ADC2"}, + {"DEC2 MUX", "ADC3", "ADC3"}, + {"DEC2 MUX", NULL, "CDC_CONN"}, + {"DEC3 MUX", "DMIC3", "DMIC3"}, + {"DEC3 MUX", "ADC1", "ADC1"}, + {"DEC3 MUX", "ADC2", "ADC2"}, + {"DEC3 MUX", "ADC3", "ADC3"}, + {"DEC3 MUX", "DMIC2", "DMIC2"}, + {"DEC3 MUX", "DMIC4", "DMIC4"}, + {"DEC3 MUX", NULL, "CDC_CONN"}, + {"DEC4 MUX", "DMIC4", "DMIC4"}, + {"DEC4 MUX", "ADC1", "ADC1"}, + {"DEC4 MUX", "ADC2", "ADC2"}, + {"DEC4 MUX", "ADC3", "ADC3"}, + {"DEC4 MUX", "DMIC3", "DMIC3"}, + {"DEC4 MUX", "DMIC2", "DMIC2"}, + {"DEC4 MUX", "DMIC1", "DMIC1"}, + {"DEC4 MUX", NULL, "CDC_CONN"}, + + /* ADC Connections */ + {"ADC1", NULL, "AMIC1"}, + {"ADC2", NULL, "AMIC2"}, + {"ADC3", NULL, "AMIC3"}, + + /* IIR */ + {"IIR1", NULL, "IIR1 INP1 MUX"}, + {"IIR1 INP1 MUX", "DEC1", "DEC1 MUX"}, + {"MIC BIAS1 Internal1", NULL, "LDO_H"}, + {"MIC BIAS1 External", NULL, "LDO_H"}, + {"MIC BIAS2 Internal1", NULL, "LDO_H"}, + {"MIC BIAS2 External", NULL, "LDO_H"}, +}; + +static int sitar_readable(struct snd_soc_codec *ssc, unsigned int reg) +{ + return sitar_reg_readable[reg]; +} + +static int sitar_volatile(struct snd_soc_codec *ssc, unsigned int reg) +{ + /* Registers lower than 0x100 are top level registers which can be + * written by the Sitar core driver. + */ + + if ((reg >= SITAR_A_CDC_MBHC_EN_CTL) || (reg < 0x100)) + return 1; + + /* IIR Coeff registers are not cacheable */ + if ((reg >= SITAR_A_CDC_IIR1_COEF_B1_CTL) && + (reg <= SITAR_A_CDC_IIR1_COEF_B5_CTL)) + return 1; + + return 0; +} + +#define SITAR_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +static int sitar_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + + BUG_ON(reg > SITAR_MAX_REGISTER); + + if (!sitar_volatile(codec, reg)) { + ret = snd_soc_cache_write(codec, reg, value); + if (ret != 0) + dev_err(codec->dev, "Cache write to %x failed: %d\n", + reg, ret); + } + + return wcd9xxx_reg_write(codec->control_data, reg, value); +} +static unsigned int sitar_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + BUG_ON(reg > SITAR_MAX_REGISTER); + + if (!sitar_volatile(codec, reg) && sitar_readable(codec, reg) && + reg < codec->driver->reg_cache_size) { + ret = snd_soc_cache_read(codec, reg, &val); + if (ret >= 0) { + return val; + } else + dev_err(codec->dev, "Cache read from %x failed: %d\n", + reg, ret); + } + + val = wcd9xxx_reg_read(codec->control_data, reg); + return val; +} + +static void sitar_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec) +{ + struct wcd9xxx *sitar_core = dev_get_drvdata(codec->dev->parent); + + if (SITAR_IS_1P0(sitar_core->version)) + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x80, 0x80); + + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x08); + usleep_range(1000, 1000); + snd_soc_write(codec, SITAR_A_BIAS_REF_CTL, 0x1C); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x04, + 0x04); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x01); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); +} + +static void sitar_codec_enable_bandgap(struct snd_soc_codec *codec, + enum sitar_bandgap_type choice) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct wcd9xxx *sitar_core = dev_get_drvdata(codec->dev->parent); + + /* TODO lock resources accessed by audio streams and threaded + * interrupt handlers + */ + + pr_debug("%s, choice is %d, current is %d\n", __func__, choice, + sitar->bandgap_type); + + if (sitar->bandgap_type == choice) + return; + + if ((sitar->bandgap_type == SITAR_BANDGAP_OFF) && + (choice == SITAR_BANDGAP_AUDIO_MODE)) { + sitar_codec_enable_audio_mode_bandgap(codec); + } else if (choice == SITAR_BANDGAP_MBHC_MODE) { + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x08); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x2, + 0x2); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x4, + 0x4); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x1); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); + } else if ((sitar->bandgap_type == SITAR_BANDGAP_MBHC_MODE) && + (choice == SITAR_BANDGAP_AUDIO_MODE)) { + snd_soc_write(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x50); + usleep_range(100, 100); + sitar_codec_enable_audio_mode_bandgap(codec); + } else if (choice == SITAR_BANDGAP_OFF) { + snd_soc_update_bits(codec, SITAR_A_BIAS_CURR_CTL_2, 0x0C, 0x00); + snd_soc_write(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x50); + if (SITAR_IS_1P0(sitar_core->version)) + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, + 0xFF, 0x65); + usleep_range(1000, 1000); + } else { + pr_err("%s: Error, Invalid bandgap settings\n", __func__); + } + sitar->bandgap_type = choice; +} + +static int sitar_codec_enable_config_mode(struct snd_soc_codec *codec, + int enable) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (enable) { + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x10, 0); + snd_soc_write(codec, SITAR_A_BIAS_OSC_BG_CTL, 0x17); + usleep_range(5, 5); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x80, + 0x80); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_TEST, 0x80, + 0x80); + usleep_range(10, 10); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_TEST, 0x80, 0); + usleep_range(20, 20); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x08); + } else { + snd_soc_update_bits(codec, SITAR_A_BIAS_OSC_BG_CTL, 0x1, + 0); + snd_soc_update_bits(codec, SITAR_A_RC_OSC_FREQ, 0x80, 0); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x00); + } + sitar->config_mode_active = enable ? true : false; + + return 0; +} + +static int sitar_codec_enable_clock_block(struct snd_soc_codec *codec, + int config_mode) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s\n", __func__); + + if (config_mode) { + sitar_codec_enable_config_mode(codec, 1); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x00); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x02); + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN1, 0x0D); + usleep_range(1000, 1000); + } else + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x08, 0x00); + + if (!config_mode && sitar->mbhc_polling_active) { + snd_soc_write(codec, SITAR_A_CLK_BUFF_EN2, 0x02); + sitar_codec_enable_config_mode(codec, 0); + + } + + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x05); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x02, 0x00); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x04, 0x04); + usleep_range(50, 50); + sitar->clock_active = true; + return 0; +} +static void sitar_codec_disable_clock_block(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + pr_debug("%s\n", __func__); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x04, 0x00); + ndelay(160); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN2, 0x02, 0x02); + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x00); + sitar->clock_active = false; +} + +static int sitar_codec_mclk_index(const struct sitar_priv *sitar) +{ + if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ) + return 0; + else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ) + return 1; + else { + BUG_ON(1); + return -EINVAL; + } +} + +static void sitar_codec_calibrate_hs_polling(struct snd_soc_codec *codec) +{ + u8 *n_ready, *n_cic; + struct sitar_mbhc_btn_detect_cfg *btn_det; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B1_CTL, + sitar->mbhc_data.v_ins_hu & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B2_CTL, + (sitar->mbhc_data.v_ins_hu >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B3_CTL, + sitar->mbhc_data.v_b1_hu & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B4_CTL, + (sitar->mbhc_data.v_b1_hu >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B5_CTL, + sitar->mbhc_data.v_b1_h & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B6_CTL, + (sitar->mbhc_data.v_b1_h >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B9_CTL, + sitar->mbhc_data.v_brh & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B10_CTL, + (sitar->mbhc_data.v_brh >> 8) & 0xFF); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B11_CTL, + sitar->mbhc_data.v_brl & 0xFF); + snd_soc_write(codec, SITAR_A_CDC_MBHC_VOLT_B12_CTL, + (sitar->mbhc_data.v_brl >> 8) & 0xFF); + + n_ready = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_READY); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B1_CTL, + n_ready[sitar_codec_mclk_index(sitar)]); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B2_CTL, + sitar->mbhc_data.npoll); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B3_CTL, + sitar->mbhc_data.nbounce_wait); + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B6_CTL, + n_cic[sitar_codec_mclk_index(sitar)]); +} + +static int sitar_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(dai->codec->dev->parent); + if ((wcd9xxx != NULL) && (wcd9xxx->dev != NULL) && + (wcd9xxx->dev->parent != NULL)) + pm_runtime_get_sync(wcd9xxx->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + + return 0; +} + +static void sitar_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(dai->codec->dev->parent); + if ((wcd9xxx != NULL) && (wcd9xxx->dev != NULL) && + (wcd9xxx->dev->parent != NULL)) { + pm_runtime_mark_last_busy(wcd9xxx->dev->parent); + pm_runtime_put(wcd9xxx->dev->parent); + } + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); +} + +int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, bool dapm) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s() mclk_enable = %u\n", __func__, mclk_enable); + + if (dapm) + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + if (mclk_enable) { + sitar->mclk_enabled = true; + + if (sitar->mbhc_polling_active && (sitar->mclk_enabled)) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_AUDIO_MODE); + sitar_codec_enable_clock_block(codec, 0); + sitar_codec_calibrate_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + } else { + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_AUDIO_MODE); + sitar_codec_enable_clock_block(codec, 0); + } + } else { + + if (!sitar->mclk_enabled) { + if (dapm) + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_err("Error, MCLK already diabled\n"); + return -EINVAL; + } + sitar->mclk_enabled = false; + + if (sitar->mbhc_polling_active) { + if (!sitar->mclk_enabled) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_MBHC_MODE); + sitar_enable_rx_bias(codec, 1); + sitar_codec_enable_clock_block(codec, 1); + sitar_codec_calibrate_hs_polling(codec); + sitar_codec_start_hs_polling(codec); + } + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, + 0x05, 0x01); + } else { + sitar_codec_disable_clock_block(codec); + sitar_codec_enable_bandgap(codec, + SITAR_BANDGAP_OFF); + } + } + if (dapm) + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + return 0; +} + +static int sitar_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int sitar_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + u8 val = 0; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + + pr_debug("%s\n", __func__); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + SITAR_I2S_MASTER_MODE_MASK, 0); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + SITAR_I2S_MASTER_MODE_MASK, 0); + } + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + val = SITAR_I2S_MASTER_MODE_MASK; + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, val, val); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, val, val); + } + break; + default: + return -EINVAL; + } + return 0; +} +static int sitar_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + u32 i = 0; + if (!tx_slot && !rx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s: DAI-ID %x %d %d\n", __func__, dai->id, tx_num, rx_num); + + if (dai->id == AIF1_PB) { + for (i = 0; i < rx_num; i++) { + sitar->dai[dai->id - 1].ch_num[i] = rx_slot[i]; + sitar->dai[dai->id - 1].ch_act = 0; + sitar->dai[dai->id - 1].ch_tot = rx_num; + } + } else if (dai->id == AIF1_CAP) { + for (i = 0; i < tx_num; i++) { + sitar->dai[dai->id - 1].ch_num[i] = tx_slot[i]; + sitar->dai[dai->id - 1].ch_act = 0; + sitar->dai[dai->id - 1].ch_tot = tx_num; + } + } + return 0; +} + +static int sitar_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) + +{ + struct wcd9xxx *sitar = dev_get_drvdata(dai->codec->control_data); + + u32 cnt = 0; + u32 tx_ch[SLIM_MAX_TX_PORTS]; + u32 rx_ch[SLIM_MAX_RX_PORTS]; + + if (!rx_slot && !tx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s: DAI-ID %x\n", __func__, dai->id); + /* for virtual port, codec driver needs to do + * housekeeping, for now should be ok + */ + wcd9xxx_get_channel(sitar, rx_ch, tx_ch); + if (dai->id == AIF1_PB) { + *rx_num = sitar_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[cnt]; + cnt++; + } + } else if (dai->id == AIF1_CAP) { + *tx_num = sitar_dai[dai->id - 1].capture.channels_max; + tx_slot[0] = tx_ch[cnt]; + tx_slot[1] = tx_ch[4 + cnt]; + tx_slot[2] = tx_ch[2 + cnt]; + tx_slot[3] = tx_ch[3 + cnt]; + } + return 0; +} + +static int sitar_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(dai->codec); + u8 path, shift; + u16 tx_fs_reg, rx_fs_reg; + u8 tx_fs_rate, rx_fs_rate, rx_state, tx_state; + + pr_debug("%s: DAI-ID %x\n", __func__, dai->id); + + switch (params_rate(params)) { + case 8000: + tx_fs_rate = 0x00; + rx_fs_rate = 0x00; + break; + case 16000: + tx_fs_rate = 0x01; + rx_fs_rate = 0x20; + break; + case 32000: + tx_fs_rate = 0x02; + rx_fs_rate = 0x40; + break; + case 48000: + tx_fs_rate = 0x03; + rx_fs_rate = 0x60; + break; + default: + pr_err("%s: Invalid sampling rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + + /** + * If current dai is a tx dai, set sample rate to + * all the txfe paths that are currently not active + */ + if (dai->id == AIF1_CAP) { + + tx_state = snd_soc_read(codec, + SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_DECIMATORS; path++, shift++) { + + if (!(tx_state & (1 << shift))) { + tx_fs_reg = SITAR_A_CDC_TX1_CLK_FS_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, tx_fs_reg, + 0x03, tx_fs_rate); + } + } + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_TX_I2S_CTL, + 0x03, tx_fs_rate); + } + } else { + sitar->dai[dai->id - 1].rate = params_rate(params); + } + + /** + * TODO: Need to handle case where same RX chain takes 2 or more inputs + * with varying sample rates + */ + + /** + * If current dai is a rx dai, set sample rate to + * all the rx paths that are currently not active + */ + if (dai->id == AIF1_PB) { + + rx_state = snd_soc_read(codec, + SITAR_A_CDC_CLK_RX_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_INTERPOLATORS; path++, shift++) { + + if (!(rx_state & (1 << shift))) { + rx_fs_reg = SITAR_A_CDC_RX1_B5_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, rx_fs_reg, + 0xE0, rx_fs_rate); + } + } + if (sitar->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, SITAR_A_CDC_CLK_RX_I2S_CTL, + 0x03, (rx_fs_rate >> 0x05)); + } + } else { + sitar->dai[dai->id - 1].rate = params_rate(params); + } + + return 0; +} + +static struct snd_soc_dai_ops sitar_dai_ops = { + .startup = sitar_startup, + .shutdown = sitar_shutdown, + .hw_params = sitar_hw_params, + .set_sysclk = sitar_set_dai_sysclk, + .set_fmt = sitar_set_dai_fmt, + .set_channel_map = sitar_set_channel_map, + .get_channel_map = sitar_get_channel_map, +}; + +static struct snd_soc_dai_driver sitar_dai[] = { + { + .name = "sitar_rx1", + .id = AIF1_PB, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9304_RATES, + .formats = SITAR_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &sitar_dai_ops, + }, + { + .name = "sitar_tx1", + .id = AIF1_CAP, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9304_RATES, + .formats = SITAR_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &sitar_dai_ops, + }, +}; + +static int sitar_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *sitar; + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar_p = snd_soc_codec_get_drvdata(codec); + u32 j = 0; + codec->control_data = dev_get_drvdata(codec->dev->parent); + sitar = codec->control_data; + /* Execute the callback only if interface type is slimbus */ + if (sitar_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_CAP) + continue; + if (!strncmp(w->sname, + sitar_dai[j].playback.stream_name, 13)) { + ++sitar_p->dai[j].ch_act; + break; + } + } + if (sitar_p->dai[j].ch_act == sitar_p->dai[j].ch_tot) + wcd9xxx_cfg_slim_sch_rx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot, + sitar_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_CAP) + continue; + if (!strncmp(w->sname, + sitar_dai[j].playback.stream_name, 13)) { + --sitar_p->dai[j].ch_act; + break; + } + } + if (!sitar_p->dai[j].ch_act) { + wcd9xxx_close_slim_sch_rx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot); + /* Wait for remove channel to complete + * before derouting Rx path + */ + usleep_range(15000, 15000); + sitar_p->dai[j].rate = 0; + memset(sitar_p->dai[j].ch_num, 0, (sizeof(u32)* + sitar_p->dai[j].ch_tot)); + sitar_p->dai[j].ch_tot = 0; + } + } + return 0; +} + +static int sitar_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *sitar; + struct snd_soc_codec *codec = w->codec; + struct sitar_priv *sitar_p = snd_soc_codec_get_drvdata(codec); + /* index to the DAI ID, for now hardcoding */ + u32 j = 0; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + sitar = codec->control_data; + + /* Execute the callback only if interface type is slimbus */ + if (sitar_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_PB) + continue; + if (!strncmp(w->sname, + sitar_dai[j].capture.stream_name, 13)) { + ++sitar_p->dai[j].ch_act; + break; + } + } + if (sitar_p->dai[j].ch_act == sitar_p->dai[j].ch_tot) + wcd9xxx_cfg_slim_sch_tx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot, + sitar_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(sitar_dai); j++) { + if (sitar_dai[j].id == AIF1_PB) + continue; + if (!strncmp(w->sname, + sitar_dai[j].capture.stream_name, 13)) { + --sitar_p->dai[j].ch_act; + break; + } + } + if (!sitar_p->dai[j].ch_act) { + wcd9xxx_close_slim_sch_tx(sitar, + sitar_p->dai[j].ch_num, + sitar_p->dai[j].ch_tot); + sitar_p->dai[j].rate = 0; + memset(sitar_p->dai[j].ch_num, 0, (sizeof(u32)* + sitar_p->dai[j].ch_tot)); + sitar_p->dai[j].ch_tot = 0; + } + } + return 0; +} + + +static short sitar_codec_read_sta_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B3_STATUS); + bias_lsb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B2_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static short sitar_codec_read_dce_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B5_STATUS); + bias_lsb = snd_soc_read(codec, SITAR_A_CDC_MBHC_B4_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static void sitar_turn_onoff_rel_detection(struct snd_soc_codec *codec, + bool on) +{ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, on << 1); +} + +static short __sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool override_bypass, bool noreldetection) +{ + short bias_value; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + if (noreldetection) + sitar_turn_onoff_rel_detection(codec, false); + + /* Turn on the override */ + if (!override_bypass) + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + if (dce) { + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x4); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(sitar->mbhc_data.t_sta_dce, + sitar->mbhc_data.t_sta_dce); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x4); + usleep_range(sitar->mbhc_data.t_dce, + sitar->mbhc_data.t_dce); + bias_value = sitar_codec_read_dce_result(codec); + } else { + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(sitar->mbhc_data.t_sta_dce, + sitar->mbhc_data.t_sta_dce); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x2); + usleep_range(sitar->mbhc_data.t_sta, + sitar->mbhc_data.t_sta); + bias_value = sitar_codec_read_sta_result(codec); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x0); + } + /* Turn off the override after measuring mic voltage */ + if (!override_bypass) + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + + if (noreldetection) + sitar_turn_onoff_rel_detection(codec, true); + wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + + return bias_value; +} + +static short sitar_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool norel) +{ + return __sitar_codec_sta_dce(codec, dce, false, norel); +} + +static void sitar_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + const struct sitar_mbhc_general_cfg *generic = + SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration); + + if (!sitar->mclk_enabled && !sitar->mbhc_polling_active) + sitar_codec_enable_config_mode(codec, 1); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x6, 0x0); + + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0xA, 0x8); + if (!sitar->mclk_enabled && !sitar->mbhc_polling_active) + sitar_codec_enable_config_mode(codec, 0); + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x00); +} + +static void sitar_codec_cleanup_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + sitar_codec_shutdown_hs_removal_detect(codec); + + if (!sitar->mclk_enabled) { + sitar_codec_disable_clock_block(codec); + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF); + } + + sitar->mbhc_polling_active = false; + sitar->mbhc_state = MBHC_STATE_NONE; +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static short sitar_codec_setup_hs_polling(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + short bias_value; + u8 cfilt_mode; + + if (!sitar->mbhc_cfg.calibration) { + pr_err("Error, no sitar calibration\n"); + return -ENODEV; + } + + if (!sitar->mclk_enabled) { + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_MBHC_MODE); + sitar_enable_rx_bias(codec, 1); + sitar_codec_enable_clock_block(codec, 1); + } + + snd_soc_update_bits(codec, SITAR_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* Make sure CFILT is in fast mode, save current mode */ + cfilt_mode = snd_soc_read(codec, sitar->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x70, 0x00); + + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x1F, 0x16); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x80, 0x80); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x1F, 0x1C); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_TEST_CTL, 0x40, 0x40); + + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, 0x80, 0x00); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x00); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + sitar_codec_calibrate_hs_polling(codec); + + /* don't flip override */ + bias_value = __sitar_codec_sta_dce(codec, 1, true, true); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00); + + return bias_value; +} + +static int sitar_cancel_btn_work(struct sitar_priv *sitar) +{ + int r = 0; + struct wcd9xxx *core = dev_get_drvdata(sitar->codec->dev->parent); + + if (cancel_delayed_work_sync(&sitar->mbhc_btn_dwork)) { + /* if scheduled mbhc_btn_dwork is canceled from here, + * we have to unlock from here instead btn_work */ + wcd9xxx_unlock_sleep(core); + r = 1; + } + return r; +} + + +static u16 sitar_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce, + s16 vin_mv) +{ + short diff, zero; + struct sitar_priv *sitar; + u32 mb_mv, in; + + sitar = snd_soc_codec_get_drvdata(codec); + mb_mv = sitar->mbhc_data.micb_mv; + + if (mb_mv == 0) { + pr_err("%s: Mic Bias voltage is set to zero\n", __func__); + return -EINVAL; + } + + if (dce) { + diff = sitar->mbhc_data.dce_mb - sitar->mbhc_data.dce_z; + zero = sitar->mbhc_data.dce_z; + } else { + diff = sitar->mbhc_data.sta_mb - sitar->mbhc_data.sta_z; + zero = sitar->mbhc_data.sta_z; + } + in = (u32) diff * vin_mv; + + return (u16) (in / mb_mv) + zero; +} + +static s32 sitar_codec_sta_dce_v(struct snd_soc_codec *codec, s8 dce, + u16 bias_value) +{ + struct sitar_priv *sitar; + s16 value, z, mb; + s32 mv; + + sitar = snd_soc_codec_get_drvdata(codec); + value = bias_value; + + if (dce) { + z = (sitar->mbhc_data.dce_z); + mb = (sitar->mbhc_data.dce_mb); + mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z); + } else { + z = (sitar->mbhc_data.sta_z); + mb = (sitar->mbhc_data.sta_mb); + mv = (value - z) * (s32)sitar->mbhc_data.micb_mv / (mb - z); + } + + return mv; +} + +static void btn_lpress_fn(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct sitar_priv *sitar; + short bias_value; + int dce_mv, sta_mv; + struct wcd9xxx *core; + + pr_debug("%s:\n", __func__); + + delayed_work = to_delayed_work(work); + sitar = container_of(delayed_work, struct sitar_priv, mbhc_btn_dwork); + core = dev_get_drvdata(sitar->codec->dev->parent); + + if (sitar) { + if (sitar->mbhc_cfg.button_jack) { + bias_value = sitar_codec_read_sta_result(sitar->codec); + sta_mv = sitar_codec_sta_dce_v(sitar->codec, 0, + bias_value); + bias_value = sitar_codec_read_dce_result(sitar->codec); + dce_mv = sitar_codec_sta_dce_v(sitar->codec, 1, + bias_value); + pr_debug("%s: Reporting long button press event" + " STA: %d, DCE: %d\n", __func__, sta_mv, dce_mv); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.button_jack, + sitar->buttons_pressed, + sitar->buttons_pressed); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + pr_debug("%s: leave\n", __func__); + wcd9xxx_unlock_sleep(core); +} + + +void sitar_mbhc_cal(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + struct sitar_mbhc_btn_detect_cfg *btn_det; + u8 cfilt_mode, bg_mode; + u8 ncic, nmeas, navg; + u32 mclk_rate; + u32 dce_wait, sta_wait; + u8 *n_cic; + void *calibration; + + sitar = snd_soc_codec_get_drvdata(codec); + calibration = sitar->mbhc_cfg.calibration; + + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + sitar_turn_onoff_rel_detection(codec, false); + + /* First compute the DCE / STA wait times + * depending on tunable parameters. + * The value is computed in microseconds + */ + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(calibration); + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + ncic = n_cic[sitar_codec_mclk_index(sitar)]; + nmeas = SITAR_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas; + navg = SITAR_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg; + mclk_rate = sitar->mbhc_cfg.mclk_rate; + dce_wait = (1000 * 512 * 60 * (nmeas + 1)) / (mclk_rate / 1000); + sta_wait = (1000 * 128 * (navg + 1)) / (mclk_rate / 1000); + + sitar->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + sitar->mbhc_data.t_sta = DEFAULT_STA_WAIT; + + /* LDOH and CFILT are already configured during pdata handling. + * Only need to make sure CFILT and bandgap are in Fast mode. + * Need to restore defaults once calculation is done. + */ + cfilt_mode = snd_soc_read(codec, sitar->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, 0x00); + bg_mode = snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x02, + 0x02); + + /* Micbias, CFILT, LDOH, MBHC MUX mode settings + * to perform ADC calibration + */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x60, + sitar->mbhc_cfg.micbias << 5); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x60, 0x60); + snd_soc_write(codec, SITAR_A_TX_4_MBHC_TEST_CTL, 0x78); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x04); + + /* DCE measurement for 0 volts */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(sitar->mbhc_data.t_dce, sitar->mbhc_data.t_dce); + sitar->mbhc_data.dce_z = sitar_codec_read_dce_result(codec); + + /* DCE measurment for MB voltage */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(sitar->mbhc_data.t_dce, sitar->mbhc_data.t_dce); + sitar->mbhc_data.dce_mb = sitar_codec_read_dce_result(codec); + + /* Sta measuremnt for 0 volts */ + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + snd_soc_write(codec, SITAR_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(sitar->mbhc_data.t_sta, sitar->mbhc_data.t_sta); + sitar->mbhc_data.sta_z = sitar_codec_read_sta_result(codec); + + /* STA Measurement for MB Voltage */ + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, SITAR_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(sitar->mbhc_data.t_sta, sitar->mbhc_data.t_sta); + sitar->mbhc_data.sta_mb = sitar_codec_read_sta_result(codec); + + /* Restore default settings. */ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, SITAR_A_BIAS_CENTRAL_BG_CTL, 0x02, bg_mode); + + snd_soc_write(codec, SITAR_A_MBHC_SCALING_MUX_1, 0x84); + usleep_range(100, 100); + + wcd9xxx_enable_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL); + sitar_turn_onoff_rel_detection(codec, true); +} + +void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg* btn_det, + const enum sitar_mbhc_btn_det_mem mem) +{ + void *ret = &btn_det->_v_btn_low; + + switch (mem) { + case SITAR_BTN_DET_GAIN: + ret += sizeof(btn_det->_n_cic); + case SITAR_BTN_DET_N_CIC: + ret += sizeof(btn_det->_n_ready); + case SITAR_BTN_DET_N_READY: + ret += sizeof(btn_det->_v_btn_high[0]) * btn_det->num_btn; + case SITAR_BTN_DET_V_BTN_HIGH: + ret += sizeof(btn_det->_v_btn_low[0]) * btn_det->num_btn; + case SITAR_BTN_DET_V_BTN_LOW: + /* do nothing */ + break; + default: + ret = NULL; + } + + return ret; +} + +static void sitar_mbhc_calc_thres(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + s16 btn_mv = 0, btn_delta_mv; + struct sitar_mbhc_btn_detect_cfg *btn_det; + struct sitar_mbhc_plug_type_cfg *plug_type; + u16 *btn_high; + u8 *n_ready; + int i; + + sitar = snd_soc_codec_get_drvdata(codec); + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + plug_type = SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + n_ready = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_READY); + if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_12288KHZ) { + sitar->mbhc_data.npoll = 9; + sitar->mbhc_data.nbounce_wait = 30; + } else if (sitar->mbhc_cfg.mclk_rate == SITAR_MCLK_RATE_9600KHZ) { + sitar->mbhc_data.npoll = 7; + sitar->mbhc_data.nbounce_wait = 23; + } + + sitar->mbhc_data.t_sta_dce = ((1000 * 256) / + (sitar->mbhc_cfg.mclk_rate / 1000) * + n_ready[sitar_codec_mclk_index(sitar)]) + + 10; + sitar->mbhc_data.v_ins_hu = + sitar_codec_v_sta_dce(codec, STA, plug_type->v_hs_max); + sitar->mbhc_data.v_ins_h = + sitar_codec_v_sta_dce(codec, DCE, plug_type->v_hs_max); + + btn_high = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) + btn_mv = btn_high[i] > btn_mv ? btn_high[i] : btn_mv; + + sitar->mbhc_data.v_b1_h = sitar_codec_v_sta_dce(codec, DCE, btn_mv); + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_sta; + + sitar->mbhc_data.v_b1_hu = + sitar_codec_v_sta_dce(codec, STA, btn_delta_mv); + + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_cic; + + sitar->mbhc_data.v_b1_huc = + sitar_codec_v_sta_dce(codec, DCE, btn_delta_mv); + + sitar->mbhc_data.v_brh = sitar->mbhc_data.v_b1_h; + sitar->mbhc_data.v_brl = SITAR_MBHC_BUTTON_MIN; + + sitar->mbhc_data.v_no_mic = + sitar_codec_v_sta_dce(codec, STA, plug_type->v_no_mic); +} + +void sitar_mbhc_init(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar; + struct sitar_mbhc_general_cfg *generic; + struct sitar_mbhc_btn_detect_cfg *btn_det; + int n; + u8 *n_cic, *gain; + + pr_err("%s(): ENTER\n", __func__); + sitar = snd_soc_codec_get_drvdata(codec); + generic = SITAR_MBHC_CAL_GENERAL_PTR(sitar->mbhc_cfg.calibration); + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(sitar->mbhc_cfg.calibration); + + for (n = 0; n < 8; n++) { + if (n != 7) { + snd_soc_update_bits(codec, + SITAR_A_CDC_MBHC_FIR_B1_CFG, + 0x07, n); + snd_soc_write(codec, SITAR_A_CDC_MBHC_FIR_B2_CFG, + btn_det->c[n]); + } + } + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B2_CTL, 0x07, + btn_det->nc); + + n_cic = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_N_CIC); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B6_CTL, 0xFF, + n_cic[sitar_codec_mclk_index(sitar)]); + + gain = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_GAIN); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B2_CTL, 0x78, + gain[sitar_codec_mclk_index(sitar)] << 3); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B4_CTL, 0x70, + generic->mbhc_nsa << 4); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_TIMER_B4_CTL, 0x0F, + btn_det->n_meas); + + snd_soc_write(codec, SITAR_A_CDC_MBHC_TIMER_B5_CTL, generic->mbhc_navg); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x80, 0x80); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + + snd_soc_update_bits(codec, SITAR_A_MICB_1_MBHC, 0x03, + sitar->mbhc_cfg.micbias); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + + snd_soc_update_bits(codec, SITAR_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0); + +} + +static bool sitar_mbhc_fw_validate(const struct firmware *fw) +{ + u32 cfg_offset; + struct sitar_mbhc_imped_detect_cfg *imped_cfg; + struct sitar_mbhc_btn_detect_cfg *btn_cfg; + + if (fw->size < SITAR_MBHC_CAL_MIN_SIZE) + return false; + + /* previous check guarantees that there is enough fw data up + * to num_btn + */ + btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data); + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_BTN_SZ(btn_cfg))) + return false; + + /* previous check guarantees that there is enough fw data up + * to start of impedance detection configuration + */ + imped_cfg = SITAR_MBHC_CAL_IMPED_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data); + + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_MIN_SZ)) + return false; + + if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_SZ(imped_cfg))) + return false; + + return true; +} + + +static void sitar_turn_onoff_override(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, on << 2); +} + +/* called under codec_resource_lock acquisition */ +void sitar_set_and_turnoff_hph_padac(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + u8 wg_time; + + wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + /* If headphone PA is on, check if userspace receives + * removal event to sync-up PA's state */ + if (sitar_is_hph_pa_on(codec)) { + pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__); + set_bit(SITAR_HPHL_PA_OFF_ACK, &sitar->hph_pa_dac_state); + set_bit(SITAR_HPHR_PA_OFF_ACK, &sitar->hph_pa_dac_state); + } else { + pr_debug("%s PA is off\n", __func__); + } + + if (sitar_is_hph_dac_on(codec, 1)) + set_bit(SITAR_HPHL_DAC_OFF_ACK, &sitar->hph_pa_dac_state); + if (sitar_is_hph_dac_on(codec, 0)) + set_bit(SITAR_HPHR_DAC_OFF_ACK, &sitar->hph_pa_dac_state); + + snd_soc_update_bits(codec, SITAR_A_RX_HPH_CNP_EN, 0x30, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_L_DAC_CTL, + 0xC0, 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_R_DAC_CTL, + 0xC0, 0x00); + usleep_range(wg_time * 1000, wg_time * 1000); +} + +static void sitar_clr_and_turnon_hph_padac(struct sitar_priv *sitar) +{ + bool pa_turned_on = false; + struct snd_soc_codec *codec = sitar->codec; + u8 wg_time; + + wg_time = snd_soc_read(codec, SITAR_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + if (test_and_clear_bit(SITAR_HPHR_DAC_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable DAC\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_R_DAC_CTL, + 0xC0, 0xC0); + } + if (test_and_clear_bit(SITAR_HPHL_DAC_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable DAC\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_L_DAC_CTL, + 0xC0, 0xC0); + } + + if (test_and_clear_bit(SITAR_HPHR_PA_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable PA\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x10, + 1 << 4); + pa_turned_on = true; + } + if (test_and_clear_bit(SITAR_HPHL_PA_OFF_ACK, + &sitar->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable PA\n", __func__); + snd_soc_update_bits(sitar->codec, SITAR_A_RX_HPH_CNP_EN, 0x20, + 1 << 5); + pa_turned_on = true; + } + + if (pa_turned_on) { + pr_debug("%s: PA was turned off by MBHC and not by DAPM\n", + __func__); + usleep_range(wg_time * 1000, wg_time * 1000); + } +} + +static void sitar_codec_report_plug(struct snd_soc_codec *codec, int insertion, + enum snd_jack_types jack_type) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (!insertion) { + /* Report removal */ + sitar->hph_status &= ~jack_type; + if (sitar->mbhc_cfg.headset_jack) { + /* cancel possibly scheduled btn work and + * report release if we reported button press */ + if (sitar_cancel_btn_work(sitar)) { + pr_debug("%s: button press is canceled\n", + __func__); + } else if (sitar->buttons_pressed) { + pr_debug("%s: Reporting release for reported " + "button press %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.button_jack, 0, + sitar->buttons_pressed); + sitar->buttons_pressed &= + ~SITAR_JACK_BUTTON_MASK; + } + pr_debug("%s: Reporting removal %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + sitar_set_and_turnoff_hph_padac(codec); + hphocp_off_report(sitar, SND_JACK_OC_HPHR, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + hphocp_off_report(sitar, SND_JACK_OC_HPHL, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + sitar->current_plug = PLUG_TYPE_NONE; + sitar->mbhc_polling_active = false; + } else { + /* Report insertion */ + sitar->hph_status |= jack_type; + + if (jack_type == SND_JACK_HEADPHONE) + sitar->current_plug = PLUG_TYPE_HEADPHONE; + else if (jack_type == SND_JACK_HEADSET) { + sitar->mbhc_polling_active = true; + sitar->current_plug = PLUG_TYPE_HEADSET; + } + if (sitar->mbhc_cfg.headset_jack) { + pr_debug("%s: Reporting insertion %d\n", __func__, + jack_type); + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + sitar_clr_and_turnon_hph_padac(sitar); + } +} + + +static bool sitar_hs_gpio_level_remove(struct sitar_priv *sitar) +{ + return (gpio_get_value_cansleep(sitar->mbhc_cfg.gpio) != + sitar->mbhc_cfg.gpio_level_insert); +} + +static bool sitar_is_invalid_insert_delta(struct snd_soc_codec *codec, + int mic_volt, int mic_volt_prev) +{ + int delta = abs(mic_volt - mic_volt_prev); + if (delta > SITAR_MBHC_FAKE_INSERT_VOLT_DELTA_MV) { + pr_debug("%s: volt delta %dmv\n", __func__, delta); + return true; + } + return false; +} + +static bool sitar_is_invalid_insertion_range(struct snd_soc_codec *codec, + s32 mic_volt) +{ + bool invalid = false; + + if (mic_volt < SITAR_MBHC_FAKE_INSERT_HIGH + && (mic_volt > SITAR_MBHC_FAKE_INSERT_LOW)) { + invalid = true; + } + + return invalid; +} + +static bool sitar_codec_is_invalid_plug(struct snd_soc_codec *codec, + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT], + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]) +{ + int i; + bool r = false; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct sitar_mbhc_plug_type_cfg *plug_type_ptr = + SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + for (i = 0 ; i < MBHC_NUM_DCE_PLUG_DETECT && !r; i++) { + if (mic_mv[i] < plug_type_ptr->v_no_mic) + plug_type[i] = PLUG_TYPE_HEADPHONE; + else if (mic_mv[i] < plug_type_ptr->v_hs_max) + plug_type[i] = PLUG_TYPE_HEADSET; + else if (mic_mv[i] > plug_type_ptr->v_hs_max) + plug_type[i] = PLUG_TYPE_HIGH_HPH; + + r = sitar_is_invalid_insertion_range(codec, mic_mv[i]); + if (!r && i > 0) { + if (plug_type[i-1] != plug_type[i]) + r = true; + else + r = sitar_is_invalid_insert_delta(codec, + mic_mv[i], + mic_mv[i - 1]); + } + } + + return r; +} + +/* called under codec_resource_lock acquisition */ +void sitar_find_plug_and_report(struct snd_soc_codec *codec, + enum sitar_mbhc_plug_type plug_type) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + if (plug_type == PLUG_TYPE_HEADPHONE + && sitar->current_plug == PLUG_TYPE_NONE) { + /* Nothing was reported previously + * reporte a headphone + */ + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HEADSET) { + /* If Headphone was reported previously, this will + * only report the mic line + */ + sitar_codec_report_plug(codec, 1, SND_JACK_HEADSET); + msleep(100); + sitar_codec_start_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + if (sitar->current_plug == PLUG_TYPE_NONE) + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_codec_cleanup_hs_polling(codec); + pr_debug("setup mic trigger for further detection\n"); + sitar->lpi_enabled = true; + /* TODO ::: sitar_codec_enable_hs_detect */ + pr_err("%s(): High impedence hph not supported\n", __func__); + } +} + +/* should be called under interrupt context that hold suspend */ +static void sitar_schedule_hs_detect_plug(struct sitar_priv *sitar) +{ + pr_debug("%s: scheduling sitar_hs_correct_gpio_plug\n", __func__); + sitar->hs_detect_work_stop = false; + wcd9xxx_lock_sleep(sitar->codec->control_data); + schedule_work(&sitar->hs_correct_plug_work); +} + +/* called under codec_resource_lock acquisition */ +static void sitar_cancel_hs_detect_plug(struct sitar_priv *sitar) +{ + pr_debug("%s: canceling hs_correct_plug_work\n", __func__); + sitar->hs_detect_work_stop = true; + wmb(); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + if (cancel_work_sync(&sitar->hs_correct_plug_work)) { + pr_debug("%s: hs_correct_plug_work is canceled\n", __func__); + wcd9xxx_unlock_sleep(sitar->codec->control_data); + } + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); +} + +static void sitar_hs_correct_gpio_plug(struct work_struct *work) +{ + struct sitar_priv *sitar; + struct snd_soc_codec *codec; + int retry = 0, i; + bool correction = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long timeout; + + sitar = container_of(work, struct sitar_priv, hs_correct_plug_work); + codec = sitar->codec; + + pr_debug("%s: enter\n", __func__); + sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false); + + /* Keep override on during entire plug type correction work. + * + * This is okay under the assumption that any GPIO irqs which use + * MBHC block cancel and sync this work so override is off again + * prior to GPIO interrupt handler's MBHC block usage. + * Also while this correction work is running, we can guarantee + * DAPM doesn't use any MBHC block as this work only runs with + * headphone detection. + */ + sitar_turn_onoff_override(codec, true); + + timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS); + while (!time_after(jiffies, timeout)) { + ++retry; + rmb(); + if (sitar->hs_detect_work_stop) { + pr_debug("%s: stop requested\n", __func__); + break; + } + + msleep(SITAR_HS_DETECT_PLUG_INERVAL_MS); + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO value is low\n", __func__); + break; + } + + /* can race with removal interrupt */ + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %d, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + + if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) { + pr_debug("Invalid plug in attempt # %d\n", retry); + if (retry == NUM_ATTEMPTS_TO_REPORT && + sitar->current_plug == PLUG_TYPE_NONE) { + sitar_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else if (!sitar_codec_is_invalid_plug(codec, mic_mv, + plug_type) && + plug_type[0] == PLUG_TYPE_HEADPHONE) { + pr_debug("Good headphone detected, continue polling mic\n"); + if (sitar->current_plug == PLUG_TYPE_NONE) { + sitar_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else { + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + /* Turn off override */ + sitar_turn_onoff_override(codec, false); + sitar_find_plug_and_report(codec, plug_type[0]); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + pr_debug("Attempt %d found correct plug %d\n", retry, + plug_type[0]); + correction = true; + break; + } + } + + /* Turn off override */ + if (!correction) + sitar_turn_onoff_override(codec, false); + + sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false); + pr_debug("%s: leave\n", __func__); + /* unlock sleep */ + wcd9xxx_unlock_sleep(sitar->codec->control_data); +} + +/* called under codec_resource_lock acquisition */ +static void sitar_codec_decide_gpio_plug(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + enum sitar_mbhc_plug_type plug_type[MBHC_NUM_DCE_PLUG_DETECT]; + int i; + + pr_debug("%s: enter\n", __func__); + + sitar_turn_onoff_override(codec, true); + mb_v[0] = sitar_codec_setup_hs_polling(codec); + mic_mv[0] = sitar_codec_sta_dce_v(codec, 1, mb_v[0]); + pr_debug("%s: DCE run 1, mic_mv = %d\n", __func__, mic_mv[0]); + + for (i = 1; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s: DCE run %d, mic_mv = %d\n", __func__, i + 1, + mic_mv[i]); + } + sitar_turn_onoff_override(codec, false); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO value is low when determining plug\n", + __func__); + return; + } + + if (sitar_codec_is_invalid_plug(codec, mic_mv, plug_type)) { + sitar_schedule_hs_detect_plug(sitar); + } else if (plug_type[0] == PLUG_TYPE_HEADPHONE) { + sitar_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + sitar_schedule_hs_detect_plug(sitar); + } else if (plug_type[0] == PLUG_TYPE_HEADSET) { + pr_debug("%s: Valid plug found, determine plug type\n", + __func__); + sitar_find_plug_and_report(codec, plug_type[0]); + } + +} + +/* called under codec_resource_lock acquisition */ +static void sitar_codec_detect_plug_type(struct snd_soc_codec *codec) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + const struct sitar_mbhc_plug_detect_cfg *plug_det = + SITAR_MBHC_CAL_PLUG_DET_PTR(sitar->mbhc_cfg.calibration); + + if (plug_det->t_ins_complete > 20) + msleep(plug_det->t_ins_complete); + else + usleep_range(plug_det->t_ins_complete * 1000, + plug_det->t_ins_complete * 1000); + + if (sitar_hs_gpio_level_remove(sitar)) + pr_debug("%s: GPIO value is low when determining " + "plug\n", __func__); + else + sitar_codec_decide_gpio_plug(codec); + + return; +} + +static void sitar_hs_gpio_handler(struct snd_soc_codec *codec) +{ + bool insert; + struct sitar_priv *priv = snd_soc_codec_get_drvdata(codec); + bool is_removed = false; + + pr_debug("%s: enter\n", __func__); + + priv->in_gpio_handler = true; + /* Wait here for debounce time */ + usleep_range(SITAR_GPIO_IRQ_DEBOUNCE_TIME_US, + SITAR_GPIO_IRQ_DEBOUNCE_TIME_US); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + + /* cancel pending button press */ + if (sitar_cancel_btn_work(priv)) + pr_debug("%s: button press is canceled\n", __func__); + + insert = (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert); + if ((priv->current_plug == PLUG_TYPE_NONE) && insert) { + priv->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + sitar_cancel_hs_detect_plug(priv); + + /* Disable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01, 0x00); + sitar_codec_detect_plug_type(codec); + } else if ((priv->current_plug != PLUG_TYPE_NONE) && !insert) { + priv->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + sitar_cancel_hs_detect_plug(priv); + + if (priv->current_plug == PLUG_TYPE_HEADPHONE) { + sitar_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + is_removed = true; + } else if (priv->current_plug == PLUG_TYPE_HEADSET) { + sitar_codec_pause_hs_polling(codec); + sitar_codec_cleanup_hs_polling(codec); + sitar_codec_report_plug(codec, 0, SND_JACK_HEADSET); + is_removed = true; + } + + if (is_removed) { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.ctl_reg, 0x01, + 0x01); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x01, + 0x01); + /* Make sure mic trigger is turned off */ + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, + priv->mbhc_bias_regs.mbhc_reg, + 0x90, 0x00); + /* Reset MBHC State Machine */ + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_CLK_CTL, + 0x08, 0x00); + /* Turn off override */ + sitar_turn_onoff_override(codec, false); + } + } + + priv->in_gpio_handler = false; + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + pr_debug("%s: leave\n", __func__); +} + +static irqreturn_t sitar_mechanical_plug_detect_irq(int irq, void *data) +{ + int r = IRQ_HANDLED; + struct snd_soc_codec *codec = data; + + if (unlikely(wcd9xxx_lock_sleep(codec->control_data) == false)) { + pr_warn("%s(): Failed to hold suspend\n", __func__); + r = IRQ_NONE; + } else { + sitar_hs_gpio_handler(codec); + wcd9xxx_unlock_sleep(codec->control_data); + } + return r; +} + +static int sitar_mbhc_init_and_calibrate(struct snd_soc_codec *codec) +{ + int rc = 0; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + sitar->mbhc_cfg.mclk_cb_fn(codec, 1, false); + sitar_mbhc_init(codec); + sitar_mbhc_cal(codec); + sitar_mbhc_calc_thres(codec); + sitar->mbhc_cfg.mclk_cb_fn(codec, 0, false); + sitar_codec_calibrate_hs_polling(codec); + + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, + 0x01, 0x01); + + rc = request_threaded_irq(sitar->mbhc_cfg.gpio_irq, + NULL, + sitar_mechanical_plug_detect_irq, + (IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING), + "sitar-hs-gpio", codec); + + if (!IS_ERR_VALUE(rc)) { + rc = enable_irq_wake(sitar->mbhc_cfg.gpio_irq); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, + 0x10, 0x10); + wcd9xxx_enable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + wcd9xxx_enable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + /* Bootup time detection */ + sitar_hs_gpio_handler(codec); + } + + return rc; +} + +static void mbhc_fw_read(struct work_struct *work) +{ + struct delayed_work *dwork; + struct sitar_priv *sitar; + struct snd_soc_codec *codec; + const struct firmware *fw; + int ret = -1, retry = 0; + + dwork = to_delayed_work(work); + sitar = container_of(dwork, struct sitar_priv, + mbhc_firmware_dwork); + codec = sitar->codec; + + while (retry < MBHC_FW_READ_ATTEMPTS) { + retry++; + pr_info("%s:Attempt %d to request MBHC firmware\n", + __func__, retry); + ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin", + codec->dev); + + if (ret != 0) { + usleep_range(MBHC_FW_READ_TIMEOUT, + MBHC_FW_READ_TIMEOUT); + } else { + pr_info("%s: MBHC Firmware read succesful\n", __func__); + break; + } + } + + if (ret != 0) { + pr_err("%s: Cannot load MBHC firmware use default cal\n", + __func__); + } else if (sitar_mbhc_fw_validate(fw) == false) { + pr_err("%s: Invalid MBHC cal data size use default cal\n", + __func__); + release_firmware(fw); + } else { + sitar->calibration = (void *)fw->data; + sitar->mbhc_fw = fw; + } + + sitar_mbhc_init_and_calibrate(codec); +} + +int sitar_hs_detect(struct snd_soc_codec *codec, + const struct sitar_mbhc_config *cfg) +{ + struct sitar_priv *sitar; + int rc = 0; + + if (!codec || !cfg->calibration) { + pr_err("Error: no codec or calibration\n"); + return -EINVAL; + } + + if (cfg->mclk_rate != SITAR_MCLK_RATE_12288KHZ) { + if (cfg->mclk_rate == SITAR_MCLK_RATE_9600KHZ) + pr_err("Error: clock rate %dHz is not yet supported\n", + cfg->mclk_rate); + else + pr_err("Error: unsupported clock rate %d\n", + cfg->mclk_rate); + return -EINVAL; + } + + sitar = snd_soc_codec_get_drvdata(codec); + sitar->mbhc_cfg = *cfg; + sitar->in_gpio_handler = false; + sitar->current_plug = PLUG_TYPE_NONE; + sitar->lpi_enabled = false; + sitar_get_mbhc_micbias_regs(codec, &sitar->mbhc_bias_regs); + + /* Put CFILT in fast mode by default */ + snd_soc_update_bits(codec, sitar->mbhc_bias_regs.cfilt_ctl, + 0x40, SITAR_CFILT_FAST_MODE); + + INIT_DELAYED_WORK(&sitar->mbhc_firmware_dwork, mbhc_fw_read); + INIT_DELAYED_WORK(&sitar->mbhc_btn_dwork, btn_lpress_fn); + INIT_WORK(&sitar->hphlocp_work, hphlocp_off_report); + INIT_WORK(&sitar->hphrocp_work, hphrocp_off_report); + INIT_WORK(&sitar->hs_correct_plug_work, + sitar_hs_correct_gpio_plug); + + if (!sitar->mbhc_cfg.read_fw_bin) { + rc = sitar_mbhc_init_and_calibrate(codec); + } else { + schedule_delayed_work(&sitar->mbhc_firmware_dwork, + usecs_to_jiffies(MBHC_FW_READ_TIMEOUT)); + } + + return rc; +} +EXPORT_SYMBOL_GPL(sitar_hs_detect); + +static int sitar_determine_button(const struct sitar_priv *priv, + const s32 bias_mv) +{ + s16 *v_btn_low, *v_btn_high; + struct sitar_mbhc_btn_detect_cfg *btn_det; + int i, btn = -1; + + btn_det = SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + v_btn_low = sitar_mbhc_cal_btn_det_mp(btn_det, SITAR_BTN_DET_V_BTN_LOW); + v_btn_high = sitar_mbhc_cal_btn_det_mp(btn_det, + SITAR_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) { + if ((v_btn_low[i] <= bias_mv) && (v_btn_high[i] >= bias_mv)) { + btn = i; + break; + } + } + + if (btn == -1) + pr_debug("%s: couldn't find button number for mic mv %d\n", + __func__, bias_mv); + + return btn; +} + +static int sitar_get_button_mask(const int btn) +{ + int mask = 0; + switch (btn) { + case 0: + mask = SND_JACK_BTN_0; + break; + case 1: + mask = SND_JACK_BTN_1; + break; + case 2: + mask = SND_JACK_BTN_2; + break; + case 3: + mask = SND_JACK_BTN_3; + break; + case 4: + mask = SND_JACK_BTN_4; + break; + case 5: + mask = SND_JACK_BTN_5; + break; + case 6: + mask = SND_JACK_BTN_6; + break; + case 7: + mask = SND_JACK_BTN_7; + break; + } + return mask; +} + + +static irqreturn_t sitar_dce_handler(int irq, void *data) +{ + int i, mask; + short dce, sta, bias_value_dce; + s32 mv, stamv, bias_mv_dce; + int btn = -1, meas = 0; + struct sitar_priv *priv = data; + const struct sitar_mbhc_btn_detect_cfg *d = + SITAR_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + short btnmeas[d->n_btn_meas + 1]; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + int n_btn_meas = d->n_btn_meas; + u8 mbhc_status = snd_soc_read(codec, SITAR_A_CDC_MBHC_B1_STATUS) & 0x3E; + + pr_debug("%s: enter\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + if (priv->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) { + pr_debug("%s: mbhc is being recovered, skip button press\n", + __func__); + goto done; + } + + priv->mbhc_state = MBHC_STATE_POTENTIAL; + + if (!priv->mbhc_polling_active) { + pr_warn("%s: mbhc polling is not active, skip button press\n", + __func__); + goto done; + } + + dce = sitar_codec_read_dce_result(codec); + mv = sitar_codec_sta_dce_v(codec, 1, dce); + + /* If GPIO interrupt already kicked in, ignore button press */ + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO State Changed, ignore button press\n", + __func__); + btn = -1; + goto done; + } + + if (mbhc_status != SITAR_MBHC_STATUS_REL_DETECTION) { + if (priv->mbhc_last_resume && + !time_after(jiffies, priv->mbhc_last_resume + HZ)) { + pr_debug("%s: Button is already released shortly after " + "resume\n", __func__); + n_btn_meas = 0; + } else { + pr_debug("%s: Button is already released without " + "resume", __func__); + sta = sitar_codec_read_sta_result(codec); + stamv = sitar_codec_sta_dce_v(codec, 0, sta); + btn = sitar_determine_button(priv, mv); + if (btn != sitar_determine_button(priv, stamv)) + btn = -1; + goto done; + } + } + + /* determine pressed button */ + btnmeas[meas++] = sitar_determine_button(priv, mv); + pr_debug("%s: meas %d - DCE %d,%d, button %d\n", __func__, + meas - 1, dce, mv, btnmeas[meas - 1]); + if (n_btn_meas == 0) + btn = btnmeas[0]; + for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) { + bias_value_dce = sitar_codec_sta_dce(codec, 1, false); + bias_mv_dce = sitar_codec_sta_dce_v(codec, 1, bias_value_dce); + btnmeas[meas] = sitar_determine_button(priv, bias_mv_dce); + pr_debug("%s: meas %d - DCE %d,%d, button %d\n", + __func__, meas, bias_value_dce, bias_mv_dce, + btnmeas[meas]); + /* if large enough measurements are collected, + * start to check if last all n_btn_con measurements were + * in same button low/high range */ + if (meas + 1 >= d->n_btn_con) { + for (i = 0; i < d->n_btn_con; i++) + if ((btnmeas[meas] < 0) || + (btnmeas[meas] != btnmeas[meas - i])) + break; + if (i == d->n_btn_con) { + /* button pressed */ + btn = btnmeas[meas]; + break; + } else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) { + /* if left measurements are less than n_btn_con, + * it's impossible to find button number */ + break; + } + } + } + + if (btn >= 0) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO already triggered, ignore button " + "press\n", __func__); + goto done; + } + mask = sitar_get_button_mask(btn); + priv->buttons_pressed |= mask; + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_btn_dwork, + msecs_to_jiffies(400)) == 0) { + WARN(1, "Button pressed twice without release" + "event\n"); + wcd9xxx_unlock_sleep(core); + } + } else { + pr_debug("%s: bogus button press, too short press?\n", + __func__); + } + + done: + pr_debug("%s: leave\n", __func__); + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static int sitar_is_fake_press(struct sitar_priv *priv) +{ + int i; + int r = 0; + struct snd_soc_codec *codec = priv->codec; + const int dces = MBHC_NUM_DCE_PLUG_DETECT; + short mb_v; + + for (i = 0; i < dces; i++) { + usleep_range(10000, 10000); + if (i == 0) { + mb_v = sitar_codec_sta_dce(codec, 0, true); + pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v, + sitar_codec_sta_dce_v(codec, 0, mb_v)); + if (mb_v < (short)priv->mbhc_data.v_b1_hu || + mb_v > (short)priv->mbhc_data.v_ins_hu) { + r = 1; + break; + } + } else { + mb_v = sitar_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v, + sitar_codec_sta_dce_v(codec, 1, mb_v)); + if (mb_v < (short)priv->mbhc_data.v_b1_h || + mb_v > (short)priv->mbhc_data.v_ins_h) { + r = 1; + break; + } + } + } + + return r; +} + +static irqreturn_t sitar_release_handler(int irq, void *data) +{ + int ret; + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + priv->mbhc_state = MBHC_STATE_RELEASE; + + if (priv->buttons_pressed & SITAR_JACK_BUTTON_MASK) { + ret = sitar_cancel_btn_work(priv); + if (ret == 0) { + pr_debug("%s: Reporting long button release event\n", + __func__); + if (priv->mbhc_cfg.button_jack) + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } else { + if (sitar_is_fake_press(priv)) { + pr_debug("%s: Fake button press interrupt\n", + __func__); + } else if (priv->mbhc_cfg.button_jack) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO kicked in, ignore\n", + __func__); + } else { + pr_debug("%s: Reporting short button 0 " + "press and release\n", + __func__); + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, + priv->buttons_pressed, + priv->buttons_pressed); + sitar_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } + } + } + + priv->buttons_pressed &= ~SITAR_JACK_BUTTON_MASK; + } + + sitar_codec_calibrate_hs_polling(codec); + + if (priv->mbhc_cfg.gpio) + msleep(SITAR_MBHC_GPIO_REL_DEBOUNCE_TIME_MS); + + sitar_codec_start_hs_polling(codec); + + pr_debug("%s: leave\n", __func__); + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hphl_ocp_irq(int irq, void *data) +{ + struct sitar_priv *sitar = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHL OCP irq\n", __func__); + + if (sitar) { + codec = sitar->codec; + if (sitar->hphlocp_cnt++ < SITAR_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + sitar->hphlocp_cnt = 0; + sitar->hph_status |= SND_JACK_OC_HPHL; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hphr_ocp_irq(int irq, void *data) +{ + struct sitar_priv *sitar = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHR OCP irq\n", __func__); + + if (sitar) { + codec = sitar->codec; + if (sitar->hphrocp_cnt++ < SITAR_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + sitar->hphrocp_cnt = 0; + sitar->hph_status |= SND_JACK_OC_HPHR; + if (sitar->mbhc_cfg.headset_jack) + sitar_snd_soc_jack_report(sitar, + sitar->mbhc_cfg.headset_jack, + sitar->hph_status, + SITAR_JACK_MASK); + } + } else { + pr_err("%s: Bad sitar private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t sitar_hs_insert_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION); + + snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_INT_CTL, 0x03, 0x00); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, SITAR_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + + pr_debug("%s: MIC trigger insertion interrupt\n", __func__); + + rmb(); + if (priv->lpi_enabled) + msleep(100); + + rmb(); + if (!priv->lpi_enabled) { + pr_debug("%s: lpi is disabled\n", __func__); + } else if (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert) { + pr_debug("%s: Valid insertion, " + "detect plug type\n", __func__); + sitar_codec_decide_gpio_plug(codec); + } else { + pr_debug("%s: Invalid insertion, " + "stop plug detection\n", __func__); + } + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static bool is_valid_mic_voltage(struct snd_soc_codec *codec, s32 mic_mv) +{ + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + struct sitar_mbhc_plug_type_cfg *plug_type = + SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar->mbhc_cfg.calibration); + + return (!(mic_mv > SITAR_MBHC_FAKE_INSERT_LOW + && mic_mv < SITAR_MBHC_FAKE_INSERT_HIGH) + && (mic_mv > plug_type->v_no_mic) + && (mic_mv < plug_type->v_hs_max)) ? true : false; +} + +/* called under codec_resource_lock acquisition + * returns true if mic voltage range is back to normal insertion + * returns false either if timedout or removed */ +static bool sitar_hs_remove_settle(struct snd_soc_codec *codec) +{ + int i; + bool timedout, settled = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long retry = 0, timeout; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + + timeout = jiffies + msecs_to_jiffies(SITAR_HS_DETECT_PLUG_TIME_MS); + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (retry > 1) + msleep(250); + else + msleep(50); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + sitar_turn_onoff_override(codec, true); + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __sitar_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = sitar_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + sitar_turn_onoff_override(codec, false); + + if (sitar_hs_gpio_level_remove(sitar)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (!is_valid_mic_voltage(codec, mic_mv[i])) + break; + + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: MIC voltage settled\n", __func__); + settled = true; + msleep(200); + break; + } + } + + if (timedout) + pr_debug("%s: Microphone did not settle in %d seconds\n", + __func__, SITAR_HS_DETECT_PLUG_TIME_MS); + return settled; +} + +static irqreturn_t sitar_hs_remove_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter, removal interrupt\n", __func__); + + SITAR_ACQUIRE_LOCK(priv->codec_resource_lock); + if (sitar_hs_remove_settle(codec)) + sitar_codec_start_hs_polling(codec); + pr_debug("%s: remove settle done\n", __func__); + + SITAR_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + + +static unsigned long slimbus_value; + +static irqreturn_t sitar_slimbus_irq(int irq, void *data) +{ + struct sitar_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + int i, j; + u8 val; + + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) { + slimbus_value = wcd9xxx_interface_reg_read(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_STATUS0 + i); + for_each_set_bit(j, &slimbus_value, BITS_PER_BYTE) { + val = wcd9xxx_interface_reg_read(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_SOURCE0 + i*8 + j); + if (val & 0x1) + pr_err_ratelimited("overflow error on port %x," + " value %x\n", i*8 + j, val); + if (val & 0x2) + pr_err_ratelimited("underflow error on port %x," + " value %x\n", i*8 + j, val); + } + wcd9xxx_interface_reg_write(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); + } + + return IRQ_HANDLED; +} + + +static int sitar_handle_pdata(struct sitar_priv *sitar) +{ + struct snd_soc_codec *codec = sitar->codec; + struct wcd9xxx_pdata *pdata = sitar->pdata; + int k1, k2, rc = 0; + u8 leg_mode = pdata->amic_settings.legacy_mode; + u8 txfe_bypass = pdata->amic_settings.txfe_enable; + u8 txfe_buff = pdata->amic_settings.txfe_buff; + u8 flag = pdata->amic_settings.use_pdata; + u8 i = 0, j = 0; + u8 val_txfe = 0, value = 0; + + if (!pdata) { + rc = -ENODEV; + goto done; + } + + /* Make sure settings are correct */ + if ((pdata->micbias.ldoh_v > SITAR_LDOH_2P85_V) || + (pdata->micbias.bias1_cfilt_sel > SITAR_CFILT2_SEL) || + (pdata->micbias.bias2_cfilt_sel > SITAR_CFILT2_SEL)) { + rc = -EINVAL; + goto done; + } + + /* figure out k value */ + k1 = sitar_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt1_mv); + k2 = sitar_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt2_mv); + + if (IS_ERR_VALUE(k1) || IS_ERR_VALUE(k2)) { + rc = -EINVAL; + goto done; + } + + /* Set voltage level and always use LDO */ + snd_soc_update_bits(codec, SITAR_A_LDO_H_MODE_1, 0x0C, + (pdata->micbias.ldoh_v << 2)); + + snd_soc_update_bits(codec, SITAR_A_MICB_CFILT_1_VAL, 0xFC, + (k1 << 2)); + snd_soc_update_bits(codec, SITAR_A_MICB_CFILT_2_VAL, 0xFC, + (k2 << 2)); + + snd_soc_update_bits(codec, SITAR_A_MICB_1_CTL, 0x60, + (pdata->micbias.bias1_cfilt_sel << 5)); + snd_soc_update_bits(codec, SITAR_A_MICB_2_CTL, 0x60, + (pdata->micbias.bias2_cfilt_sel << 5)); + + for (i = 0; i < 6; j++, i += 2) { + if (flag & (0x01 << i)) { + value = (leg_mode & (0x01 << i)) ? 0x10 : 0x00; + val_txfe = (txfe_bypass & (0x01 << i)) ? 0x20 : 0x00; + val_txfe = val_txfe | + ((txfe_buff & (0x01 << i)) ? 0x10 : 0x00); + snd_soc_update_bits(codec, SITAR_A_TX_1_2_EN + j * 10, + 0x10, value); + snd_soc_update_bits(codec, + SITAR_A_TX_1_2_TEST_EN + j * 10, + 0x30, val_txfe); + } + if (flag & (0x01 << (i + 1))) { + value = (leg_mode & (0x01 << (i + 1))) ? 0x01 : 0x00; + val_txfe = (txfe_bypass & + (0x01 << (i + 1))) ? 0x02 : 0x00; + val_txfe |= (txfe_buff & + (0x01 << (i + 1))) ? 0x01 : 0x00; + snd_soc_update_bits(codec, SITAR_A_TX_1_2_EN + j * 10, + 0x01, value); + snd_soc_update_bits(codec, + SITAR_A_TX_1_2_TEST_EN + j * 10, + 0x03, val_txfe); + } + } + if (flag & 0x40) { + value = (leg_mode & 0x40) ? 0x10 : 0x00; + value = value | ((txfe_bypass & 0x40) ? 0x02 : 0x00); + value = value | ((txfe_buff & 0x40) ? 0x01 : 0x00); + snd_soc_update_bits(codec, SITAR_A_TX_4_MBHC_EN, + 0x13, value); + } + + + if (pdata->ocp.use_pdata) { + /* not defined in CODEC specification */ + if (pdata->ocp.hph_ocp_limit == 1 || + pdata->ocp.hph_ocp_limit == 5) { + rc = -EINVAL; + goto done; + } + snd_soc_update_bits(codec, SITAR_A_RX_COM_OCP_CTL, + 0x0F, pdata->ocp.num_attempts); + snd_soc_write(codec, SITAR_A_RX_COM_OCP_COUNT, + ((pdata->ocp.run_time << 4) | pdata->ocp.wait_time)); + snd_soc_update_bits(codec, SITAR_A_RX_HPH_OCP_CTL, + 0xE0, (pdata->ocp.hph_ocp_limit << 5)); + } +done: + return rc; +} + +static const struct sitar_reg_mask_val sitar_1_1_reg_defaults[] = { + + SITAR_REG_VAL(SITAR_A_MICB_1_INT_RBIAS, 0x24), + SITAR_REG_VAL(SITAR_A_MICB_2_INT_RBIAS, 0x24), + + SITAR_REG_VAL(SITAR_A_RX_HPH_BIAS_PA, 0x57), + SITAR_REG_VAL(SITAR_A_RX_HPH_BIAS_LDO, 0x56), + + SITAR_REG_VAL(SITAR_A_RX_EAR_BIAS_PA, 0xA6), + SITAR_REG_VAL(SITAR_A_RX_EAR_GAIN, 0x02), + SITAR_REG_VAL(SITAR_A_RX_EAR_VCM, 0x03), + + SITAR_REG_VAL(SITAR_A_CDC_RX1_B5_CTL, 0x78), + SITAR_REG_VAL(SITAR_A_CDC_RX2_B5_CTL, 0x78), + SITAR_REG_VAL(SITAR_A_CDC_RX3_B5_CTL, 0x78), + + SITAR_REG_VAL(SITAR_A_CDC_RX1_B6_CTL, 0x80), + + SITAR_REG_VAL(SITAR_A_CDC_CLSG_FREQ_THRESH_B3_CTL, 0x1B), + +}; + +static void sitar_update_reg_defaults(struct snd_soc_codec *codec) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(sitar_1_1_reg_defaults); i++) + snd_soc_write(codec, sitar_1_1_reg_defaults[i].reg, + sitar_1_1_reg_defaults[i].val); + +} +static const struct sitar_reg_mask_val sitar_codec_reg_init_val[] = { + /* Initialize current threshold to 350MA + * number of wait and run cycles to 4096 + */ + {SITAR_A_RX_HPH_OCP_CTL, 0xF8, 0x60}, + {SITAR_A_RX_COM_OCP_COUNT, 0xFF, 0xFF}, + + {SITAR_A_QFUSE_CTL, 0xFF, 0x03}, + + /* Initialize gain registers to use register gain */ + {SITAR_A_RX_HPH_L_GAIN, 0x10, 0x10}, + {SITAR_A_RX_HPH_R_GAIN, 0x10, 0x10}, + {SITAR_A_RX_LINE_1_GAIN, 0x10, 0x10}, + {SITAR_A_RX_LINE_2_GAIN, 0x10, 0x10}, + + /* Initialize mic biases to differential mode */ + {SITAR_A_MICB_1_INT_RBIAS, 0x24, 0x24}, + {SITAR_A_MICB_2_INT_RBIAS, 0x24, 0x24}, + + {SITAR_A_CDC_CONN_CLSG_CTL, 0x3C, 0x14}, + + /* Use 16 bit sample size for TX1 to TX6 */ + {SITAR_A_CDC_CONN_TX_SB_B1_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B2_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B3_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B4_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CONN_TX_SB_B5_CTL, 0x30, 0x20}, + {SITAR_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0x1, 0x1}, + + /* Use 16 bit sample size for RX */ + {SITAR_A_CDC_CONN_RX_SB_B1_CTL, 0xFF, 0xAA}, + {SITAR_A_CDC_CONN_RX_SB_B2_CTL, 0x02, 0x02}, + + /*enable HPF filter for TX paths */ + {SITAR_A_CDC_TX1_MUX_CTL, 0x8, 0x0}, + {SITAR_A_CDC_TX2_MUX_CTL, 0x8, 0x0}, + + /*enable External clock select*/ + {SITAR_A_CDC_CLK_MCLK_CTL, 0x01, 0x01}, +}; + +static void sitar_codec_init_reg(struct snd_soc_codec *codec) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(sitar_codec_reg_init_val); i++) + snd_soc_update_bits(codec, sitar_codec_reg_init_val[i].reg, + sitar_codec_reg_init_val[i].mask, + sitar_codec_reg_init_val[i].val); +} + +static int sitar_codec_probe(struct snd_soc_codec *codec) +{ + struct sitar *control; + struct sitar_priv *sitar; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret = 0; + int i; + u8 sitar_version; + int ch_cnt; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + control = codec->control_data; + + sitar = kzalloc(sizeof(struct sitar_priv), GFP_KERNEL); + if (!sitar) { + dev_err(codec->dev, "Failed to allocate private data\n"); + return -ENOMEM; + } + + /* Make sure mbhc micbias register addresses are zeroed out */ + memset(&sitar->mbhc_bias_regs, 0, + sizeof(struct mbhc_micbias_regs)); + sitar->cfilt_k_value = 0; + sitar->mbhc_micbias_switched = false; + + /* Make sure mbhc intenal calibration data is zeroed out */ + memset(&sitar->mbhc_data, 0, + sizeof(struct mbhc_internal_cal_data)); + sitar->mbhc_data.t_sta_dce = DEFAULT_DCE_STA_WAIT; + sitar->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + sitar->mbhc_data.t_sta = DEFAULT_STA_WAIT; + snd_soc_codec_set_drvdata(codec, sitar); + + sitar->mclk_enabled = false; + sitar->bandgap_type = SITAR_BANDGAP_OFF; + sitar->clock_active = false; + sitar->config_mode_active = false; + sitar->mbhc_polling_active = false; + sitar->no_mic_headset_override = false; + mutex_init(&sitar->codec_resource_lock); + sitar->codec = codec; + sitar->mbhc_state = MBHC_STATE_NONE; + sitar->mbhc_last_resume = 0; + sitar->pdata = dev_get_platdata(codec->dev->parent); + sitar_update_reg_defaults(codec); + sitar_codec_init_reg(codec); + + ret = sitar_handle_pdata(sitar); + if (IS_ERR_VALUE(ret)) { + pr_err("%s: bad pdata\n", __func__); + goto err_pdata; + } + + snd_soc_add_codec_controls(codec, sitar_snd_controls, + ARRAY_SIZE(sitar_snd_controls)); + snd_soc_dapm_new_controls(dapm, sitar_dapm_widgets, + ARRAY_SIZE(sitar_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + sitar_version = snd_soc_read(codec, WCD9XXX_A_CHIP_VERSION); + pr_info("%s : Sitar version reg 0x%2x\n", __func__, (u32)sitar_version); + + sitar_version &= 0x1F; + pr_info("%s : Sitar version %u\n", __func__, (u32)sitar_version); + + snd_soc_dapm_sync(dapm); + + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION, + sitar_hs_insert_irq, "Headset insert detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_INSERTION); + goto err_insert_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION); + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL, + sitar_hs_remove_irq, "Headset remove detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_REMOVAL); + goto err_remove_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL, + sitar_dce_handler, "DC Estimation detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_POTENTIAL); + goto err_potential_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE, + sitar_release_handler, "Button Release detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_MBHC_RELEASE); + goto err_release_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, SITAR_IRQ_SLIMBUS, + sitar_slimbus_irq, "SLIMBUS Slave", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_SLIMBUS); + goto err_slimbus_irq; + } + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) + wcd9xxx_interface_reg_write(codec->control_data, + SITAR_SLIM_PGD_PORT_INT_EN0 + i, 0xFF); + + + ret = wcd9xxx_request_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT, sitar_hphl_ocp_irq, + "HPH_L OCP detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_HPH_PA_OCPL_FAULT); + goto err_hphl_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_HPH_PA_OCPL_FAULT); + + ret = wcd9xxx_request_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPR_FAULT, sitar_hphr_ocp_irq, + "HPH_R OCP detect", sitar); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + SITAR_IRQ_HPH_PA_OCPR_FAULT); + goto err_hphr_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, SITAR_IRQ_HPH_PA_OCPR_FAULT); + + for (i = 0; i < ARRAY_SIZE(sitar_dai); i++) { + switch (sitar_dai[i].id) { + case AIF1_PB: + ch_cnt = sitar_dai[i].playback.channels_max; + break; + case AIF1_CAP: + ch_cnt = sitar_dai[i].capture.channels_max; + break; + default: + continue; + } + sitar->dai[i].ch_num = kzalloc((sizeof(unsigned int)* + ch_cnt), GFP_KERNEL); + } + + codec->ignore_pmdown_time = 1; + +#ifdef CONFIG_DEBUG_FS + debug_sitar_priv = sitar; +#endif + + return ret; + +err_hphr_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_HPH_PA_OCPL_FAULT, sitar); +err_hphl_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_SLIMBUS, sitar); +err_slimbus_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_RELEASE, sitar); +err_release_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_POTENTIAL, sitar); +err_potential_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_REMOVAL, sitar); +err_remove_irq: + wcd9xxx_free_irq(codec->control_data, + SITAR_IRQ_MBHC_INSERTION, sitar); +err_insert_irq: +err_pdata: + mutex_destroy(&sitar->codec_resource_lock); + kfree(sitar); + return ret; +} +static int sitar_codec_remove(struct snd_soc_codec *codec) +{ + int i; + struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_SLIMBUS, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_RELEASE, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_POTENTIAL, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_REMOVAL, sitar); + wcd9xxx_free_irq(codec->control_data, SITAR_IRQ_MBHC_INSERTION, sitar); + SITAR_ACQUIRE_LOCK(sitar->codec_resource_lock); + sitar_codec_disable_clock_block(codec); + SITAR_RELEASE_LOCK(sitar->codec_resource_lock); + sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF); + if (sitar->mbhc_fw) + release_firmware(sitar->mbhc_fw); + for (i = 0; i < ARRAY_SIZE(sitar_dai); i++) + kfree(sitar->dai[i].ch_num); + mutex_destroy(&sitar->codec_resource_lock); + kfree(sitar); + return 0; +} +static struct snd_soc_codec_driver soc_codec_dev_sitar = { + .probe = sitar_codec_probe, + .remove = sitar_codec_remove, + .read = sitar_read, + .write = sitar_write, + + .readable_register = sitar_readable, + .volatile_register = sitar_volatile, + + .reg_cache_size = SITAR_CACHE_SIZE, + .reg_cache_default = sitar_reg_defaults, + .reg_word_size = 1, +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_poke; + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char lbuf[32]; + char *buf; + int rc; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + buf = (char *)lbuf; + debug_sitar_priv->no_mic_headset_override = (*strsep(&buf, " ") == '0') + ? false : true; + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, +}; +#endif + +#ifdef CONFIG_PM +static int sitar_suspend(struct device *dev) +{ + dev_dbg(dev, "%s: system suspend\n", __func__); + return 0; +} + +static int sitar_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sitar_priv *sitar = platform_get_drvdata(pdev); + dev_dbg(dev, "%s: system resume\n", __func__); + sitar->mbhc_last_resume = jiffies; + return 0; +} + +static const struct dev_pm_ops sitar_pm_ops = { + .suspend = sitar_suspend, + .resume = sitar_resume, +}; +#endif + +static int sitar_probe(struct platform_device *pdev) +{ + int ret = 0; + pr_err("%s\n", __func__); +#ifdef CONFIG_DEBUG_FS + debugfs_poke = debugfs_create_file("TRRS", + S_IFREG | S_IRUGO, NULL, (void *) "TRRS", &codec_debug_ops); + +#endif + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sitar, + sitar_dai, ARRAY_SIZE(sitar_dai)); + return ret; +} +static int sitar_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_poke); +#endif + return 0; +} +static struct platform_driver sitar_codec_driver = { + .probe = sitar_probe, + .remove = sitar_remove, + .driver = { + .name = "sitar_codec", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &sitar_pm_ops, +#endif + }, +}; + +static int __init sitar_codec_init(void) +{ + return platform_driver_register(&sitar_codec_driver); +} + +static void __exit sitar_codec_exit(void) +{ + platform_driver_unregister(&sitar_codec_driver); +} + +module_init(sitar_codec_init); +module_exit(sitar_codec_exit); + +MODULE_DESCRIPTION("Sitar codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wcd9304.h b/sound/soc/codecs/wcd9304.h new file mode 100644 index 000000000000..a5120397bc7b --- /dev/null +++ b/sound/soc/codecs/wcd9304.h @@ -0,0 +1,251 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#define SITAR_NUM_REGISTERS 0x400 +#define SITAR_MAX_REGISTER (SITAR_NUM_REGISTERS-1) +#define SITAR_CACHE_SIZE SITAR_NUM_REGISTERS +#define SITAR_1_X_ONLY_REGISTERS 3 +#define SITAR_2_HIGHER_ONLY_REGISTERS 3 + +#define SITAR_REG_VAL(reg, val) {reg, 0, val} + +#define DEFAULT_DCE_STA_WAIT 55 +#define DEFAULT_DCE_WAIT 60000 +#define DEFAULT_STA_WAIT 5000 + +#define STA 0 +#define DCE 1 + +#define SITAR_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ + SND_JACK_BTN_4 | SND_JACK_BTN_5 | \ + SND_JACK_BTN_6 | SND_JACK_BTN_7) + +extern const u8 sitar_reg_readable[SITAR_CACHE_SIZE]; +extern const u32 sitar_1_reg_readable[SITAR_1_X_ONLY_REGISTERS]; +extern const u32 sitar_2_reg_readable[SITAR_2_HIGHER_ONLY_REGISTERS]; +extern const u8 sitar_reg_defaults[SITAR_CACHE_SIZE]; + +enum sitar_micbias_num { + SITAR_MICBIAS1, + SITAR_MICBIAS2, + SITAR_MICBIAS3, + SITAR_MICBIAS4, +}; + +enum sitar_pid_current { + SITAR_PID_MIC_2P5_UA, + SITAR_PID_MIC_5_UA, + SITAR_PID_MIC_10_UA, + SITAR_PID_MIC_20_UA, +}; + +struct sitar_reg_mask_val { + u16 reg; + u8 mask; + u8 val; +}; + +enum sitar_mbhc_clk_freq { + SITAR_MCLK_12P2MHZ = 0, + SITAR_MCLK_9P6MHZ, + SITAR_NUM_CLK_FREQS, +}; + +enum sitar_mbhc_analog_pwr_cfg { + SITAR_ANALOG_PWR_COLLAPSED = 0, + SITAR_ANALOG_PWR_ON, + SITAR_NUM_ANALOG_PWR_CONFIGS, +}; + +enum sitar_mbhc_btn_det_mem { + SITAR_BTN_DET_V_BTN_LOW, + SITAR_BTN_DET_V_BTN_HIGH, + SITAR_BTN_DET_N_READY, + SITAR_BTN_DET_N_CIC, + SITAR_BTN_DET_GAIN +}; + +struct sitar_mbhc_general_cfg { + u8 t_ldoh; + u8 t_bg_fast_settle; + u8 t_shutdown_plug_rem; + u8 mbhc_nsa; + u8 mbhc_navg; + u8 v_micbias_l; + u8 v_micbias; + u8 mbhc_reserved; + u16 settle_wait; + u16 t_micbias_rampup; + u16 t_micbias_rampdown; + u16 t_supply_bringup; +} __packed; + +struct sitar_mbhc_plug_detect_cfg { + u32 mic_current; + u32 hph_current; + u16 t_mic_pid; + u16 t_ins_complete; + u16 t_ins_retry; + u16 v_removal_delta; + u8 micbias_slow_ramp; + u8 reserved0; + u8 reserved1; + u8 reserved2; +} __packed; + +struct sitar_mbhc_plug_type_cfg { + u8 av_detect; + u8 mono_detect; + u8 num_ins_tries; + u8 reserved0; + s16 v_no_mic; + s16 v_av_min; + s16 v_av_max; + s16 v_hs_min; + s16 v_hs_max; + u16 reserved1; +} __packed; + + +struct sitar_mbhc_btn_detect_cfg { + s8 c[8]; + u8 nc; + u8 n_meas; + u8 mbhc_nsc; + u8 n_btn_meas; + u8 n_btn_con; + u8 num_btn; + u8 reserved0; + u8 reserved1; + u16 t_poll; + u16 t_bounce_wait; + u16 t_rel_timeout; + s16 v_btn_press_delta_sta; + s16 v_btn_press_delta_cic; + u16 t_btn0_timeout; + s16 _v_btn_low[0]; /* v_btn_low[num_btn] */ + s16 _v_btn_high[0]; /* v_btn_high[num_btn] */ + u8 _n_ready[SITAR_NUM_CLK_FREQS]; + u8 _n_cic[SITAR_NUM_CLK_FREQS]; + u8 _gain[SITAR_NUM_CLK_FREQS]; +} __packed; + +struct sitar_mbhc_imped_detect_cfg { + u8 _hs_imped_detect; + u8 _n_rload; + u8 _hph_keep_on; + u8 _repeat_rload_calc; + u16 _t_dac_ramp_time; + u16 _rhph_high; + u16 _rhph_low; + u16 _rload[0]; /* rload[n_rload] */ + u16 _alpha[0]; /* alpha[n_rload] */ + u16 _beta[3]; +} __packed; + +struct sitar_mbhc_config { + struct snd_soc_jack *headset_jack; + struct snd_soc_jack *button_jack; + bool read_fw_bin; + /* void* calibration contains: + * struct tabla_mbhc_general_cfg generic; + * struct tabla_mbhc_plug_detect_cfg plug_det; + * struct tabla_mbhc_plug_type_cfg plug_type; + * struct tabla_mbhc_btn_detect_cfg btn_det; + * struct tabla_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + enum sitar_micbias_num micbias; + int (*mclk_cb_fn) (struct snd_soc_codec*, int, bool); + unsigned int mclk_rate; + unsigned int gpio; + unsigned int gpio_irq; + int gpio_level_insert; +}; + +extern int sitar_hs_detect(struct snd_soc_codec *codec, + const struct sitar_mbhc_config *cfg); + +#ifndef anc_header_dec +struct anc_header { + u32 reserved[3]; + u32 num_anc_slots; +}; +#define anc_header_dec +#endif + +extern int sitar_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, + bool dapm); + +extern void *sitar_mbhc_cal_btn_det_mp(const struct sitar_mbhc_btn_detect_cfg + *btn_det, + const enum sitar_mbhc_btn_det_mem mem); + +#define SITAR_MBHC_CAL_SIZE(buttons, rload) ( \ + sizeof(enum sitar_micbias_num) + \ + sizeof(struct sitar_mbhc_general_cfg) + \ + sizeof(struct sitar_mbhc_plug_detect_cfg) + \ + ((sizeof(s16) + sizeof(s16)) * buttons) + \ + sizeof(struct sitar_mbhc_plug_type_cfg) + \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + ((sizeof(u16) + sizeof(u16)) * rload) \ + ) + +#define SITAR_MBHC_CAL_GENERAL_PTR(cali) ( \ + (struct sitar_mbhc_general_cfg *) cali) +#define SITAR_MBHC_CAL_PLUG_DET_PTR(cali) ( \ + (struct sitar_mbhc_plug_detect_cfg *) \ + &(SITAR_MBHC_CAL_GENERAL_PTR(cali)[1])) +#define SITAR_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \ + (struct sitar_mbhc_plug_type_cfg *) \ + &(SITAR_MBHC_CAL_PLUG_DET_PTR(cali)[1])) +#define SITAR_MBHC_CAL_BTN_DET_PTR(cali) ( \ + (struct sitar_mbhc_btn_detect_cfg *) \ + &(SITAR_MBHC_CAL_PLUG_TYPE_PTR(cali)[1])) +#define SITAR_MBHC_CAL_IMPED_DET_PTR(cali) ( \ + (struct sitar_mbhc_imped_detect_cfg *) \ + (((void *)&SITAR_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \ + (SITAR_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \ + (sizeof(SITAR_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \ + sizeof(SITAR_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \ + ) + +/* minimum size of calibration data assuming there is only one button and + * one rload. + */ +#define SITAR_MBHC_CAL_MIN_SIZE ( \ + sizeof(struct sitar_mbhc_general_cfg) + \ + sizeof(struct sitar_mbhc_plug_detect_cfg) + \ + sizeof(struct sitar_mbhc_plug_type_cfg) + \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + (sizeof(u16) * 2)) + +#define SITAR_MBHC_CAL_BTN_SZ(cfg_ptr) ( \ + sizeof(struct sitar_mbhc_btn_detect_cfg) + \ + (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \ + sizeof(cfg_ptr->_v_btn_high[0])))) + +#define SITAR_MBHC_CAL_IMPED_MIN_SZ ( \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + sizeof(u16) * 2) + +#define SITAR_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \ + sizeof(struct sitar_mbhc_imped_detect_cfg) + \ + (cfg_ptr->_n_rload * (sizeof(cfg_ptr->_rload[0]) + \ + sizeof(cfg_ptr->_alpha[0])))) diff --git a/sound/soc/codecs/wcd9310-tables.c b/sound/soc/codecs/wcd9310-tables.c new file mode 100644 index 000000000000..b7cea9b63992 --- /dev/null +++ b/sound/soc/codecs/wcd9310-tables.c @@ -0,0 +1,1114 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "wcd9310.h" + +const u8 tabla_reg_readable[TABLA_CACHE_SIZE] = { + [TABLA_A_CHIP_CTL] = 1, + [TABLA_A_CHIP_STATUS] = 1, + [TABLA_A_CHIP_ID_BYTE_0] = 1, + [TABLA_A_CHIP_ID_BYTE_1] = 1, + [TABLA_A_CHIP_ID_BYTE_2] = 1, + [TABLA_A_CHIP_ID_BYTE_3] = 1, + [TABLA_A_CHIP_VERSION] = 1, + [TABLA_A_SB_VERSION] = 1, + [TABLA_A_SLAVE_ID_1] = 1, + [TABLA_A_SLAVE_ID_2] = 1, + [TABLA_A_SLAVE_ID_3] = 1, + [TABLA_A_PIN_CTL_OE0] = 1, + [TABLA_A_PIN_CTL_OE1] = 1, + [TABLA_A_PIN_CTL_DATA0] = 1, + [TABLA_A_PIN_CTL_DATA1] = 1, + [TABLA_A_HDRIVE_GENERIC] = 1, + [TABLA_A_HDRIVE_OVERRIDE] = 1, + [TABLA_A_ANA_CSR_WAIT_STATE] = 1, + [TABLA_A_PROCESS_MONITOR_CTL0] = 1, + [TABLA_A_PROCESS_MONITOR_CTL1] = 1, + [TABLA_A_PROCESS_MONITOR_CTL2] = 1, + [TABLA_A_PROCESS_MONITOR_CTL3] = 1, + [TABLA_A_QFUSE_CTL] = 1, + [TABLA_A_QFUSE_STATUS] = 1, + [TABLA_A_QFUSE_DATA_OUT0] = 1, + [TABLA_A_QFUSE_DATA_OUT1] = 1, + [TABLA_A_QFUSE_DATA_OUT2] = 1, + [TABLA_A_QFUSE_DATA_OUT3] = 1, + [TABLA_A_CDC_CTL] = 1, + [TABLA_A_LEAKAGE_CTL] = 1, + [TABLA_A_INTR_MODE] = 1, + [TABLA_A_INTR_MASK0] = 1, + [TABLA_A_INTR_MASK1] = 1, + [TABLA_A_INTR_MASK2] = 1, + [TABLA_A_INTR_STATUS0] = 1, + [TABLA_A_INTR_STATUS1] = 1, + [TABLA_A_INTR_STATUS2] = 1, + [TABLA_A_INTR_CLEAR0] = 0, + [TABLA_A_INTR_CLEAR1] = 0, + [TABLA_A_INTR_CLEAR2] = 0, + [TABLA_A_INTR_LEVEL0] = 1, + [TABLA_A_INTR_LEVEL1] = 1, + [TABLA_A_INTR_LEVEL2] = 1, + [TABLA_A_INTR_TEST0] = 1, + [TABLA_A_INTR_TEST1] = 1, + [TABLA_A_INTR_TEST2] = 1, + [TABLA_A_INTR_SET0] = 1, + [TABLA_A_INTR_SET1] = 1, + [TABLA_A_INTR_SET2] = 1, + [TABLA_A_CDC_TX_I2S_SCK_MODE] = 1, + [TABLA_A_CDC_TX_I2S_WS_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA0_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK0_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA1_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK1_MODE] = 1, + [TABLA_A_CDC_RX_I2S_SCK_MODE] = 1, + [TABLA_A_CDC_RX_I2S_WS_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA2_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK2_MODE] = 1, + [TABLA_A_CDC_INTR_MODE] = 1, + [TABLA_A_BIAS_REF_CTL] = 1, + [TABLA_A_BIAS_CENTRAL_BG_CTL] = 1, + [TABLA_A_BIAS_PRECHRG_CTL] = 1, + [TABLA_A_BIAS_CURR_CTL_1] = 1, + [TABLA_A_BIAS_CURR_CTL_2] = 1, + [TABLA_A_BIAS_CONFIG_MODE_BG_CTL] = 1, + [TABLA_A_BIAS_BG_STATUS] = 1, + [TABLA_A_CLK_BUFF_EN1] = 1, + [TABLA_A_CLK_BUFF_EN2] = 1, + [TABLA_A_LDO_H_MODE_1] = 1, + [TABLA_A_LDO_H_MODE_2] = 1, + [TABLA_A_LDO_H_LOOP_CTL] = 1, + [TABLA_A_LDO_H_COMP_1] = 1, + [TABLA_A_LDO_H_COMP_2] = 1, + [TABLA_A_LDO_H_BIAS_1] = 1, + [TABLA_A_LDO_H_BIAS_2] = 1, + [TABLA_A_LDO_H_BIAS_3] = 1, + [TABLA_A_LDO_L_MODE_1] = 1, + [TABLA_A_LDO_L_MODE_2] = 1, + [TABLA_A_LDO_L_LOOP_CTL] = 1, + [TABLA_A_LDO_L_COMP_1] = 1, + [TABLA_A_LDO_L_COMP_2] = 1, + [TABLA_A_LDO_L_BIAS_1] = 1, + [TABLA_A_LDO_L_BIAS_2] = 1, + [TABLA_A_LDO_L_BIAS_3] = 1, + [TABLA_A_MICB_CFILT_1_CTL] = 1, + [TABLA_A_MICB_CFILT_1_VAL] = 1, + [TABLA_A_MICB_CFILT_1_PRECHRG] = 1, + [TABLA_A_MICB_1_CTL] = 1, + [TABLA_A_MICB_1_INT_RBIAS] = 1, + [TABLA_A_MICB_1_MBHC] = 1, + [TABLA_A_MICB_CFILT_2_CTL] = 1, + [TABLA_A_MICB_CFILT_2_VAL] = 1, + [TABLA_A_MICB_CFILT_2_PRECHRG] = 1, + [TABLA_A_MICB_2_CTL] = 1, + [TABLA_A_MICB_2_INT_RBIAS] = 1, + [TABLA_A_MICB_2_MBHC] = 1, + [TABLA_A_MICB_CFILT_3_CTL] = 1, + [TABLA_A_MICB_CFILT_3_VAL] = 1, + [TABLA_A_MICB_CFILT_3_PRECHRG] = 1, + [TABLA_A_MICB_3_CTL] = 1, + [TABLA_A_MICB_3_INT_RBIAS] = 1, + [TABLA_A_MICB_3_MBHC] = 1, + [TABLA_A_TX_COM_BIAS] = 1, + [TABLA_A_MBHC_SCALING_MUX_1] = 1, + [TABLA_A_MBHC_SCALING_MUX_2] = 1, + [TABLA_A_TX_SUP_SWITCH_CTRL_1] = 1, + [TABLA_A_TX_SUP_SWITCH_CTRL_2] = 1, + [TABLA_A_TX_1_2_EN] = 1, + [TABLA_A_TX_1_2_TEST_EN] = 1, + [TABLA_A_TX_1_2_ADC_CH1] = 1, + [TABLA_A_TX_1_2_ADC_CH2] = 1, + [TABLA_A_TX_1_2_ATEST_REFCTRL] = 1, + [TABLA_A_TX_1_2_TEST_CTL] = 1, + [TABLA_A_TX_1_2_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_1_2_TXFE_CLKDIV] = 1, + [TABLA_A_TX_1_2_SAR_ERR_CH1] = 1, + [TABLA_A_TX_1_2_SAR_ERR_CH2] = 1, + [TABLA_A_TX_3_4_EN] = 1, + [TABLA_A_TX_3_4_TEST_EN] = 1, + [TABLA_A_TX_3_4_ADC_CH3] = 1, + [TABLA_A_TX_3_4_ADC_CH4] = 1, + [TABLA_A_TX_3_4_ATEST_REFCTRL] = 1, + [TABLA_A_TX_3_4_TEST_CTL] = 1, + [TABLA_A_TX_3_4_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_3_4_TXFE_CKDIV] = 1, + [TABLA_A_TX_3_4_SAR_ERR_CH3] = 1, + [TABLA_A_TX_3_4_SAR_ERR_CH4] = 1, + [TABLA_A_TX_5_6_EN] = 1, + [TABLA_A_TX_5_6_TEST_EN] = 1, + [TABLA_A_TX_5_6_ADC_CH5] = 1, + [TABLA_A_TX_5_6_ADC_CH6] = 1, + [TABLA_A_TX_5_6_ATEST_REFCTRL] = 1, + [TABLA_A_TX_5_6_TEST_CTL] = 1, + [TABLA_A_TX_5_6_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_5_6_TXFE_CKDIV] = 1, + [TABLA_A_TX_5_6_SAR_ERR_CH5] = 1, + [TABLA_A_TX_5_6_SAR_ERR_CH6] = 1, + [TABLA_A_TX_7_MBHC_EN] = 1, + [TABLA_A_TX_7_MBHC_ATEST_REFCTRL] = 1, + [TABLA_A_TX_7_MBHC_ADC] = 1, + [TABLA_A_TX_7_MBHC_TEST_CTL] = 1, + [TABLA_A_TX_7_MBHC_SAR_ERR] = 1, + [TABLA_A_TX_7_TXFE_CLKDIV] = 1, + [TABLA_A_AUX_COM_CTL] = 1, + [TABLA_A_AUX_COM_ATEST] = 1, + [TABLA_A_AUX_L_EN] = 1, + [TABLA_A_AUX_L_GAIN] = 1, + [TABLA_A_AUX_L_PA_CONN] = 1, + [TABLA_A_AUX_L_PA_CONN_INV] = 1, + [TABLA_A_AUX_R_EN] = 1, + [TABLA_A_AUX_R_GAIN] = 1, + [TABLA_A_AUX_R_PA_CONN] = 1, + [TABLA_A_AUX_R_PA_CONN_INV] = 1, + [TABLA_A_CP_EN] = 1, + [TABLA_A_CP_CLK] = 1, + [TABLA_A_CP_STATIC] = 1, + [TABLA_A_CP_DCC1] = 1, + [TABLA_A_CP_DCC3] = 1, + [TABLA_A_CP_ATEST] = 1, + [TABLA_A_CP_DTEST] = 1, + [TABLA_A_RX_COM_TIMER_DIV] = 1, + [TABLA_A_RX_COM_OCP_CTL] = 1, + [TABLA_A_RX_COM_OCP_COUNT] = 1, + [TABLA_A_RX_COM_DAC_CTL] = 1, + [TABLA_A_RX_COM_BIAS] = 1, + [TABLA_A_RX_HPH_BIAS_PA] = 1, + [TABLA_A_RX_HPH_BIAS_LDO] = 1, + [TABLA_A_RX_HPH_BIAS_CNP] = 1, + [TABLA_A_RX_HPH_BIAS_WG] = 1, + [TABLA_A_RX_HPH_OCP_CTL] = 1, + [TABLA_A_RX_HPH_CNP_EN] = 1, + [TABLA_A_RX_HPH_CNP_WG_CTL] = 1, + [TABLA_A_RX_HPH_CNP_WG_TIME] = 1, + [TABLA_A_RX_HPH_L_GAIN] = 1, + [TABLA_A_RX_HPH_L_TEST] = 1, + [TABLA_A_RX_HPH_L_PA_CTL] = 1, + [TABLA_A_RX_HPH_L_DAC_CTL] = 1, + [TABLA_A_RX_HPH_L_ATEST] = 1, + [TABLA_A_RX_HPH_L_STATUS] = 1, + [TABLA_A_RX_HPH_R_GAIN] = 1, + [TABLA_A_RX_HPH_R_TEST] = 1, + [TABLA_A_RX_HPH_R_PA_CTL] = 1, + [TABLA_A_RX_HPH_R_DAC_CTL] = 1, + [TABLA_A_RX_HPH_R_ATEST] = 1, + [TABLA_A_RX_HPH_R_STATUS] = 1, + [TABLA_A_RX_EAR_BIAS_PA] = 1, + [TABLA_A_RX_EAR_BIAS_CMBUFF] = 1, + [TABLA_A_RX_EAR_EN] = 1, + [TABLA_A_RX_EAR_GAIN] = 1, + [TABLA_A_RX_EAR_CMBUFF] = 1, + [TABLA_A_RX_EAR_ICTL] = 1, + [TABLA_A_RX_EAR_CCOMP] = 1, + [TABLA_A_RX_EAR_VCM] = 1, + [TABLA_A_RX_EAR_CNP] = 1, + [TABLA_A_RX_EAR_ATEST] = 1, + [TABLA_A_RX_EAR_STATUS] = 1, + [TABLA_A_RX_LINE_BIAS_PA] = 1, + [TABLA_A_RX_LINE_BIAS_DAC] = 1, + [TABLA_A_RX_LINE_BIAS_CNP] = 1, + [TABLA_A_RX_LINE_COM] = 1, + [TABLA_A_RX_LINE_CNP_EN] = 1, + [TABLA_A_RX_LINE_CNP_WG_CTL] = 1, + [TABLA_A_RX_LINE_CNP_WG_TIME] = 1, + [TABLA_A_RX_LINE_1_GAIN] = 1, + [TABLA_A_RX_LINE_1_TEST] = 1, + [TABLA_A_RX_LINE_1_DAC_CTL] = 1, + [TABLA_A_RX_LINE_1_STATUS] = 1, + [TABLA_A_RX_LINE_2_GAIN] = 1, + [TABLA_A_RX_LINE_2_TEST] = 1, + [TABLA_A_RX_LINE_2_DAC_CTL] = 1, + [TABLA_A_RX_LINE_2_STATUS] = 1, + [TABLA_A_RX_LINE_3_GAIN] = 1, + [TABLA_A_RX_LINE_3_TEST] = 1, + [TABLA_A_RX_LINE_3_DAC_CTL] = 1, + [TABLA_A_RX_LINE_3_STATUS] = 1, + [TABLA_A_RX_LINE_4_GAIN] = 1, + [TABLA_A_RX_LINE_4_TEST] = 1, + [TABLA_A_RX_LINE_4_DAC_CTL] = 1, + [TABLA_A_RX_LINE_4_STATUS] = 1, + [TABLA_A_RX_LINE_5_GAIN] = 1, + [TABLA_A_RX_LINE_5_TEST] = 1, + [TABLA_A_RX_LINE_5_DAC_CTL] = 1, + [TABLA_A_RX_LINE_5_STATUS] = 1, + [TABLA_A_RX_LINE_CNP_DBG] = 1, + [TABLA_A_MBHC_HPH] = 1, + [TABLA_A_CONFIG_MODE_FREQ] = 1, + [TABLA_A_CONFIG_MODE_TEST] = 1, + [TABLA_A_CONFIG_MODE_STATUS] = 1, + [TABLA_A_CONFIG_MODE_TUNER] = 1, + [TABLA_A_CDC_ANC1_CTL] = 1, + [TABLA_A_CDC_ANC1_SHIFT] = 1, + [TABLA_A_CDC_ANC1_FILT1_B1_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B2_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B3_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT1_B4_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B1_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B2_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT2_B3_CTL] = 1, + [TABLA_A_CDC_ANC1_SPARE] = 1, + [TABLA_A_CDC_ANC1_FILT3_CTL] = 1, + [TABLA_A_CDC_ANC1_FILT4_CTL] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX1_MUX_CTL] = 1, + [TABLA_A_CDC_TX2_MUX_CTL] = 1, + [TABLA_A_CDC_TX3_MUX_CTL] = 1, + [TABLA_A_CDC_TX4_MUX_CTL] = 1, + [TABLA_A_CDC_TX5_MUX_CTL] = 1, + [TABLA_A_CDC_TX6_MUX_CTL] = 1, + [TABLA_A_CDC_TX7_MUX_CTL] = 1, + [TABLA_A_CDC_TX8_MUX_CTL] = 1, + [TABLA_A_CDC_TX9_MUX_CTL] = 1, + [TABLA_A_CDC_TX10_MUX_CTL] = 1, + [TABLA_A_CDC_TX1_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX2_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX3_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX4_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX5_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX6_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX7_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX8_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX9_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX10_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX1_DMIC_CTL] = 1, + [TABLA_A_CDC_TX2_DMIC_CTL] = 1, + [TABLA_A_CDC_TX3_DMIC_CTL] = 1, + [TABLA_A_CDC_TX4_DMIC_CTL] = 1, + [TABLA_A_CDC_TX5_DMIC_CTL] = 1, + [TABLA_A_CDC_TX6_DMIC_CTL] = 1, + [TABLA_A_CDC_TX7_DMIC_CTL] = 1, + [TABLA_A_CDC_TX8_DMIC_CTL] = 1, + [TABLA_A_CDC_TX9_DMIC_CTL] = 1, + [TABLA_A_CDC_TX10_DMIC_CTL] = 1, + [TABLA_A_CDC_ANC2_CTL] = 1, + [TABLA_A_CDC_ANC2_SHIFT] = 1, + [TABLA_A_CDC_ANC2_FILT1_B1_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B2_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B3_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT1_B4_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B1_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B2_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT2_B3_CTL] = 1, + [TABLA_A_CDC_ANC2_SPARE] = 1, + [TABLA_A_CDC_ANC2_FILT3_CTL] = 1, + [TABLA_A_CDC_ANC2_FILT4_CTL] = 1, + [TABLA_A_CDC_SRC1_PDA_CFG] = 1, + [TABLA_A_CDC_SRC2_PDA_CFG] = 1, + [TABLA_A_CDC_SRC1_FS_CTL] = 1, + [TABLA_A_CDC_SRC2_FS_CTL] = 1, + [TABLA_A_CDC_RX1_B1_CTL] = 1, + [TABLA_A_CDC_RX2_B1_CTL] = 1, + [TABLA_A_CDC_RX3_B1_CTL] = 1, + [TABLA_A_CDC_RX4_B1_CTL] = 1, + [TABLA_A_CDC_RX5_B1_CTL] = 1, + [TABLA_A_CDC_RX6_B1_CTL] = 1, + [TABLA_A_CDC_RX7_B1_CTL] = 1, + [TABLA_A_CDC_RX1_B2_CTL] = 1, + [TABLA_A_CDC_RX2_B2_CTL] = 1, + [TABLA_A_CDC_RX3_B2_CTL] = 1, + [TABLA_A_CDC_RX4_B2_CTL] = 1, + [TABLA_A_CDC_RX5_B2_CTL] = 1, + [TABLA_A_CDC_RX6_B2_CTL] = 1, + [TABLA_A_CDC_RX7_B2_CTL] = 1, + [TABLA_A_CDC_RX1_B3_CTL] = 1, + [TABLA_A_CDC_RX2_B3_CTL] = 1, + [TABLA_A_CDC_RX3_B3_CTL] = 1, + [TABLA_A_CDC_RX4_B3_CTL] = 1, + [TABLA_A_CDC_RX5_B3_CTL] = 1, + [TABLA_A_CDC_RX6_B3_CTL] = 1, + [TABLA_A_CDC_RX7_B3_CTL] = 1, + [TABLA_A_CDC_RX1_B4_CTL] = 1, + [TABLA_A_CDC_RX2_B4_CTL] = 1, + [TABLA_A_CDC_RX3_B4_CTL] = 1, + [TABLA_A_CDC_RX4_B4_CTL] = 1, + [TABLA_A_CDC_RX5_B4_CTL] = 1, + [TABLA_A_CDC_RX6_B4_CTL] = 1, + [TABLA_A_CDC_RX7_B4_CTL] = 1, + [TABLA_A_CDC_RX1_B5_CTL] = 1, + [TABLA_A_CDC_RX2_B5_CTL] = 1, + [TABLA_A_CDC_RX3_B5_CTL] = 1, + [TABLA_A_CDC_RX4_B5_CTL] = 1, + [TABLA_A_CDC_RX5_B5_CTL] = 1, + [TABLA_A_CDC_RX6_B5_CTL] = 1, + [TABLA_A_CDC_RX7_B5_CTL] = 1, + [TABLA_A_CDC_RX1_B6_CTL] = 1, + [TABLA_A_CDC_RX2_B6_CTL] = 1, + [TABLA_A_CDC_RX3_B6_CTL] = 1, + [TABLA_A_CDC_RX4_B6_CTL] = 1, + [TABLA_A_CDC_RX5_B6_CTL] = 1, + [TABLA_A_CDC_RX6_B6_CTL] = 1, + [TABLA_A_CDC_RX7_B6_CTL] = 1, + [TABLA_A_CDC_RX1_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX2_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX3_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX4_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX5_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX6_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX7_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX1_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX2_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX3_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX4_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX5_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX6_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX7_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_CLK_ANC_RESET_CTL] = 1, + [TABLA_A_CDC_CLK_RX_RESET_CTL] = 1, + [TABLA_A_CDC_CLK_TX_RESET_B1_CTL] = 1, + [TABLA_A_CDC_CLK_TX_RESET_B2_CTL] = 1, + [TABLA_A_CDC_CLK_DMIC_CTL] = 1, + [TABLA_A_CDC_CLK_RX_I2S_CTL] = 1, + [TABLA_A_CDC_CLK_TX_I2S_CTL] = 1, + [TABLA_A_CDC_CLK_OTHR_RESET_CTL] = 1, + [TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL] = 1, + [TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL] = 1, + [TABLA_A_CDC_CLK_OTHR_CTL] = 1, + [TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL] = 1, + [TABLA_A_CDC_CLK_ANC_CLK_EN_CTL] = 1, + [TABLA_A_CDC_CLK_RX_B1_CTL] = 1, + [TABLA_A_CDC_CLK_RX_B2_CTL] = 1, + [TABLA_A_CDC_CLK_MCLK_CTL] = 1, + [TABLA_A_CDC_CLK_PDM_CTL] = 1, + [TABLA_A_CDC_CLK_SD_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = 1, + [TABLA_A_CDC_CLSG_GAIN_THRESH_CTL] = 1, + [TABLA_A_CDC_CLSG_TIMER_B1_CFG] = 1, + [TABLA_A_CDC_CLSG_TIMER_B2_CFG] = 1, + [TABLA_A_CDC_CLSG_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B1_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B1_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B2_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B2_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B3_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B3_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B4_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B4_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B5_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B5_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B6_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B6_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B7_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B7_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B8_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B8_CTL] = 1, + [TABLA_A_CDC_IIR1_CTL] = 1, + [TABLA_A_CDC_IIR2_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_TIMER_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_TIMER_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B1_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B1_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B2_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B2_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B3_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B3_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B4_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B4_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B5_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B5_CTL] = 1, + [TABLA_A_CDC_TOP_GAIN_UPDATE] = 1, + [TABLA_A_CDC_DEBUG_B1_CTL] = 1, + [TABLA_A_CDC_DEBUG_B2_CTL] = 1, + [TABLA_A_CDC_DEBUG_B3_CTL] = 1, + [TABLA_A_CDC_DEBUG_B4_CTL] = 1, + [TABLA_A_CDC_DEBUG_B5_CTL] = 1, + [TABLA_A_CDC_DEBUG_B6_CTL] = 1, + [TABLA_A_CDC_COMP1_B1_CTL] = 1, + [TABLA_A_CDC_COMP1_B2_CTL] = 1, + [TABLA_A_CDC_COMP1_B3_CTL] = 1, + [TABLA_A_CDC_COMP1_B4_CTL] = 1, + [TABLA_A_CDC_COMP1_B5_CTL] = 1, + [TABLA_A_CDC_COMP1_B6_CTL] = 1, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = 1, + [TABLA_A_CDC_COMP1_FS_CFG] = 1, + [TABLA_A_CDC_COMP2_B1_CTL] = 1, + [TABLA_A_CDC_COMP2_B2_CTL] = 1, + [TABLA_A_CDC_COMP2_B3_CTL] = 1, + [TABLA_A_CDC_COMP2_B4_CTL] = 1, + [TABLA_A_CDC_COMP2_B5_CTL] = 1, + [TABLA_A_CDC_COMP2_B6_CTL] = 1, + [TABLA_A_CDC_COMP2_SHUT_DOWN_STATUS] = 1, + [TABLA_A_CDC_COMP2_FS_CFG] = 1, + [TABLA_A_CDC_CONN_RX1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX1_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX4_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX4_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX5_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX5_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX6_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX6_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX7_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX7_B2_CTL] = 1, + [TABLA_A_CDC_CONN_ANC_B1_CTL] = 1, + [TABLA_A_CDC_CONN_ANC_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B1_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B3_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B4_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B3_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B4_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B3_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B4_CTL] = 1, + [TABLA_A_CDC_CONN_SRC1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_SRC1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_SRC2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_SRC2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B1_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B3_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B4_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B5_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B6_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B7_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B8_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B9_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B10_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B11_CTL] = 1, + [TABLA_A_CDC_CONN_RX_SB_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX_SB_B2_CTL] = 1, + [TABLA_A_CDC_CONN_CLSG_CTL] = 1, + [TABLA_A_CDC_CONN_SPARE] = 1, + [TABLA_A_CDC_MBHC_EN_CTL] = 1, + [TABLA_A_CDC_MBHC_FEATURE_B1_CFG] = 1, + [TABLA_A_CDC_MBHC_FEATURE_B2_CFG] = 1, + [TABLA_A_CDC_MBHC_TIMER_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B3_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B4_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B5_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B6_CTL] = 1, + [TABLA_A_CDC_MBHC_B1_STATUS] = 1, + [TABLA_A_CDC_MBHC_B2_STATUS] = 1, + [TABLA_A_CDC_MBHC_B3_STATUS] = 1, + [TABLA_A_CDC_MBHC_B4_STATUS] = 1, + [TABLA_A_CDC_MBHC_B5_STATUS] = 1, + [TABLA_A_CDC_MBHC_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B3_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B4_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B5_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B6_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B7_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B8_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B9_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B10_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B11_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B12_CTL] = 1, + [TABLA_A_CDC_MBHC_CLK_CTL] = 1, + [TABLA_A_CDC_MBHC_INT_CTL] = 1, + [TABLA_A_CDC_MBHC_DEBUG_CTL] = 1, + [TABLA_A_CDC_MBHC_SPARE] = 1, +}; + +const unsigned int tabla_1_reg_readable[TABLA_1_X_ONLY_REGISTERS] = { + TABLA_1_A_MICB_4_CTL, + TABLA_1_A_MICB_4_INT_RBIAS, + TABLA_1_A_MICB_4_MBHC, +}; + +const unsigned int tabla_2_reg_readable[TABLA_2_HIGHER_ONLY_REGISTERS] = { + TABLA_2_A_MICB_4_CTL, + TABLA_2_A_MICB_4_INT_RBIAS, + TABLA_2_A_MICB_4_MBHC, +}; + +const u8 tabla_reg_defaults[TABLA_CACHE_SIZE] = { + [TABLA_A_CHIP_CTL] = TABLA_A_CHIP_CTL__POR, + [TABLA_A_CHIP_STATUS] = TABLA_A_CHIP_STATUS__POR, + [TABLA_A_CHIP_ID_BYTE_0] = TABLA_A_CHIP_ID_BYTE_0__POR, + [TABLA_A_CHIP_ID_BYTE_1] = TABLA_A_CHIP_ID_BYTE_1__POR, + [TABLA_A_CHIP_ID_BYTE_2] = TABLA_A_CHIP_ID_BYTE_2__POR, + [TABLA_A_CHIP_ID_BYTE_3] = TABLA_A_CHIP_ID_BYTE_3__POR, + [TABLA_A_CHIP_VERSION] = TABLA_A_CHIP_VERSION__POR, + [TABLA_A_SB_VERSION] = TABLA_A_SB_VERSION__POR, + [TABLA_A_SLAVE_ID_1] = TABLA_A_SLAVE_ID_1__POR, + [TABLA_A_SLAVE_ID_2] = TABLA_A_SLAVE_ID_2__POR, + [TABLA_A_SLAVE_ID_3] = TABLA_A_SLAVE_ID_3__POR, + [TABLA_A_PIN_CTL_OE0] = TABLA_A_PIN_CTL_OE0__POR, + [TABLA_A_PIN_CTL_OE1] = TABLA_A_PIN_CTL_OE1__POR, + [TABLA_A_PIN_CTL_DATA0] = TABLA_A_PIN_CTL_DATA0__POR, + [TABLA_A_PIN_CTL_DATA1] = TABLA_A_PIN_CTL_DATA1__POR, + [TABLA_A_HDRIVE_GENERIC] = TABLA_A_HDRIVE_GENERIC__POR, + [TABLA_A_HDRIVE_OVERRIDE] = TABLA_A_HDRIVE_OVERRIDE__POR, + [TABLA_A_ANA_CSR_WAIT_STATE] = TABLA_A_ANA_CSR_WAIT_STATE__POR, + [TABLA_A_PROCESS_MONITOR_CTL0] = TABLA_A_PROCESS_MONITOR_CTL0__POR, + [TABLA_A_PROCESS_MONITOR_CTL1] = TABLA_A_PROCESS_MONITOR_CTL1__POR, + [TABLA_A_PROCESS_MONITOR_CTL2] = TABLA_A_PROCESS_MONITOR_CTL2__POR, + [TABLA_A_PROCESS_MONITOR_CTL3] = TABLA_A_PROCESS_MONITOR_CTL3__POR, + [TABLA_A_QFUSE_CTL] = TABLA_A_QFUSE_CTL__POR, + [TABLA_A_QFUSE_STATUS] = TABLA_A_QFUSE_STATUS__POR, + [TABLA_A_QFUSE_DATA_OUT0] = TABLA_A_QFUSE_DATA_OUT0__POR, + [TABLA_A_QFUSE_DATA_OUT1] = TABLA_A_QFUSE_DATA_OUT1__POR, + [TABLA_A_QFUSE_DATA_OUT2] = TABLA_A_QFUSE_DATA_OUT2__POR, + [TABLA_A_QFUSE_DATA_OUT3] = TABLA_A_QFUSE_DATA_OUT3__POR, + [TABLA_A_CDC_CTL] = TABLA_A_CDC_CTL__POR, + [TABLA_A_LEAKAGE_CTL] = TABLA_A_LEAKAGE_CTL__POR, + [TABLA_A_INTR_MODE] = TABLA_A_INTR_MODE__POR, + [TABLA_A_INTR_MASK0] = TABLA_A_INTR_MASK0__POR, + [TABLA_A_INTR_MASK1] = TABLA_A_INTR_MASK1__POR, + [TABLA_A_INTR_MASK2] = TABLA_A_INTR_MASK2__POR, + [TABLA_A_INTR_STATUS0] = TABLA_A_INTR_STATUS0__POR, + [TABLA_A_INTR_STATUS1] = TABLA_A_INTR_STATUS1__POR, + [TABLA_A_INTR_STATUS2] = TABLA_A_INTR_STATUS2__POR, + [TABLA_A_INTR_CLEAR0] = TABLA_A_INTR_CLEAR0__POR, + [TABLA_A_INTR_CLEAR1] = TABLA_A_INTR_CLEAR1__POR, + [TABLA_A_INTR_CLEAR2] = TABLA_A_INTR_CLEAR2__POR, + [TABLA_A_INTR_LEVEL0] = TABLA_A_INTR_LEVEL0__POR, + [TABLA_A_INTR_LEVEL1] = TABLA_A_INTR_LEVEL1__POR, + [TABLA_A_INTR_LEVEL2] = TABLA_A_INTR_LEVEL2__POR, + [TABLA_A_INTR_TEST0] = TABLA_A_INTR_TEST0__POR, + [TABLA_A_INTR_TEST1] = TABLA_A_INTR_TEST1__POR, + [TABLA_A_INTR_TEST2] = TABLA_A_INTR_TEST2__POR, + [TABLA_A_INTR_SET0] = TABLA_A_INTR_SET0__POR, + [TABLA_A_INTR_SET1] = TABLA_A_INTR_SET1__POR, + [TABLA_A_INTR_SET2] = TABLA_A_INTR_SET2__POR, + [TABLA_A_CDC_TX_I2S_SCK_MODE] = TABLA_A_CDC_TX_I2S_SCK_MODE__POR, + [TABLA_A_CDC_TX_I2S_WS_MODE] = TABLA_A_CDC_TX_I2S_WS_MODE__POR, + [TABLA_A_CDC_DMIC_DATA0_MODE] = TABLA_A_CDC_DMIC_DATA0_MODE__POR, + [TABLA_A_CDC_DMIC_CLK0_MODE] = TABLA_A_CDC_DMIC_CLK0_MODE__POR, + [TABLA_A_CDC_DMIC_DATA1_MODE] = TABLA_A_CDC_DMIC_DATA1_MODE__POR, + [TABLA_A_CDC_DMIC_CLK1_MODE] = TABLA_A_CDC_DMIC_CLK1_MODE__POR, + [TABLA_A_CDC_RX_I2S_SCK_MODE] = TABLA_A_CDC_RX_I2S_SCK_MODE__POR, + [TABLA_A_CDC_RX_I2S_WS_MODE] = TABLA_A_CDC_RX_I2S_WS_MODE__POR, + [TABLA_A_CDC_DMIC_DATA2_MODE] = TABLA_A_CDC_DMIC_DATA2_MODE__POR, + [TABLA_A_CDC_DMIC_CLK2_MODE] = TABLA_A_CDC_DMIC_CLK2_MODE__POR, + [TABLA_A_CDC_INTR_MODE] = TABLA_A_CDC_INTR_MODE__POR, + [TABLA_A_BIAS_REF_CTL] = TABLA_A_BIAS_REF_CTL__POR, + [TABLA_A_BIAS_CENTRAL_BG_CTL] = TABLA_A_BIAS_CENTRAL_BG_CTL__POR, + [TABLA_A_BIAS_PRECHRG_CTL] = TABLA_A_BIAS_PRECHRG_CTL__POR, + [TABLA_A_BIAS_CURR_CTL_1] = TABLA_A_BIAS_CURR_CTL_1__POR, + [TABLA_A_BIAS_CURR_CTL_2] = TABLA_A_BIAS_CURR_CTL_2__POR, + [TABLA_A_BIAS_CONFIG_MODE_BG_CTL] = + TABLA_A_BIAS_CONFIG_MODE_BG_CTL__POR, + [TABLA_A_BIAS_BG_STATUS] = TABLA_A_BIAS_BG_STATUS__POR, + [TABLA_A_CLK_BUFF_EN1] = TABLA_A_CLK_BUFF_EN1__POR, + [TABLA_A_CLK_BUFF_EN2] = TABLA_A_CLK_BUFF_EN2__POR, + [TABLA_A_LDO_H_MODE_1] = TABLA_A_LDO_H_MODE_1__POR, + [TABLA_A_LDO_H_MODE_2] = TABLA_A_LDO_H_MODE_2__POR, + [TABLA_A_LDO_H_LOOP_CTL] = TABLA_A_LDO_H_LOOP_CTL__POR, + [TABLA_A_LDO_H_COMP_1] = TABLA_A_LDO_H_COMP_1__POR, + [TABLA_A_LDO_H_COMP_2] = TABLA_A_LDO_H_COMP_2__POR, + [TABLA_A_LDO_H_BIAS_1] = TABLA_A_LDO_H_BIAS_1__POR, + [TABLA_A_LDO_H_BIAS_2] = TABLA_A_LDO_H_BIAS_2__POR, + [TABLA_A_LDO_H_BIAS_3] = TABLA_A_LDO_H_BIAS_3__POR, + [TABLA_A_LDO_L_MODE_1] = TABLA_A_LDO_L_MODE_1__POR, + [TABLA_A_LDO_L_MODE_2] = TABLA_A_LDO_L_MODE_2__POR, + [TABLA_A_LDO_L_LOOP_CTL] = TABLA_A_LDO_L_LOOP_CTL__POR, + [TABLA_A_LDO_L_COMP_1] = TABLA_A_LDO_L_COMP_1__POR, + [TABLA_A_LDO_L_COMP_2] = TABLA_A_LDO_L_COMP_2__POR, + [TABLA_A_LDO_L_BIAS_1] = TABLA_A_LDO_L_BIAS_1__POR, + [TABLA_A_LDO_L_BIAS_2] = TABLA_A_LDO_L_BIAS_2__POR, + [TABLA_A_LDO_L_BIAS_3] = TABLA_A_LDO_L_BIAS_3__POR, + [TABLA_A_MICB_CFILT_1_CTL] = TABLA_A_MICB_CFILT_1_CTL__POR, + [TABLA_A_MICB_CFILT_1_VAL] = TABLA_A_MICB_CFILT_1_VAL__POR, + [TABLA_A_MICB_CFILT_1_PRECHRG] = TABLA_A_MICB_CFILT_1_PRECHRG__POR, + [TABLA_A_MICB_1_CTL] = TABLA_A_MICB_1_CTL__POR, + [TABLA_A_MICB_1_INT_RBIAS] = TABLA_A_MICB_1_INT_RBIAS__POR, + [TABLA_A_MICB_1_MBHC] = TABLA_A_MICB_1_MBHC__POR, + [TABLA_A_MICB_CFILT_2_CTL] = TABLA_A_MICB_CFILT_2_CTL__POR, + [TABLA_A_MICB_CFILT_2_VAL] = TABLA_A_MICB_CFILT_2_VAL__POR, + [TABLA_A_MICB_CFILT_2_PRECHRG] = TABLA_A_MICB_CFILT_2_PRECHRG__POR, + [TABLA_A_MICB_2_CTL] = TABLA_A_MICB_2_CTL__POR, + [TABLA_A_MICB_2_INT_RBIAS] = TABLA_A_MICB_2_INT_RBIAS__POR, + [TABLA_A_MICB_2_MBHC] = TABLA_A_MICB_2_MBHC__POR, + [TABLA_A_MICB_CFILT_3_CTL] = TABLA_A_MICB_CFILT_3_CTL__POR, + [TABLA_A_MICB_CFILT_3_VAL] = TABLA_A_MICB_CFILT_3_VAL__POR, + [TABLA_A_MICB_CFILT_3_PRECHRG] = TABLA_A_MICB_CFILT_3_PRECHRG__POR, + [TABLA_A_MICB_3_CTL] = TABLA_A_MICB_3_CTL__POR, + [TABLA_A_MICB_3_INT_RBIAS] = TABLA_A_MICB_3_INT_RBIAS__POR, + [TABLA_A_MICB_3_MBHC] = TABLA_A_MICB_3_MBHC__POR, + [TABLA_1_A_MICB_4_CTL] = TABLA_A_MICB_4_CTL__POR, + [TABLA_1_A_MICB_4_INT_RBIAS] = TABLA_A_MICB_4_INT_RBIAS__POR, + [TABLA_1_A_MICB_4_MBHC] = TABLA_A_MICB_4_MBHC__POR, + [TABLA_2_A_MICB_4_CTL] = TABLA_A_MICB_4_CTL__POR, + [TABLA_2_A_MICB_4_INT_RBIAS] = TABLA_A_MICB_4_INT_RBIAS__POR, + [TABLA_2_A_MICB_4_MBHC] = TABLA_A_MICB_4_MBHC__POR, + [TABLA_A_TX_COM_BIAS] = TABLA_A_TX_COM_BIAS__POR, + [TABLA_A_MBHC_SCALING_MUX_1] = TABLA_A_MBHC_SCALING_MUX_1__POR, + [TABLA_A_MBHC_SCALING_MUX_2] = TABLA_A_MBHC_SCALING_MUX_2__POR, + [TABLA_A_TX_SUP_SWITCH_CTRL_1] = TABLA_A_TX_SUP_SWITCH_CTRL_1__POR, + [TABLA_A_TX_SUP_SWITCH_CTRL_2] = TABLA_A_TX_SUP_SWITCH_CTRL_2__POR, + [TABLA_A_TX_1_2_EN] = TABLA_A_TX_1_2_EN__POR, + [TABLA_A_TX_1_2_TEST_EN] = TABLA_A_TX_1_2_TEST_EN__POR, + [TABLA_A_TX_1_2_ADC_CH1] = TABLA_A_TX_1_2_ADC_CH1__POR, + [TABLA_A_TX_1_2_ADC_CH2] = TABLA_A_TX_1_2_ADC_CH2__POR, + [TABLA_A_TX_1_2_ATEST_REFCTRL] = TABLA_A_TX_1_2_ATEST_REFCTRL__POR, + [TABLA_A_TX_1_2_TEST_CTL] = TABLA_A_TX_1_2_TEST_CTL__POR, + [TABLA_A_TX_1_2_TEST_BLOCK_EN] = TABLA_A_TX_1_2_TEST_BLOCK_EN__POR, + [TABLA_A_TX_1_2_TXFE_CLKDIV] = TABLA_A_TX_1_2_TXFE_CLKDIV__POR, + [TABLA_A_TX_1_2_SAR_ERR_CH1] = TABLA_A_TX_1_2_SAR_ERR_CH1__POR, + [TABLA_A_TX_1_2_SAR_ERR_CH2] = TABLA_A_TX_1_2_SAR_ERR_CH2__POR, + [TABLA_A_TX_3_4_EN] = TABLA_A_TX_3_4_EN__POR, + [TABLA_A_TX_3_4_TEST_EN] = TABLA_A_TX_3_4_TEST_EN__POR, + [TABLA_A_TX_3_4_ADC_CH3] = TABLA_A_TX_3_4_ADC_CH3__POR, + [TABLA_A_TX_3_4_ADC_CH4] = TABLA_A_TX_3_4_ADC_CH4__POR, + [TABLA_A_TX_3_4_ATEST_REFCTRL] = TABLA_A_TX_3_4_ATEST_REFCTRL__POR, + [TABLA_A_TX_3_4_TEST_CTL] = TABLA_A_TX_3_4_TEST_CTL__POR, + [TABLA_A_TX_3_4_TEST_BLOCK_EN] = TABLA_A_TX_3_4_TEST_BLOCK_EN__POR, + [TABLA_A_TX_3_4_TXFE_CKDIV] = TABLA_A_TX_3_4_TXFE_CKDIV__POR, + [TABLA_A_TX_3_4_SAR_ERR_CH3] = TABLA_A_TX_3_4_SAR_ERR_CH3__POR, + [TABLA_A_TX_3_4_SAR_ERR_CH4] = TABLA_A_TX_3_4_SAR_ERR_CH4__POR, + [TABLA_A_TX_5_6_EN] = TABLA_A_TX_5_6_EN__POR, + [TABLA_A_TX_5_6_TEST_EN] = TABLA_A_TX_5_6_TEST_EN__POR, + [TABLA_A_TX_5_6_ADC_CH5] = TABLA_A_TX_5_6_ADC_CH5__POR, + [TABLA_A_TX_5_6_ADC_CH6] = TABLA_A_TX_5_6_ADC_CH6__POR, + [TABLA_A_TX_5_6_ATEST_REFCTRL] = TABLA_A_TX_5_6_ATEST_REFCTRL__POR, + [TABLA_A_TX_5_6_TEST_CTL] = TABLA_A_TX_5_6_TEST_CTL__POR, + [TABLA_A_TX_5_6_TEST_BLOCK_EN] = TABLA_A_TX_5_6_TEST_BLOCK_EN__POR, + [TABLA_A_TX_5_6_TXFE_CKDIV] = TABLA_A_TX_5_6_TXFE_CKDIV__POR, + [TABLA_A_TX_5_6_SAR_ERR_CH5] = TABLA_A_TX_5_6_SAR_ERR_CH5__POR, + [TABLA_A_TX_5_6_SAR_ERR_CH6] = TABLA_A_TX_5_6_SAR_ERR_CH6__POR, + [TABLA_A_TX_7_MBHC_EN] = TABLA_A_TX_7_MBHC_EN__POR, + [TABLA_A_TX_7_MBHC_ATEST_REFCTRL] = + TABLA_A_TX_7_MBHC_ATEST_REFCTRL__POR, + [TABLA_A_TX_7_MBHC_ADC] = TABLA_A_TX_7_MBHC_ADC__POR, + [TABLA_A_TX_7_MBHC_TEST_CTL] = TABLA_A_TX_7_MBHC_TEST_CTL__POR, + [TABLA_A_TX_7_MBHC_SAR_ERR] = TABLA_A_TX_7_MBHC_SAR_ERR__POR, + [TABLA_A_TX_7_TXFE_CLKDIV] = TABLA_A_TX_7_TXFE_CLKDIV__POR, + [TABLA_A_AUX_COM_CTL] = TABLA_A_AUX_COM_CTL__POR, + [TABLA_A_AUX_COM_ATEST] = TABLA_A_AUX_COM_ATEST__POR, + [TABLA_A_AUX_L_EN] = TABLA_A_AUX_L_EN__POR, + [TABLA_A_AUX_L_GAIN] = TABLA_A_AUX_L_GAIN__POR, + [TABLA_A_AUX_L_PA_CONN] = TABLA_A_AUX_L_PA_CONN__POR, + [TABLA_A_AUX_L_PA_CONN_INV] = TABLA_A_AUX_L_PA_CONN_INV__POR, + [TABLA_A_AUX_R_EN] = TABLA_A_AUX_R_EN__POR, + [TABLA_A_AUX_R_GAIN] = TABLA_A_AUX_R_GAIN__POR, + [TABLA_A_AUX_R_PA_CONN] = TABLA_A_AUX_R_PA_CONN__POR, + [TABLA_A_AUX_R_PA_CONN_INV] = TABLA_A_AUX_R_PA_CONN_INV__POR, + [TABLA_A_CP_EN] = TABLA_A_CP_EN__POR, + [TABLA_A_CP_CLK] = TABLA_A_CP_CLK__POR, + [TABLA_A_CP_STATIC] = TABLA_A_CP_STATIC__POR, + [TABLA_A_CP_DCC1] = TABLA_A_CP_DCC1__POR, + [TABLA_A_CP_DCC3] = TABLA_A_CP_DCC3__POR, + [TABLA_A_CP_ATEST] = TABLA_A_CP_ATEST__POR, + [TABLA_A_CP_DTEST] = TABLA_A_CP_DTEST__POR, + [TABLA_A_RX_COM_TIMER_DIV] = TABLA_A_RX_COM_TIMER_DIV__POR, + [TABLA_A_RX_COM_OCP_CTL] = TABLA_A_RX_COM_OCP_CTL__POR, + [TABLA_A_RX_COM_OCP_COUNT] = TABLA_A_RX_COM_OCP_COUNT__POR, + [TABLA_A_RX_COM_DAC_CTL] = TABLA_A_RX_COM_DAC_CTL__POR, + [TABLA_A_RX_COM_BIAS] = TABLA_A_RX_COM_BIAS__POR, + [TABLA_A_RX_HPH_BIAS_PA] = TABLA_A_RX_HPH_BIAS_PA__POR, + [TABLA_A_RX_HPH_BIAS_LDO] = TABLA_A_RX_HPH_BIAS_LDO__POR, + [TABLA_A_RX_HPH_BIAS_CNP] = TABLA_A_RX_HPH_BIAS_CNP__POR, + [TABLA_A_RX_HPH_BIAS_WG] = TABLA_A_RX_HPH_BIAS_WG__POR, + [TABLA_A_RX_HPH_OCP_CTL] = TABLA_A_RX_HPH_OCP_CTL__POR, + [TABLA_A_RX_HPH_CNP_EN] = TABLA_A_RX_HPH_CNP_EN__POR, + [TABLA_A_RX_HPH_CNP_WG_CTL] = TABLA_A_RX_HPH_CNP_WG_CTL__POR, + [TABLA_A_RX_HPH_CNP_WG_TIME] = TABLA_A_RX_HPH_CNP_WG_TIME__POR, + [TABLA_A_RX_HPH_L_GAIN] = TABLA_A_RX_HPH_L_GAIN__POR, + [TABLA_A_RX_HPH_L_TEST] = TABLA_A_RX_HPH_L_TEST__POR, + [TABLA_A_RX_HPH_L_PA_CTL] = TABLA_A_RX_HPH_L_PA_CTL__POR, + [TABLA_A_RX_HPH_L_DAC_CTL] = TABLA_A_RX_HPH_L_DAC_CTL__POR, + [TABLA_A_RX_HPH_L_ATEST] = TABLA_A_RX_HPH_L_ATEST__POR, + [TABLA_A_RX_HPH_L_STATUS] = TABLA_A_RX_HPH_L_STATUS__POR, + [TABLA_A_RX_HPH_R_GAIN] = TABLA_A_RX_HPH_R_GAIN__POR, + [TABLA_A_RX_HPH_R_TEST] = TABLA_A_RX_HPH_R_TEST__POR, + [TABLA_A_RX_HPH_R_PA_CTL] = TABLA_A_RX_HPH_R_PA_CTL__POR, + [TABLA_A_RX_HPH_R_DAC_CTL] = TABLA_A_RX_HPH_R_DAC_CTL__POR, + [TABLA_A_RX_HPH_R_ATEST] = TABLA_A_RX_HPH_R_ATEST__POR, + [TABLA_A_RX_HPH_R_STATUS] = TABLA_A_RX_HPH_R_STATUS__POR, + [TABLA_A_RX_EAR_BIAS_PA] = TABLA_A_RX_EAR_BIAS_PA__POR, + [TABLA_A_RX_EAR_BIAS_CMBUFF] = TABLA_A_RX_EAR_BIAS_CMBUFF__POR, + [TABLA_A_RX_EAR_EN] = TABLA_A_RX_EAR_EN__POR, + [TABLA_A_RX_EAR_GAIN] = TABLA_A_RX_EAR_GAIN__POR, + [TABLA_A_RX_EAR_CMBUFF] = TABLA_A_RX_EAR_CMBUFF__POR, + [TABLA_A_RX_EAR_ICTL] = TABLA_A_RX_EAR_ICTL__POR, + [TABLA_A_RX_EAR_CCOMP] = TABLA_A_RX_EAR_CCOMP__POR, + [TABLA_A_RX_EAR_VCM] = TABLA_A_RX_EAR_VCM__POR, + [TABLA_A_RX_EAR_CNP] = TABLA_A_RX_EAR_CNP__POR, + [TABLA_A_RX_EAR_ATEST] = TABLA_A_RX_EAR_ATEST__POR, + [TABLA_A_RX_EAR_STATUS] = TABLA_A_RX_EAR_STATUS__POR, + [TABLA_A_RX_LINE_BIAS_PA] = TABLA_A_RX_LINE_BIAS_PA__POR, + [TABLA_A_RX_LINE_BIAS_DAC] = TABLA_A_RX_LINE_BIAS_DAC__POR, + [TABLA_A_RX_LINE_BIAS_CNP] = TABLA_A_RX_LINE_BIAS_CNP__POR, + [TABLA_A_RX_LINE_COM] = TABLA_A_RX_LINE_COM__POR, + [TABLA_A_RX_LINE_CNP_EN] = TABLA_A_RX_LINE_CNP_EN__POR, + [TABLA_A_RX_LINE_CNP_WG_CTL] = TABLA_A_RX_LINE_CNP_WG_CTL__POR, + [TABLA_A_RX_LINE_CNP_WG_TIME] = TABLA_A_RX_LINE_CNP_WG_TIME__POR, + [TABLA_A_RX_LINE_1_GAIN] = TABLA_A_RX_LINE_1_GAIN__POR, + [TABLA_A_RX_LINE_1_TEST] = TABLA_A_RX_LINE_1_TEST__POR, + [TABLA_A_RX_LINE_1_DAC_CTL] = TABLA_A_RX_LINE_1_DAC_CTL__POR, + [TABLA_A_RX_LINE_1_STATUS] = TABLA_A_RX_LINE_1_STATUS__POR, + [TABLA_A_RX_LINE_2_GAIN] = TABLA_A_RX_LINE_2_GAIN__POR, + [TABLA_A_RX_LINE_2_TEST] = TABLA_A_RX_LINE_2_TEST__POR, + [TABLA_A_RX_LINE_2_DAC_CTL] = TABLA_A_RX_LINE_2_DAC_CTL__POR, + [TABLA_A_RX_LINE_2_STATUS] = TABLA_A_RX_LINE_2_STATUS__POR, + [TABLA_A_RX_LINE_3_GAIN] = TABLA_A_RX_LINE_3_GAIN__POR, + [TABLA_A_RX_LINE_3_TEST] = TABLA_A_RX_LINE_3_TEST__POR, + [TABLA_A_RX_LINE_3_DAC_CTL] = TABLA_A_RX_LINE_3_DAC_CTL__POR, + [TABLA_A_RX_LINE_3_STATUS] = TABLA_A_RX_LINE_3_STATUS__POR, + [TABLA_A_RX_LINE_4_GAIN] = TABLA_A_RX_LINE_4_GAIN__POR, + [TABLA_A_RX_LINE_4_TEST] = TABLA_A_RX_LINE_4_TEST__POR, + [TABLA_A_RX_LINE_4_DAC_CTL] = TABLA_A_RX_LINE_4_DAC_CTL__POR, + [TABLA_A_RX_LINE_4_STATUS] = TABLA_A_RX_LINE_4_STATUS__POR, + [TABLA_A_RX_LINE_5_GAIN] = TABLA_A_RX_LINE_5_GAIN__POR, + [TABLA_A_RX_LINE_5_TEST] = TABLA_A_RX_LINE_5_TEST__POR, + [TABLA_A_RX_LINE_5_DAC_CTL] = TABLA_A_RX_LINE_5_DAC_CTL__POR, + [TABLA_A_RX_LINE_5_STATUS] = TABLA_A_RX_LINE_5_STATUS__POR, + [TABLA_A_RX_LINE_CNP_DBG] = TABLA_A_RX_LINE_CNP_DBG__POR, + [TABLA_A_MBHC_HPH] = TABLA_A_MBHC_HPH__POR, + [TABLA_A_CONFIG_MODE_FREQ] = TABLA_A_CONFIG_MODE_FREQ__POR, + [TABLA_A_CONFIG_MODE_TEST] = TABLA_A_CONFIG_MODE_TEST__POR, + [TABLA_A_CONFIG_MODE_STATUS] = TABLA_A_CONFIG_MODE_STATUS__POR, + [TABLA_A_CONFIG_MODE_TUNER] = TABLA_A_CONFIG_MODE_TUNER__POR, + [TABLA_A_CDC_ANC1_CTL] = TABLA_A_CDC_ANC1_CTL__POR, + [TABLA_A_CDC_ANC1_SHIFT] = TABLA_A_CDC_ANC1_SHIFT__POR, + [TABLA_A_CDC_ANC1_FILT1_B1_CTL] = TABLA_A_CDC_ANC1_FILT1_B1_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B2_CTL] = TABLA_A_CDC_ANC1_FILT1_B2_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B3_CTL] = TABLA_A_CDC_ANC1_FILT1_B3_CTL__POR, + [TABLA_A_CDC_ANC1_FILT1_B4_CTL] = TABLA_A_CDC_ANC1_FILT1_B4_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B1_CTL] = TABLA_A_CDC_ANC1_FILT2_B1_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B2_CTL] = TABLA_A_CDC_ANC1_FILT2_B2_CTL__POR, + [TABLA_A_CDC_ANC1_FILT2_B3_CTL] = TABLA_A_CDC_ANC1_FILT2_B3_CTL__POR, + [TABLA_A_CDC_ANC1_SPARE] = TABLA_A_CDC_ANC1_SPARE__POR, + [TABLA_A_CDC_ANC1_FILT3_CTL] = TABLA_A_CDC_ANC1_FILT3_CTL__POR, + [TABLA_A_CDC_ANC1_FILT4_CTL] = TABLA_A_CDC_ANC1_FILT4_CTL__POR, + [TABLA_A_CDC_TX1_VOL_CTL_TIMER] = TABLA_A_CDC_TX1_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX2_VOL_CTL_TIMER] = TABLA_A_CDC_TX2_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX3_VOL_CTL_TIMER] = TABLA_A_CDC_TX3_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX4_VOL_CTL_TIMER] = TABLA_A_CDC_TX4_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX5_VOL_CTL_TIMER] = TABLA_A_CDC_TX5_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX6_VOL_CTL_TIMER] = TABLA_A_CDC_TX6_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX7_VOL_CTL_TIMER] = TABLA_A_CDC_TX7_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX8_VOL_CTL_TIMER] = TABLA_A_CDC_TX8_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX9_VOL_CTL_TIMER] = TABLA_A_CDC_TX9_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX10_VOL_CTL_TIMER] = TABLA_A_CDC_TX10_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX1_VOL_CTL_GAIN] = TABLA_A_CDC_TX1_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX2_VOL_CTL_GAIN] = TABLA_A_CDC_TX2_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX3_VOL_CTL_GAIN] = TABLA_A_CDC_TX3_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX4_VOL_CTL_GAIN] = TABLA_A_CDC_TX4_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX5_VOL_CTL_GAIN] = TABLA_A_CDC_TX5_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX6_VOL_CTL_GAIN] = TABLA_A_CDC_TX6_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX7_VOL_CTL_GAIN] = TABLA_A_CDC_TX7_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX8_VOL_CTL_GAIN] = TABLA_A_CDC_TX8_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX9_VOL_CTL_GAIN] = TABLA_A_CDC_TX9_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX10_VOL_CTL_GAIN] = TABLA_A_CDC_TX10_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX1_VOL_CTL_CFG] = TABLA_A_CDC_TX1_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX2_VOL_CTL_CFG] = TABLA_A_CDC_TX2_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX3_VOL_CTL_CFG] = TABLA_A_CDC_TX3_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX4_VOL_CTL_CFG] = TABLA_A_CDC_TX4_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX5_VOL_CTL_CFG] = TABLA_A_CDC_TX5_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX6_VOL_CTL_CFG] = TABLA_A_CDC_TX6_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX7_VOL_CTL_CFG] = TABLA_A_CDC_TX7_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX8_VOL_CTL_CFG] = TABLA_A_CDC_TX8_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX9_VOL_CTL_CFG] = TABLA_A_CDC_TX9_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX10_VOL_CTL_CFG] = TABLA_A_CDC_TX10_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX1_MUX_CTL] = TABLA_A_CDC_TX1_MUX_CTL__POR, + [TABLA_A_CDC_TX2_MUX_CTL] = TABLA_A_CDC_TX2_MUX_CTL__POR, + [TABLA_A_CDC_TX3_MUX_CTL] = TABLA_A_CDC_TX3_MUX_CTL__POR, + [TABLA_A_CDC_TX4_MUX_CTL] = TABLA_A_CDC_TX4_MUX_CTL__POR, + [TABLA_A_CDC_TX5_MUX_CTL] = TABLA_A_CDC_TX5_MUX_CTL__POR, + [TABLA_A_CDC_TX6_MUX_CTL] = TABLA_A_CDC_TX6_MUX_CTL__POR, + [TABLA_A_CDC_TX7_MUX_CTL] = TABLA_A_CDC_TX7_MUX_CTL__POR, + [TABLA_A_CDC_TX8_MUX_CTL] = TABLA_A_CDC_TX8_MUX_CTL__POR, + [TABLA_A_CDC_TX9_MUX_CTL] = TABLA_A_CDC_TX9_MUX_CTL__POR, + [TABLA_A_CDC_TX10_MUX_CTL] = TABLA_A_CDC_TX10_MUX_CTL__POR, + [TABLA_A_CDC_TX1_CLK_FS_CTL] = TABLA_A_CDC_TX1_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX2_CLK_FS_CTL] = TABLA_A_CDC_TX2_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX3_CLK_FS_CTL] = TABLA_A_CDC_TX3_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX4_CLK_FS_CTL] = TABLA_A_CDC_TX4_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX5_CLK_FS_CTL] = TABLA_A_CDC_TX5_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX6_CLK_FS_CTL] = TABLA_A_CDC_TX6_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX7_CLK_FS_CTL] = TABLA_A_CDC_TX7_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX8_CLK_FS_CTL] = TABLA_A_CDC_TX8_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX9_CLK_FS_CTL] = TABLA_A_CDC_TX9_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX10_CLK_FS_CTL] = TABLA_A_CDC_TX10_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX1_DMIC_CTL] = TABLA_A_CDC_TX1_DMIC_CTL__POR, + [TABLA_A_CDC_TX2_DMIC_CTL] = TABLA_A_CDC_TX2_DMIC_CTL__POR, + [TABLA_A_CDC_TX3_DMIC_CTL] = TABLA_A_CDC_TX3_DMIC_CTL__POR, + [TABLA_A_CDC_TX4_DMIC_CTL] = TABLA_A_CDC_TX4_DMIC_CTL__POR, + [TABLA_A_CDC_TX5_DMIC_CTL] = TABLA_A_CDC_TX5_DMIC_CTL__POR, + [TABLA_A_CDC_TX6_DMIC_CTL] = TABLA_A_CDC_TX6_DMIC_CTL__POR, + [TABLA_A_CDC_TX7_DMIC_CTL] = TABLA_A_CDC_TX7_DMIC_CTL__POR, + [TABLA_A_CDC_TX8_DMIC_CTL] = TABLA_A_CDC_TX8_DMIC_CTL__POR, + [TABLA_A_CDC_TX9_DMIC_CTL] = TABLA_A_CDC_TX9_DMIC_CTL__POR, + [TABLA_A_CDC_TX10_DMIC_CTL] = TABLA_A_CDC_TX10_DMIC_CTL__POR, + [TABLA_A_CDC_SRC1_PDA_CFG] = TABLA_A_CDC_SRC1_PDA_CFG__POR, + [TABLA_A_CDC_SRC2_PDA_CFG] = TABLA_A_CDC_SRC2_PDA_CFG__POR, + [TABLA_A_CDC_SRC1_FS_CTL] = TABLA_A_CDC_SRC1_FS_CTL__POR, + [TABLA_A_CDC_SRC2_FS_CTL] = TABLA_A_CDC_SRC2_FS_CTL__POR, + [TABLA_A_CDC_RX1_B1_CTL] = TABLA_A_CDC_RX1_B1_CTL__POR, + [TABLA_A_CDC_RX2_B1_CTL] = TABLA_A_CDC_RX2_B1_CTL__POR, + [TABLA_A_CDC_RX3_B1_CTL] = TABLA_A_CDC_RX3_B1_CTL__POR, + [TABLA_A_CDC_RX4_B1_CTL] = TABLA_A_CDC_RX4_B1_CTL__POR, + [TABLA_A_CDC_RX5_B1_CTL] = TABLA_A_CDC_RX5_B1_CTL__POR, + [TABLA_A_CDC_RX6_B1_CTL] = TABLA_A_CDC_RX6_B1_CTL__POR, + [TABLA_A_CDC_RX7_B1_CTL] = TABLA_A_CDC_RX7_B1_CTL__POR, + [TABLA_A_CDC_RX1_B2_CTL] = TABLA_A_CDC_RX1_B2_CTL__POR, + [TABLA_A_CDC_RX2_B2_CTL] = TABLA_A_CDC_RX2_B2_CTL__POR, + [TABLA_A_CDC_RX3_B2_CTL] = TABLA_A_CDC_RX3_B2_CTL__POR, + [TABLA_A_CDC_RX4_B2_CTL] = TABLA_A_CDC_RX4_B2_CTL__POR, + [TABLA_A_CDC_RX5_B2_CTL] = TABLA_A_CDC_RX5_B2_CTL__POR, + [TABLA_A_CDC_RX6_B2_CTL] = TABLA_A_CDC_RX6_B2_CTL__POR, + [TABLA_A_CDC_RX7_B2_CTL] = TABLA_A_CDC_RX7_B2_CTL__POR, + [TABLA_A_CDC_RX1_B3_CTL] = TABLA_A_CDC_RX1_B3_CTL__POR, + [TABLA_A_CDC_RX2_B3_CTL] = TABLA_A_CDC_RX2_B3_CTL__POR, + [TABLA_A_CDC_RX3_B3_CTL] = TABLA_A_CDC_RX3_B3_CTL__POR, + [TABLA_A_CDC_RX4_B3_CTL] = TABLA_A_CDC_RX4_B3_CTL__POR, + [TABLA_A_CDC_RX5_B3_CTL] = TABLA_A_CDC_RX5_B3_CTL__POR, + [TABLA_A_CDC_RX6_B3_CTL] = TABLA_A_CDC_RX6_B3_CTL__POR, + [TABLA_A_CDC_RX7_B3_CTL] = TABLA_A_CDC_RX7_B3_CTL__POR, + [TABLA_A_CDC_RX1_B4_CTL] = TABLA_A_CDC_RX1_B4_CTL__POR, + [TABLA_A_CDC_RX2_B4_CTL] = TABLA_A_CDC_RX2_B4_CTL__POR, + [TABLA_A_CDC_RX3_B4_CTL] = TABLA_A_CDC_RX3_B4_CTL__POR, + [TABLA_A_CDC_RX4_B4_CTL] = TABLA_A_CDC_RX4_B4_CTL__POR, + [TABLA_A_CDC_RX5_B4_CTL] = TABLA_A_CDC_RX5_B4_CTL__POR, + [TABLA_A_CDC_RX6_B4_CTL] = TABLA_A_CDC_RX6_B4_CTL__POR, + [TABLA_A_CDC_RX7_B4_CTL] = TABLA_A_CDC_RX7_B4_CTL__POR, + [TABLA_A_CDC_RX1_B5_CTL] = TABLA_A_CDC_RX1_B5_CTL__POR, + [TABLA_A_CDC_RX2_B5_CTL] = TABLA_A_CDC_RX2_B5_CTL__POR, + [TABLA_A_CDC_RX3_B5_CTL] = TABLA_A_CDC_RX3_B5_CTL__POR, + [TABLA_A_CDC_RX4_B5_CTL] = TABLA_A_CDC_RX4_B5_CTL__POR, + [TABLA_A_CDC_RX5_B5_CTL] = TABLA_A_CDC_RX5_B5_CTL__POR, + [TABLA_A_CDC_RX6_B5_CTL] = TABLA_A_CDC_RX6_B5_CTL__POR, + [TABLA_A_CDC_RX7_B5_CTL] = TABLA_A_CDC_RX7_B5_CTL__POR, + [TABLA_A_CDC_RX1_B6_CTL] = TABLA_A_CDC_RX1_B6_CTL__POR, + [TABLA_A_CDC_RX2_B6_CTL] = TABLA_A_CDC_RX2_B6_CTL__POR, + [TABLA_A_CDC_RX3_B6_CTL] = TABLA_A_CDC_RX3_B6_CTL__POR, + [TABLA_A_CDC_RX4_B6_CTL] = TABLA_A_CDC_RX4_B6_CTL__POR, + [TABLA_A_CDC_RX5_B6_CTL] = TABLA_A_CDC_RX5_B6_CTL__POR, + [TABLA_A_CDC_RX6_B6_CTL] = TABLA_A_CDC_RX6_B6_CTL__POR, + [TABLA_A_CDC_RX7_B6_CTL] = TABLA_A_CDC_RX7_B6_CTL__POR, + [TABLA_A_CDC_RX1_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX1_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX2_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX2_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX3_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX3_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX4_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX4_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX5_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX5_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX6_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX6_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX7_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX7_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX1_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX1_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX2_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX2_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX3_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX3_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX4_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX4_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX5_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX5_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX6_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX6_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX7_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX7_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_CLK_RX_RESET_CTL] = TABLA_A_CDC_CLK_RX_RESET_CTL__POR, + [TABLA_A_CDC_CLK_ANC_RESET_CTL] = TABLA_A_CDC_CLK_ANC_RESET_CTL__POR, + [TABLA_A_CDC_CLK_TX_RESET_B1_CTL] = + TABLA_A_CDC_CLK_TX_RESET_B1_CTL__POR, + [TABLA_A_CDC_CLK_TX_RESET_B2_CTL] = + TABLA_A_CDC_CLK_TX_RESET_B2_CTL__POR, + [TABLA_A_CDC_CLK_DMIC_CTL] = TABLA_A_CDC_CLK_DMIC_CTL__POR, + [TABLA_A_CDC_CLK_RX_I2S_CTL] = TABLA_A_CDC_CLK_RX_I2S_CTL__POR, + [TABLA_A_CDC_CLK_TX_I2S_CTL] = TABLA_A_CDC_CLK_TX_I2S_CTL__POR, + [TABLA_A_CDC_CLK_OTHR_RESET_CTL] = TABLA_A_CDC_CLK_OTHR_RESET_CTL__POR, + [TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL] = + TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR, + [TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL] = + TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL__POR, + [TABLA_A_CDC_CLK_OTHR_CTL] = TABLA_A_CDC_CLK_OTHR_CTL__POR, + [TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL] = + TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL__POR, + [TABLA_A_CDC_CLK_ANC_CLK_EN_CTL] = TABLA_A_CDC_CLK_ANC_CLK_EN_CTL__POR, + [TABLA_A_CDC_CLK_RX_B1_CTL] = TABLA_A_CDC_CLK_RX_B1_CTL__POR, + [TABLA_A_CDC_CLK_RX_B2_CTL] = TABLA_A_CDC_CLK_RX_B2_CTL__POR, + [TABLA_A_CDC_CLK_MCLK_CTL] = TABLA_A_CDC_CLK_MCLK_CTL__POR, + [TABLA_A_CDC_CLK_PDM_CTL] = TABLA_A_CDC_CLK_PDM_CTL__POR, + [TABLA_A_CDC_CLK_SD_CTL] = TABLA_A_CDC_CLK_SD_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR, + [TABLA_A_CDC_CLSG_GAIN_THRESH_CTL] = + TABLA_A_CDC_CLSG_GAIN_THRESH_CTL__POR, + [TABLA_A_CDC_CLSG_TIMER_B1_CFG] = TABLA_A_CDC_CLSG_TIMER_B1_CFG__POR, + [TABLA_A_CDC_CLSG_TIMER_B2_CFG] = TABLA_A_CDC_CLSG_TIMER_B2_CFG__POR, + [TABLA_A_CDC_CLSG_CTL] = TABLA_A_CDC_CLSG_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B1_CTL] = TABLA_A_CDC_IIR1_GAIN_B1_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B1_CTL] = TABLA_A_CDC_IIR2_GAIN_B1_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B2_CTL] = TABLA_A_CDC_IIR1_GAIN_B2_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B2_CTL] = TABLA_A_CDC_IIR2_GAIN_B2_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B3_CTL] = TABLA_A_CDC_IIR1_GAIN_B3_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B3_CTL] = TABLA_A_CDC_IIR2_GAIN_B3_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B4_CTL] = TABLA_A_CDC_IIR1_GAIN_B4_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B4_CTL] = TABLA_A_CDC_IIR2_GAIN_B4_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B5_CTL] = TABLA_A_CDC_IIR1_GAIN_B5_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B5_CTL] = TABLA_A_CDC_IIR2_GAIN_B5_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B6_CTL] = TABLA_A_CDC_IIR1_GAIN_B6_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B6_CTL] = TABLA_A_CDC_IIR2_GAIN_B6_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B7_CTL] = TABLA_A_CDC_IIR1_GAIN_B7_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B7_CTL] = TABLA_A_CDC_IIR2_GAIN_B7_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B8_CTL] = TABLA_A_CDC_IIR1_GAIN_B8_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B8_CTL] = TABLA_A_CDC_IIR2_GAIN_B8_CTL__POR, + [TABLA_A_CDC_IIR1_CTL] = TABLA_A_CDC_IIR1_CTL__POR, + [TABLA_A_CDC_IIR2_CTL] = TABLA_A_CDC_IIR2_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_TIMER_CTL] = + TABLA_A_CDC_IIR1_GAIN_TIMER_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_TIMER_CTL] = + TABLA_A_CDC_IIR2_GAIN_TIMER_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B1_CTL] = TABLA_A_CDC_IIR1_COEF_B1_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B1_CTL] = TABLA_A_CDC_IIR2_COEF_B1_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B2_CTL] = TABLA_A_CDC_IIR1_COEF_B2_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B2_CTL] = TABLA_A_CDC_IIR2_COEF_B2_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B3_CTL] = TABLA_A_CDC_IIR1_COEF_B3_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B3_CTL] = TABLA_A_CDC_IIR2_COEF_B3_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B4_CTL] = TABLA_A_CDC_IIR1_COEF_B4_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B4_CTL] = TABLA_A_CDC_IIR2_COEF_B4_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B5_CTL] = TABLA_A_CDC_IIR1_COEF_B5_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B5_CTL] = TABLA_A_CDC_IIR2_COEF_B5_CTL__POR, + [TABLA_A_CDC_TOP_GAIN_UPDATE] = TABLA_A_CDC_TOP_GAIN_UPDATE__POR, + [TABLA_A_CDC_DEBUG_B1_CTL] = TABLA_A_CDC_DEBUG_B1_CTL__POR, + [TABLA_A_CDC_DEBUG_B2_CTL] = TABLA_A_CDC_DEBUG_B2_CTL__POR, + [TABLA_A_CDC_DEBUG_B3_CTL] = TABLA_A_CDC_DEBUG_B3_CTL__POR, + [TABLA_A_CDC_DEBUG_B4_CTL] = TABLA_A_CDC_DEBUG_B4_CTL__POR, + [TABLA_A_CDC_DEBUG_B5_CTL] = TABLA_A_CDC_DEBUG_B5_CTL__POR, + [TABLA_A_CDC_DEBUG_B6_CTL] = TABLA_A_CDC_DEBUG_B6_CTL__POR, + [TABLA_A_CDC_COMP1_B1_CTL] = TABLA_A_CDC_COMP1_B1_CTL__POR, + [TABLA_A_CDC_COMP1_B2_CTL] = TABLA_A_CDC_COMP1_B2_CTL__POR, + [TABLA_A_CDC_COMP1_B3_CTL] = TABLA_A_CDC_COMP1_B3_CTL__POR, + [TABLA_A_CDC_COMP1_B4_CTL] = TABLA_A_CDC_COMP1_B4_CTL__POR, + [TABLA_A_CDC_COMP1_B5_CTL] = TABLA_A_CDC_COMP1_B5_CTL__POR, + [TABLA_A_CDC_COMP1_B6_CTL] = TABLA_A_CDC_COMP1_B6_CTL__POR, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = + TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [TABLA_A_CDC_COMP1_FS_CFG] = TABLA_A_CDC_COMP1_FS_CFG__POR, + [TABLA_A_CDC_COMP2_B1_CTL] = TABLA_A_CDC_COMP2_B1_CTL__POR, + [TABLA_A_CDC_COMP2_B2_CTL] = TABLA_A_CDC_COMP2_B2_CTL__POR, + [TABLA_A_CDC_COMP2_B3_CTL] = TABLA_A_CDC_COMP2_B3_CTL__POR, + [TABLA_A_CDC_COMP2_B4_CTL] = TABLA_A_CDC_COMP2_B4_CTL__POR, + [TABLA_A_CDC_COMP2_B5_CTL] = TABLA_A_CDC_COMP2_B5_CTL__POR, + [TABLA_A_CDC_COMP2_B6_CTL] = TABLA_A_CDC_COMP2_B6_CTL__POR, + [TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS] = + TABLA_A_CDC_COMP1_SHUT_DOWN_STATUS__POR, + [TABLA_A_CDC_COMP2_FS_CFG] = TABLA_A_CDC_COMP2_FS_CFG__POR, + [TABLA_A_CDC_CONN_RX1_B1_CTL] = TABLA_A_CDC_CONN_RX1_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX1_B2_CTL] = TABLA_A_CDC_CONN_RX1_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX1_B3_CTL] = TABLA_A_CDC_CONN_RX1_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B1_CTL] = TABLA_A_CDC_CONN_RX2_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B2_CTL] = TABLA_A_CDC_CONN_RX2_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B3_CTL] = TABLA_A_CDC_CONN_RX2_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B1_CTL] = TABLA_A_CDC_CONN_RX3_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B2_CTL] = TABLA_A_CDC_CONN_RX3_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B3_CTL] = TABLA_A_CDC_CONN_RX3_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX4_B1_CTL] = TABLA_A_CDC_CONN_RX4_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX4_B2_CTL] = TABLA_A_CDC_CONN_RX4_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX5_B1_CTL] = TABLA_A_CDC_CONN_RX5_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX5_B2_CTL] = TABLA_A_CDC_CONN_RX5_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX6_B1_CTL] = TABLA_A_CDC_CONN_RX6_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX6_B2_CTL] = TABLA_A_CDC_CONN_RX6_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX7_B1_CTL] = TABLA_A_CDC_CONN_RX7_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX7_B2_CTL] = TABLA_A_CDC_CONN_RX7_B2_CTL__POR, + [TABLA_A_CDC_CONN_ANC_B1_CTL] = TABLA_A_CDC_CONN_ANC_B1_CTL__POR, + [TABLA_A_CDC_CONN_ANC_B2_CTL] = TABLA_A_CDC_CONN_ANC_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_B1_CTL] = TABLA_A_CDC_CONN_TX_B1_CTL__POR, + [TABLA_A_CDC_CONN_TX_B2_CTL] = TABLA_A_CDC_CONN_TX_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_B3_CTL] = TABLA_A_CDC_CONN_TX_B3_CTL__POR, + [TABLA_A_CDC_CONN_TX_B4_CTL] = TABLA_A_CDC_CONN_TX_B4_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B1_CTL] = TABLA_A_CDC_CONN_EQ1_B1_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B2_CTL] = TABLA_A_CDC_CONN_EQ1_B2_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B3_CTL] = TABLA_A_CDC_CONN_EQ1_B3_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B4_CTL] = TABLA_A_CDC_CONN_EQ1_B4_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B1_CTL] = TABLA_A_CDC_CONN_EQ2_B1_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B2_CTL] = TABLA_A_CDC_CONN_EQ2_B2_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B3_CTL] = TABLA_A_CDC_CONN_EQ2_B3_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B4_CTL] = TABLA_A_CDC_CONN_EQ2_B4_CTL__POR, + [TABLA_A_CDC_CONN_SRC1_B1_CTL] = TABLA_A_CDC_CONN_SRC1_B1_CTL__POR, + [TABLA_A_CDC_CONN_SRC1_B2_CTL] = TABLA_A_CDC_CONN_SRC1_B2_CTL__POR, + [TABLA_A_CDC_CONN_SRC2_B1_CTL] = TABLA_A_CDC_CONN_SRC2_B1_CTL__POR, + [TABLA_A_CDC_CONN_SRC2_B2_CTL] = TABLA_A_CDC_CONN_SRC2_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B1_CTL] = TABLA_A_CDC_CONN_TX_SB_B1_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B2_CTL] = TABLA_A_CDC_CONN_TX_SB_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B3_CTL] = TABLA_A_CDC_CONN_TX_SB_B3_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B4_CTL] = TABLA_A_CDC_CONN_TX_SB_B4_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B5_CTL] = TABLA_A_CDC_CONN_TX_SB_B5_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B6_CTL] = TABLA_A_CDC_CONN_TX_SB_B6_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B7_CTL] = TABLA_A_CDC_CONN_TX_SB_B7_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B8_CTL] = TABLA_A_CDC_CONN_TX_SB_B8_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B9_CTL] = TABLA_A_CDC_CONN_TX_SB_B9_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B10_CTL] = TABLA_A_CDC_CONN_TX_SB_B10_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B11_CTL] = TABLA_A_CDC_CONN_TX_SB_B11_CTL__POR, + [TABLA_A_CDC_CONN_RX_SB_B1_CTL] = TABLA_A_CDC_CONN_RX_SB_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX_SB_B2_CTL] = TABLA_A_CDC_CONN_RX_SB_B2_CTL__POR, + [TABLA_A_CDC_CONN_CLSG_CTL] = TABLA_A_CDC_CONN_CLSG_CTL__POR, + [TABLA_A_CDC_CONN_SPARE] = TABLA_A_CDC_CONN_SPARE__POR, + [TABLA_A_CDC_MBHC_EN_CTL] = TABLA_A_CDC_MBHC_EN_CTL__POR, + [TABLA_A_CDC_MBHC_FEATURE_B1_CFG] = + TABLA_A_CDC_MBHC_FEATURE_B1_CFG__POR, + [TABLA_A_CDC_MBHC_FEATURE_B2_CFG] = + TABLA_A_CDC_MBHC_FEATURE_B2_CFG__POR, + [TABLA_A_CDC_MBHC_TIMER_B1_CTL] = TABLA_A_CDC_MBHC_TIMER_B1_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B2_CTL] = TABLA_A_CDC_MBHC_TIMER_B2_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B3_CTL] = TABLA_A_CDC_MBHC_TIMER_B3_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B4_CTL] = TABLA_A_CDC_MBHC_TIMER_B4_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B5_CTL] = TABLA_A_CDC_MBHC_TIMER_B5_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B6_CTL] = TABLA_A_CDC_MBHC_TIMER_B6_CTL__POR, + [TABLA_A_CDC_MBHC_B1_STATUS] = TABLA_A_CDC_MBHC_B1_STATUS__POR, + [TABLA_A_CDC_MBHC_B2_STATUS] = TABLA_A_CDC_MBHC_B2_STATUS__POR, + [TABLA_A_CDC_MBHC_B3_STATUS] = TABLA_A_CDC_MBHC_B3_STATUS__POR, + [TABLA_A_CDC_MBHC_B4_STATUS] = TABLA_A_CDC_MBHC_B4_STATUS__POR, + [TABLA_A_CDC_MBHC_B5_STATUS] = TABLA_A_CDC_MBHC_B5_STATUS__POR, + [TABLA_A_CDC_MBHC_B1_CTL] = TABLA_A_CDC_MBHC_B1_CTL__POR, + [TABLA_A_CDC_MBHC_B2_CTL] = TABLA_A_CDC_MBHC_B2_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B1_CTL] = TABLA_A_CDC_MBHC_VOLT_B1_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B2_CTL] = TABLA_A_CDC_MBHC_VOLT_B2_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B3_CTL] = TABLA_A_CDC_MBHC_VOLT_B3_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B4_CTL] = TABLA_A_CDC_MBHC_VOLT_B4_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B5_CTL] = TABLA_A_CDC_MBHC_VOLT_B5_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B6_CTL] = TABLA_A_CDC_MBHC_VOLT_B6_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B7_CTL] = TABLA_A_CDC_MBHC_VOLT_B7_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B8_CTL] = TABLA_A_CDC_MBHC_VOLT_B8_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B9_CTL] = TABLA_A_CDC_MBHC_VOLT_B9_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B10_CTL] = TABLA_A_CDC_MBHC_VOLT_B10_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B11_CTL] = TABLA_A_CDC_MBHC_VOLT_B11_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B12_CTL] = TABLA_A_CDC_MBHC_VOLT_B12_CTL__POR, + [TABLA_A_CDC_MBHC_CLK_CTL] = TABLA_A_CDC_MBHC_CLK_CTL__POR, + [TABLA_A_CDC_MBHC_INT_CTL] = TABLA_A_CDC_MBHC_INT_CTL__POR, + [TABLA_A_CDC_MBHC_DEBUG_CTL] = TABLA_A_CDC_MBHC_DEBUG_CTL__POR, + [TABLA_A_CDC_MBHC_SPARE] = TABLA_A_CDC_MBHC_SPARE__POR, +}; diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c new file mode 100644 index 000000000000..eaece0ce86c2 --- /dev/null +++ b/sound/soc/codecs/wcd9310.c @@ -0,0 +1,7792 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9310.h" + +#define WCD9310_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) + + +#define NUM_DECIMATORS 10 +#define NUM_INTERPOLATORS 7 +#define BITS_PER_REG 8 +#define TABLA_CFILT_FAST_MODE 0x00 +#define TABLA_CFILT_SLOW_MODE 0x40 +#define MBHC_FW_READ_ATTEMPTS 15 +#define MBHC_FW_READ_TIMEOUT 2000000 + +enum { + MBHC_USE_HPHL_TRIGGER = 1, + MBHC_USE_MB_TRIGGER = 2 +}; + +#define MBHC_NUM_DCE_PLUG_DETECT 3 +#define NUM_ATTEMPTS_INSERT_DETECT 25 +#define NUM_ATTEMPTS_TO_REPORT 5 + +#define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \ + SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED) + +#define TABLA_I2S_MASTER_MODE_MASK 0x08 + +#define TABLA_OCP_ATTEMPT 1 + +#define AIF1_PB 1 +#define AIF1_CAP 2 +#define AIF2_PB 3 +#define AIF2_CAP 4 +#define AIF3_CAP 5 +#define AIF3_PB 6 + +#define NUM_CODEC_DAIS 6 +#define TABLA_COMP_DIGITAL_GAIN_OFFSET 3 + +struct tabla_codec_dai_data { + u32 rate; + u32 *ch_num; + u32 ch_act; + u32 ch_tot; +}; + +#define TABLA_MCLK_RATE_12288KHZ 12288000 +#define TABLA_MCLK_RATE_9600KHZ 9600000 + +#define TABLA_FAKE_INS_THRESHOLD_MS 2500 +#define TABLA_FAKE_REMOVAL_MIN_PERIOD_MS 50 + +#define TABLA_MBHC_BUTTON_MIN 0x8000 + +#define TABLA_MBHC_FAKE_INSERT_LOW 10 +#define TABLA_MBHC_FAKE_INSERT_HIGH 80 +#define TABLA_MBHC_FAKE_INS_HIGH_NO_GPIO 150 + +#define TABLA_MBHC_STATUS_REL_DETECTION 0x0C + +#define TABLA_MBHC_GPIO_REL_DEBOUNCE_TIME_MS 200 + +#define TABLA_MBHC_FAKE_INS_DELTA_MV 200 +#define TABLA_MBHC_FAKE_INS_DELTA_SCALED_MV 300 + +#define TABLA_HS_DETECT_PLUG_TIME_MS (5 * 1000) +#define TABLA_HS_DETECT_PLUG_INERVAL_MS 100 + +#define TABLA_GPIO_IRQ_DEBOUNCE_TIME_US 5000 + +#define TABLA_MBHC_GND_MIC_SWAP_THRESHOLD 2 + +#define TABLA_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0) +#define TABLA_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0) + +static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0); +static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); +static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); +static struct snd_soc_dai_driver tabla_dai[]; +static const DECLARE_TLV_DB_SCALE(aux_pga_gain, 0, 2, 0); + +enum tabla_bandgap_type { + TABLA_BANDGAP_OFF = 0, + TABLA_BANDGAP_AUDIO_MODE, + TABLA_BANDGAP_MBHC_MODE, +}; + +struct mbhc_micbias_regs { + u16 cfilt_val; + u16 cfilt_ctl; + u16 mbhc_reg; + u16 int_rbias; + u16 ctl_reg; + u8 cfilt_sel; +}; + +/* Codec supports 2 IIR filters */ +enum { + IIR1 = 0, + IIR2, + IIR_MAX, +}; +/* Codec supports 5 bands */ +enum { + BAND1 = 0, + BAND2, + BAND3, + BAND4, + BAND5, + BAND_MAX, +}; + +enum { + COMPANDER_1 = 0, + COMPANDER_2, + COMPANDER_MAX, +}; + +enum { + COMPANDER_FS_8KHZ = 0, + COMPANDER_FS_16KHZ, + COMPANDER_FS_32KHZ, + COMPANDER_FS_48KHZ, + COMPANDER_FS_96KHZ, + COMPANDER_FS_192KHZ, + COMPANDER_FS_MAX, +}; + +/* Flags to track of PA and DAC state. + * PA and DAC should be tracked separately as AUXPGA loopback requires + * only PA to be turned on without DAC being on. */ +enum tabla_priv_ack_flags { + TABLA_HPHL_PA_OFF_ACK = 0, + TABLA_HPHR_PA_OFF_ACK, + TABLA_HPHL_DAC_OFF_ACK, + TABLA_HPHR_DAC_OFF_ACK +}; + + +struct comp_sample_dependent_params { + u32 peak_det_timeout; + u32 rms_meter_div_fact; + u32 rms_meter_resamp_fact; +}; + +/* Data used by MBHC */ +struct mbhc_internal_cal_data { + u16 dce_z; + u16 dce_mb; + u16 sta_z; + u16 sta_mb; + u32 t_sta_dce; + u32 t_dce; + u32 t_sta; + u32 micb_mv; + u16 v_ins_hu; + u16 v_ins_h; + u16 v_b1_hu; + u16 v_b1_h; + u16 v_b1_huc; + u16 v_brh; + u16 v_brl; + u16 v_no_mic; + u8 npoll; + u8 nbounce_wait; + s16 adj_v_hs_max; + u16 adj_v_ins_hu; + u16 adj_v_ins_h; + s16 v_inval_ins_low; + s16 v_inval_ins_high; +}; + +struct tabla_reg_address { + u16 micb_4_ctl; + u16 micb_4_int_rbias; + u16 micb_4_mbhc; +}; + +enum tabla_mbhc_plug_type { + PLUG_TYPE_INVALID = -1, + PLUG_TYPE_NONE, + PLUG_TYPE_HEADSET, + PLUG_TYPE_HEADPHONE, + PLUG_TYPE_HIGH_HPH, + PLUG_TYPE_GND_MIC_SWAP, +}; + +enum tabla_mbhc_state { + MBHC_STATE_NONE = -1, + MBHC_STATE_POTENTIAL, + MBHC_STATE_POTENTIAL_RECOVERY, + MBHC_STATE_RELEASE, +}; + +struct hpf_work { + struct tabla_priv *tabla; + u32 decimator; + u8 tx_hpf_cut_of_freq; + struct delayed_work dwork; +}; + +static struct hpf_work tx_hpf_work[NUM_DECIMATORS]; + +struct tabla_priv { + struct snd_soc_codec *codec; + struct tabla_reg_address reg_addr; + u32 adc_count; + u32 cfilt1_cnt; + u32 cfilt2_cnt; + u32 cfilt3_cnt; + u32 rx_bias_count; + s32 dmic_1_2_clk_cnt; + s32 dmic_3_4_clk_cnt; + s32 dmic_5_6_clk_cnt; + + enum tabla_bandgap_type bandgap_type; + bool mclk_enabled; + bool clock_active; + bool config_mode_active; + bool mbhc_polling_active; + unsigned long mbhc_fake_ins_start; + int buttons_pressed; + enum tabla_mbhc_state mbhc_state; + struct tabla_mbhc_config mbhc_cfg; + struct mbhc_internal_cal_data mbhc_data; + + struct wcd9xxx_pdata *pdata; + u32 anc_slot; + + bool no_mic_headset_override; + /* Delayed work to report long button press */ + struct delayed_work mbhc_btn_dwork; + + struct mbhc_micbias_regs mbhc_bias_regs; + bool mbhc_micbias_switched; + + /* track PA/DAC state */ + unsigned long hph_pa_dac_state; + + /*track tabla interface type*/ + u8 intf_type; + + u32 hph_status; /* track headhpone status */ + /* define separate work for left and right headphone OCP to avoid + * additional checking on which OCP event to report so no locking + * to ensure synchronization is required + */ + struct work_struct hphlocp_work; /* reporting left hph ocp off */ + struct work_struct hphrocp_work; /* reporting right hph ocp off */ + + u8 hphlocp_cnt; /* headphone left ocp retry */ + u8 hphrocp_cnt; /* headphone right ocp retry */ + + /* Work to perform MBHC Firmware Read */ + struct delayed_work mbhc_firmware_dwork; + const struct firmware *mbhc_fw; + + /* num of slim ports required */ + struct tabla_codec_dai_data dai[NUM_CODEC_DAIS]; + + /*compander*/ + int comp_enabled[COMPANDER_MAX]; + u32 comp_fs[COMPANDER_MAX]; + + /* Maintain the status of AUX PGA */ + int aux_pga_cnt; + u8 aux_l_gain; + u8 aux_r_gain; + + struct delayed_work mbhc_insert_dwork; + unsigned long mbhc_last_resume; /* in jiffies */ + + u8 current_plug; + struct work_struct hs_correct_plug_work; + bool hs_detect_work_stop; + bool hs_polling_irq_prepared; + bool lpi_enabled; /* low power insertion detection */ + bool in_gpio_handler; + /* Currently, only used for mbhc purpose, to protect + * concurrent execution of mbhc threaded irq handlers and + * kill race between DAPM and MBHC.But can serve as a + * general lock to protect codec resource + */ + struct mutex codec_resource_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_poke; + struct dentry *debugfs_mbhc; +#endif +}; + +static const u32 comp_shift[] = { + 0, + 2, +}; + +static const int comp_rx_path[] = { + COMPANDER_1, + COMPANDER_1, + COMPANDER_2, + COMPANDER_2, + COMPANDER_2, + COMPANDER_2, + COMPANDER_MAX, +}; + +static const struct comp_sample_dependent_params comp_samp_params[] = { + { + .peak_det_timeout = 0x2, + .rms_meter_div_fact = 0x8 << 4, + .rms_meter_resamp_fact = 0x21, + }, + { + .peak_det_timeout = 0x3, + .rms_meter_div_fact = 0x9 << 4, + .rms_meter_resamp_fact = 0x28, + }, + + { + .peak_det_timeout = 0x5, + .rms_meter_div_fact = 0xB << 4, + .rms_meter_resamp_fact = 0x28, + }, + + { + .peak_det_timeout = 0x5, + .rms_meter_div_fact = 0xB << 4, + .rms_meter_resamp_fact = 0x28, + }, +}; + +static unsigned short rx_digital_gain_reg[] = { + TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + TABLA_A_CDC_RX7_VOL_CTL_B2_CTL, +}; + + +static unsigned short tx_digital_gain_reg[] = { + TABLA_A_CDC_TX1_VOL_CTL_GAIN, + TABLA_A_CDC_TX2_VOL_CTL_GAIN, + TABLA_A_CDC_TX3_VOL_CTL_GAIN, + TABLA_A_CDC_TX4_VOL_CTL_GAIN, + TABLA_A_CDC_TX5_VOL_CTL_GAIN, + TABLA_A_CDC_TX6_VOL_CTL_GAIN, + TABLA_A_CDC_TX7_VOL_CTL_GAIN, + TABLA_A_CDC_TX8_VOL_CTL_GAIN, + TABLA_A_CDC_TX9_VOL_CTL_GAIN, + TABLA_A_CDC_TX10_VOL_CTL_GAIN, +}; + +static int tabla_codec_enable_charge_pump(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x01, + 0x01); + snd_soc_update_bits(codec, TABLA_A_CDC_CLSG_CTL, 0x08, 0x08); + usleep_range(200, 200); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x10, 0x00); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x10); + usleep_range(20, 20); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x08, 0x08); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x10, 0x10); + snd_soc_update_bits(codec, TABLA_A_CDC_CLSG_CTL, 0x08, 0x00); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x01, + 0x00); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x08, 0x00); + break; + } + return 0; +} + +static int tabla_get_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = tabla->anc_slot; + return 0; +} + +static int tabla_put_anc_slot(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + tabla->anc_slot = ucontrol->value.integer.value[0]; + return 0; +} + +static int tabla_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + ear_pa_gain = snd_soc_read(codec, TABLA_A_RX_EAR_GAIN); + + ear_pa_gain = ear_pa_gain >> 5; + + if (ear_pa_gain == 0x00) { + ucontrol->value.integer.value[0] = 0; + } else if (ear_pa_gain == 0x04) { + ucontrol->value.integer.value[0] = 1; + } else { + pr_err("%s: ERROR: Unsupported Ear Gain = 0x%x\n", + __func__, ear_pa_gain); + return -EINVAL; + } + + pr_debug("%s: ear_pa_gain = 0x%x\n", __func__, ear_pa_gain); + + return 0; +} + +static int tabla_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 ear_pa_gain; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s: ucontrol->value.integer.value[0] = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + ear_pa_gain = 0x00; + break; + case 1: + ear_pa_gain = 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_RX_EAR_GAIN, 0xE0, ear_pa_gain); + return 0; +} + +static int tabla_get_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + snd_soc_read(codec, (TABLA_A_CDC_IIR1_CTL + 16 * iir_idx)) & + (1 << band_idx); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0]); + return 0; +} + +static int tabla_put_iir_enable_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + + /* Mask first 5 bits, 6-8 are reserved */ + snd_soc_update_bits(codec, (TABLA_A_CDC_IIR1_CTL + 16 * iir_idx), + (1 << band_idx), (value << band_idx)); + + pr_debug("%s: IIR #%d band #%d enable %d\n", __func__, + iir_idx, band_idx, value); + return 0; +} +static uint32_t get_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx) +{ + /* Address does not automatically update if reading */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + (band_idx * BAND_MAX + coeff_idx) & 0x1F); + + /* Mask bits top 2 bits since they are reserved */ + return ((snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx)) << 24) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx)) << 16) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx)) << 8) | + (snd_soc_read(codec, + (TABLA_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx)))) & + 0x3FFFFFFF; +} + +static int tabla_get_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + get_iir_band_coeff(codec, iir_idx, band_idx, 0); + ucontrol->value.integer.value[1] = + get_iir_band_coeff(codec, iir_idx, band_idx, 1); + ucontrol->value.integer.value[2] = + get_iir_band_coeff(codec, iir_idx, band_idx, 2); + ucontrol->value.integer.value[3] = + get_iir_band_coeff(codec, iir_idx, band_idx, 3); + ucontrol->value.integer.value[4] = + get_iir_band_coeff(codec, iir_idx, band_idx, 4); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[0], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[1], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[2], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[3], + __func__, iir_idx, band_idx, + (uint32_t)ucontrol->value.integer.value[4]); + return 0; +} + +static void set_iir_band_coeff(struct snd_soc_codec *codec, + int iir_idx, int band_idx, + int coeff_idx, uint32_t value) +{ + /* Mask top 3 bits, 6-8 are reserved */ + /* Update address manually each time */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B1_CTL + 16 * iir_idx), + (band_idx * BAND_MAX + coeff_idx) & 0x1F); + + /* Mask top 2 bits, 7-8 are reserved */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B2_CTL + 16 * iir_idx), + (value >> 24) & 0x3F); + + /* Isolate 8bits at a time */ + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B3_CTL + 16 * iir_idx), + (value >> 16) & 0xFF); + + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B4_CTL + 16 * iir_idx), + (value >> 8) & 0xFF); + + snd_soc_write(codec, + (TABLA_A_CDC_IIR1_COEF_B5_CTL + 16 * iir_idx), + value & 0xFF); +} + +static int tabla_put_iir_band_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int iir_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + set_iir_band_coeff(codec, iir_idx, band_idx, 0, + ucontrol->value.integer.value[0]); + set_iir_band_coeff(codec, iir_idx, band_idx, 1, + ucontrol->value.integer.value[1]); + set_iir_band_coeff(codec, iir_idx, band_idx, 2, + ucontrol->value.integer.value[2]); + set_iir_band_coeff(codec, iir_idx, band_idx, 3, + ucontrol->value.integer.value[3]); + set_iir_band_coeff(codec, iir_idx, band_idx, 4, + ucontrol->value.integer.value[4]); + + pr_debug("%s: IIR #%d band #%d b0 = 0x%x\n" + "%s: IIR #%d band #%d b1 = 0x%x\n" + "%s: IIR #%d band #%d b2 = 0x%x\n" + "%s: IIR #%d band #%d a1 = 0x%x\n" + "%s: IIR #%d band #%d a2 = 0x%x\n", + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 0), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 1), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 2), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 3), + __func__, iir_idx, band_idx, + get_iir_band_coeff(codec, iir_idx, band_idx, 4)); + return 0; +} + +static int tabla_compander_gain_offset( + struct snd_soc_codec *codec, u32 enable, + unsigned int reg, int mask, int event) +{ + int pa_mode = snd_soc_read(codec, reg) & mask; + int gain_offset = 0; + /* if PMU && enable is 1-> offset is 3 + * if PMU && enable is 0-> offset is 0 + * if PMD && pa_mode is PA -> offset is 0: PMU compander is off + * if PMD && pa_mode is comp -> offset is -3: PMU compander is on. + */ + + if (SND_SOC_DAPM_EVENT_ON(event) && (enable != 0)) + gain_offset = TABLA_COMP_DIGITAL_GAIN_OFFSET; + if (SND_SOC_DAPM_EVENT_OFF(event) && (pa_mode == 0)) + gain_offset = -TABLA_COMP_DIGITAL_GAIN_OFFSET; + return gain_offset; +} + + +static int tabla_config_gain_compander( + struct snd_soc_codec *codec, + u32 compander, u32 enable, int event) +{ + int value = 0; + int mask = 1 << 4; + int gain = 0; + int gain_offset; + if (compander >= COMPANDER_MAX) { + pr_err("%s: Error, invalid compander channel\n", __func__); + return -EINVAL; + } + + if ((enable == 0) || SND_SOC_DAPM_EVENT_OFF(event)) + value = 1 << 4; + + if (compander == COMPANDER_1) { + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_HPH_L_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX1_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_HPH_R_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX2_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + } else if (compander == COMPANDER_2) { + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_1_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_1_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX3_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_3_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_3_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX4_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_2_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_2_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX5_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + gain_offset = tabla_compander_gain_offset(codec, enable, + TABLA_A_RX_LINE_4_GAIN, mask, event); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_4_GAIN, mask, value); + gain = snd_soc_read(codec, TABLA_A_CDC_RX6_VOL_CTL_B2_CTL); + snd_soc_update_bits(codec, TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + 0xFF, gain - gain_offset); + } + return 0; +} +static int tabla_get_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int comp = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->max; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tabla->comp_enabled[comp]; + + return 0; +} + +static int tabla_set_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + int comp = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->max; + int value = ucontrol->value.integer.value[0]; + + if (value == tabla->comp_enabled[comp]) { + pr_debug("%s: compander #%d enable %d no change\n", + __func__, comp, value); + return 0; + } + tabla->comp_enabled[comp] = value; + return 0; +} + + +static int tabla_config_compander(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u32 rate = tabla->comp_fs[w->shift]; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (tabla->comp_enabled[w->shift] != 0) { + /* Enable both L/R compander clocks */ + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_B2_CTL, + 0x03 << comp_shift[w->shift], + 0x03 << comp_shift[w->shift]); + /* Clar the HALT for the compander*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 1 << 2, 0); + /* Toggle compander reset bits*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_OTHR_RESET_CTL, + 0x03 << comp_shift[w->shift], + 0x03 << comp_shift[w->shift]); + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_OTHR_RESET_CTL, + 0x03 << comp_shift[w->shift], 0); + tabla_config_gain_compander(codec, w->shift, 1, event); + /* Update the RMS meter resampling*/ + snd_soc_update_bits(codec, + TABLA_A_CDC_COMP1_B3_CTL + + w->shift * 8, 0xFF, 0x01); + /* Wait for 1ms*/ + usleep_range(1000, 1000); + } + break; + case SND_SOC_DAPM_POST_PMU: + /* Set sample rate dependent paramater*/ + if (tabla->comp_enabled[w->shift] != 0) { + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_FS_CFG + + w->shift * 8, 0x03, rate); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B2_CTL + + w->shift * 8, 0x0F, + comp_samp_params[rate].peak_det_timeout); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B2_CTL + + w->shift * 8, 0xF0, + comp_samp_params[rate].rms_meter_div_fact); + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B3_CTL + + w->shift * 8, 0xFF, + comp_samp_params[rate].rms_meter_resamp_fact); + /* Compander enable -> 0x370/0x378*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 0x03, 0x03); + } + break; + case SND_SOC_DAPM_PRE_PMD: + /* Halt the compander*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 1 << 2, 1 << 2); + break; + case SND_SOC_DAPM_POST_PMD: + /* Restore the gain */ + tabla_config_gain_compander(codec, w->shift, + tabla->comp_enabled[w->shift], event); + /* Disable the compander*/ + snd_soc_update_bits(codec, TABLA_A_CDC_COMP1_B1_CTL + + w->shift * 8, 0x03, 0x00); + /* Turn off the clock for compander in pair*/ + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_B2_CTL, + 0x03 << comp_shift[w->shift], 0); + break; + } + return 0; +} + +static const char *tabla_ear_pa_gain_text[] = {"POS_6_DB", "POS_2_DB"}; +static const struct soc_enum tabla_ear_pa_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(2, tabla_ear_pa_gain_text), +}; + +/*cut of frequency for high pass filter*/ +static const char *cf_text[] = { + "MIN_3DB_4Hz", "MIN_3DB_75Hz", "MIN_3DB_150Hz" +}; + +static const struct soc_enum cf_dec1_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX1_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec2_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX2_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec3_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX3_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec4_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX4_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec5_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX5_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec6_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX6_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec7_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX7_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec8_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX8_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec9_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX9_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_dec10_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_TX10_MUX_CTL, 4, 3, cf_text); + +static const struct soc_enum cf_rxmix1_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX1_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix2_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX2_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix3_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX3_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix4_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX4_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix5_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX5_B4_CTL, 1, 3, cf_text) +; +static const struct soc_enum cf_rxmix6_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX6_B4_CTL, 1, 3, cf_text); + +static const struct soc_enum cf_rxmix7_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX7_B4_CTL, 1, 3, cf_text); + +static const struct snd_kcontrol_new tabla_snd_controls[] = { + + SOC_ENUM_EXT("EAR PA Gain", tabla_ear_pa_gain_enum[0], + tabla_pa_gain_get, tabla_pa_gain_put), + + SOC_SINGLE_TLV("LINEOUT1 Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT2 Volume", TABLA_A_RX_LINE_2_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT3 Volume", TABLA_A_RX_LINE_3_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT4 Volume", TABLA_A_RX_LINE_4_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT5 Volume", TABLA_A_RX_LINE_5_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_TLV("HPHL Volume", TABLA_A_RX_HPH_L_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("HPHR Volume", TABLA_A_RX_HPH_R_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_S8_TLV("RX1 Digital Volume", TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX2 Digital Volume", TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX3 Digital Volume", TABLA_A_CDC_RX3_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX4 Digital Volume", TABLA_A_CDC_RX4_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX5 Digital Volume", TABLA_A_CDC_RX5_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX6 Digital Volume", TABLA_A_CDC_RX6_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("RX7 Digital Volume", TABLA_A_CDC_RX7_VOL_CTL_B2_CTL, + -84, 40, digital_gain), + + SOC_SINGLE_S8_TLV("DEC1 Volume", TABLA_A_CDC_TX1_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC2 Volume", TABLA_A_CDC_TX2_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC3 Volume", TABLA_A_CDC_TX3_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC4 Volume", TABLA_A_CDC_TX4_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC5 Volume", TABLA_A_CDC_TX5_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC6 Volume", TABLA_A_CDC_TX6_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC7 Volume", TABLA_A_CDC_TX7_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC8 Volume", TABLA_A_CDC_TX8_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC9 Volume", TABLA_A_CDC_TX9_VOL_CTL_GAIN, -84, 40, + digital_gain), + SOC_SINGLE_S8_TLV("DEC10 Volume", TABLA_A_CDC_TX10_VOL_CTL_GAIN, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP1 Volume", TABLA_A_CDC_IIR1_GAIN_B1_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP2 Volume", TABLA_A_CDC_IIR1_GAIN_B2_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP3 Volume", TABLA_A_CDC_IIR1_GAIN_B3_CTL, -84, + 40, digital_gain), + SOC_SINGLE_S8_TLV("IIR1 INP4 Volume", TABLA_A_CDC_IIR1_GAIN_B4_CTL, -84, + 40, digital_gain), + SOC_SINGLE_TLV("ADC1 Volume", TABLA_A_TX_1_2_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", TABLA_A_TX_1_2_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", TABLA_A_TX_3_4_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC4 Volume", TABLA_A_TX_3_4_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC5 Volume", TABLA_A_TX_5_6_EN, 5, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC6 Volume", TABLA_A_TX_5_6_EN, 1, 3, 0, analog_gain), + + SOC_SINGLE_TLV("AUX_PGA_LEFT Volume", TABLA_A_AUX_L_GAIN, 0, 39, 0, + aux_pga_gain), + SOC_SINGLE_TLV("AUX_PGA_RIGHT Volume", TABLA_A_AUX_R_GAIN, 0, 39, 0, + aux_pga_gain), + + SOC_SINGLE("MICBIAS1 CAPLESS Switch", TABLA_A_MICB_1_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS2 CAPLESS Switch", TABLA_A_MICB_2_CTL, 4, 1, 1), + SOC_SINGLE("MICBIAS3 CAPLESS Switch", TABLA_A_MICB_3_CTL, 4, 1, 1), + + SOC_SINGLE_EXT("ANC Slot", SND_SOC_NOPM, 0, 0, 100, tabla_get_anc_slot, + tabla_put_anc_slot), + SOC_ENUM("TX1 HPF cut off", cf_dec1_enum), + SOC_ENUM("TX2 HPF cut off", cf_dec2_enum), + SOC_ENUM("TX3 HPF cut off", cf_dec3_enum), + SOC_ENUM("TX4 HPF cut off", cf_dec4_enum), + SOC_ENUM("TX5 HPF cut off", cf_dec5_enum), + SOC_ENUM("TX6 HPF cut off", cf_dec6_enum), + SOC_ENUM("TX7 HPF cut off", cf_dec7_enum), + SOC_ENUM("TX8 HPF cut off", cf_dec8_enum), + SOC_ENUM("TX9 HPF cut off", cf_dec9_enum), + SOC_ENUM("TX10 HPF cut off", cf_dec10_enum), + + SOC_SINGLE("TX1 HPF Switch", TABLA_A_CDC_TX1_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX2 HPF Switch", TABLA_A_CDC_TX2_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX3 HPF Switch", TABLA_A_CDC_TX3_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX4 HPF Switch", TABLA_A_CDC_TX4_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX5 HPF Switch", TABLA_A_CDC_TX5_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX6 HPF Switch", TABLA_A_CDC_TX6_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX7 HPF Switch", TABLA_A_CDC_TX7_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX8 HPF Switch", TABLA_A_CDC_TX8_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX9 HPF Switch", TABLA_A_CDC_TX9_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX10 HPF Switch", TABLA_A_CDC_TX10_MUX_CTL, 3, 1, 0), + + SOC_SINGLE("RX1 HPF Switch", TABLA_A_CDC_RX1_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX2 HPF Switch", TABLA_A_CDC_RX2_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX3 HPF Switch", TABLA_A_CDC_RX3_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX4 HPF Switch", TABLA_A_CDC_RX4_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX5 HPF Switch", TABLA_A_CDC_RX5_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX6 HPF Switch", TABLA_A_CDC_RX6_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX7 HPF Switch", TABLA_A_CDC_RX7_B5_CTL, 2, 1, 0), + + SOC_ENUM("RX1 HPF cut off", cf_rxmix1_enum), + SOC_ENUM("RX2 HPF cut off", cf_rxmix2_enum), + SOC_ENUM("RX3 HPF cut off", cf_rxmix3_enum), + SOC_ENUM("RX4 HPF cut off", cf_rxmix4_enum), + SOC_ENUM("RX5 HPF cut off", cf_rxmix5_enum), + SOC_ENUM("RX6 HPF cut off", cf_rxmix6_enum), + SOC_ENUM("RX7 HPF cut off", cf_rxmix7_enum), + + SOC_SINGLE_EXT("IIR1 Enable Band1", IIR1, BAND1, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band2", IIR1, BAND2, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band3", IIR1, BAND3, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band4", IIR1, BAND4, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR1 Enable Band5", IIR1, BAND5, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band1", IIR2, BAND1, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band2", IIR2, BAND2, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band3", IIR2, BAND3, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band4", IIR2, BAND4, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + SOC_SINGLE_EXT("IIR2 Enable Band5", IIR2, BAND5, 1, 0, + tabla_get_iir_enable_audio_mixer, tabla_put_iir_enable_audio_mixer), + + SOC_SINGLE_MULTI_EXT("IIR1 Band1", IIR1, BAND1, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band2", IIR1, BAND2, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band3", IIR1, BAND3, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band4", IIR1, BAND4, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR1 Band5", IIR1, BAND5, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band1", IIR2, BAND1, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band2", IIR2, BAND2, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band3", IIR2, BAND3, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band4", IIR2, BAND4, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("IIR2 Band5", IIR2, BAND5, 255, 0, 5, + tabla_get_iir_band_audio_mixer, tabla_put_iir_band_audio_mixer), + SOC_SINGLE_EXT("COMP1 Switch", SND_SOC_NOPM, 1, COMPANDER_1, 0, + tabla_get_compander, tabla_set_compander), + SOC_SINGLE_EXT("COMP2 Switch", SND_SOC_NOPM, 0, COMPANDER_2, 0, + tabla_get_compander, tabla_set_compander), +}; + +static const struct snd_kcontrol_new tabla_1_x_snd_controls[] = { + SOC_SINGLE("MICBIAS4 CAPLESS Switch", TABLA_1_A_MICB_4_CTL, 4, 1, 1), +}; + +static const struct snd_kcontrol_new tabla_2_higher_snd_controls[] = { + SOC_SINGLE("MICBIAS4 CAPLESS Switch", TABLA_2_A_MICB_4_CTL, 4, 1, 1), +}; + +static const char *rx_mix1_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2", "RX1", "RX2", "RX3", "RX4", + "RX5", "RX6", "RX7" +}; + +static const char *rx_mix2_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2" +}; + +static const char *rx_dsm_text[] = { + "CIC_OUT", "DSM_INV" +}; + +static const char *sb_tx1_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1" +}; + +static const char *sb_tx2_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC2" +}; + +static const char *sb_tx3_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC3" +}; + +static const char *sb_tx4_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC4" +}; + +static const char *sb_tx5_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC5" +}; + +static const char *sb_tx6_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC6" +}; + +static const char const *sb_tx7_to_tx10_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1", "DEC2", "DEC3", "DEC4", "DEC5", "DEC6", "DEC7", "DEC8", + "DEC9", "DEC10" +}; + +static const char *dec1_mux_text[] = { + "ZERO", "DMIC1", "ADC6", +}; + +static const char *dec2_mux_text[] = { + "ZERO", "DMIC2", "ADC5", +}; + +static const char *dec3_mux_text[] = { + "ZERO", "DMIC3", "ADC4", +}; + +static const char *dec4_mux_text[] = { + "ZERO", "DMIC4", "ADC3", +}; + +static const char *dec5_mux_text[] = { + "ZERO", "DMIC5", "ADC2", +}; + +static const char *dec6_mux_text[] = { + "ZERO", "DMIC6", "ADC1", +}; + +static const char const *dec7_mux_text[] = { + "ZERO", "DMIC1", "DMIC6", "ADC1", "ADC6", "ANC1_FB", "ANC2_FB", +}; + +static const char *dec8_mux_text[] = { + "ZERO", "DMIC2", "DMIC5", "ADC2", "ADC5", +}; + +static const char *dec9_mux_text[] = { + "ZERO", "DMIC4", "DMIC5", "ADC2", "ADC3", "ADCMB", "ANC1_FB", "ANC2_FB", +}; + +static const char *dec10_mux_text[] = { + "ZERO", "DMIC3", "DMIC6", "ADC1", "ADC4", "ADCMB", "ANC1_FB", "ANC2_FB", +}; + +static const char const *anc_mux_text[] = { + "ZERO", "ADC1", "ADC2", "ADC3", "ADC4", "ADC5", "ADC6", "ADC_MB", + "RSVD_1", "DMIC1", "DMIC2", "DMIC3", "DMIC4", "DMIC5", "DMIC6" +}; + +static const char const *anc1_fb_mux_text[] = { + "ZERO", "EAR_HPH_L", "EAR_LINE_1", +}; + +static const char *iir1_inp1_text[] = { + "ZERO", "DEC1", "DEC2", "DEC3", "DEC4", "DEC5", "DEC6", "DEC7", "DEC8", + "DEC9", "DEC10", "RX1", "RX2", "RX3", "RX4", "RX5", "RX6", "RX7" +}; + +static const struct soc_enum rx_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx_mix1_inp3_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B2_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx4_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX4_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx4_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX4_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx5_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX5_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx5_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX5_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx6_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX6_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx6_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX6_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx7_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX7_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx7_mix1_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX7_B1_CTL, 4, 12, rx_mix1_text); + +static const struct soc_enum rx1_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx1_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx2_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx2_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx3_mix2_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B3_CTL, 0, 5, rx_mix2_text); + +static const struct soc_enum rx3_mix2_inp2_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B3_CTL, 3, 5, rx_mix2_text); + +static const struct soc_enum rx4_dsm_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX4_B6_CTL, 4, 2, rx_dsm_text); + +static const struct soc_enum rx6_dsm_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_RX6_B6_CTL, 4, 2, rx_dsm_text); + +static const struct soc_enum sb_tx1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B1_CTL, 0, 9, sb_tx1_mux_text); + +static const struct soc_enum sb_tx2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B2_CTL, 0, 9, sb_tx2_mux_text); + +static const struct soc_enum sb_tx3_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B3_CTL, 0, 9, sb_tx3_mux_text); + +static const struct soc_enum sb_tx4_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B4_CTL, 0, 9, sb_tx4_mux_text); + +static const struct soc_enum sb_tx5_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B5_CTL, 0, 9, sb_tx5_mux_text); + +static const struct soc_enum sb_tx6_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B6_CTL, 0, 9, sb_tx6_mux_text); + +static const struct soc_enum sb_tx7_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B7_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx8_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B8_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx9_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B9_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx10_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B10_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum dec1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 0, 3, dec1_mux_text); + +static const struct soc_enum dec2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 2, 3, dec2_mux_text); + +static const struct soc_enum dec3_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 4, 3, dec3_mux_text); + +static const struct soc_enum dec4_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 6, 3, dec4_mux_text); + +static const struct soc_enum dec5_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 0, 3, dec5_mux_text); + +static const struct soc_enum dec6_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 2, 3, dec6_mux_text); + +static const struct soc_enum dec7_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 4, 7, dec7_mux_text); + +static const struct soc_enum dec8_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B3_CTL, 0, 7, dec8_mux_text); + +static const struct soc_enum dec9_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B3_CTL, 3, 8, dec9_mux_text); + +static const struct soc_enum dec10_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B4_CTL, 0, 8, dec10_mux_text); + +static const struct soc_enum anc1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B1_CTL, 0, 16, anc_mux_text); + +static const struct soc_enum anc2_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B1_CTL, 4, 16, anc_mux_text); + +static const struct soc_enum anc1_fb_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_ANC_B2_CTL, 0, 3, anc1_fb_mux_text); + +static const struct soc_enum iir1_inp1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_EQ1_B1_CTL, 0, 18, iir1_inp1_text); + +static const struct snd_kcontrol_new rx_mix1_inp1_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP1 Mux", rx_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx_mix1_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP2 Mux", rx_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx_mix1_inp3_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP3 Mux", rx_mix1_inp3_chain_enum); + +static const struct snd_kcontrol_new rx2_mix1_inp1_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP1 Mux", rx2_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx2_mix1_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP2 Mux", rx2_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx3_mix1_inp1_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP1 Mux", rx3_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx3_mix1_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP2 Mux", rx3_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx4_mix1_inp1_mux = + SOC_DAPM_ENUM("RX4 MIX1 INP1 Mux", rx4_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx4_mix1_inp2_mux = + SOC_DAPM_ENUM("RX4 MIX1 INP2 Mux", rx4_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx5_mix1_inp1_mux = + SOC_DAPM_ENUM("RX5 MIX1 INP1 Mux", rx5_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx5_mix1_inp2_mux = + SOC_DAPM_ENUM("RX5 MIX1 INP2 Mux", rx5_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx6_mix1_inp1_mux = + SOC_DAPM_ENUM("RX6 MIX1 INP1 Mux", rx6_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx6_mix1_inp2_mux = + SOC_DAPM_ENUM("RX6 MIX1 INP2 Mux", rx6_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx7_mix1_inp1_mux = + SOC_DAPM_ENUM("RX7 MIX1 INP1 Mux", rx7_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx7_mix1_inp2_mux = + SOC_DAPM_ENUM("RX7 MIX1 INP2 Mux", rx7_mix1_inp2_chain_enum); + +static const struct snd_kcontrol_new rx1_mix2_inp1_mux = + SOC_DAPM_ENUM("RX1 MIX2 INP1 Mux", rx1_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx1_mix2_inp2_mux = + SOC_DAPM_ENUM("RX1 MIX2 INP2 Mux", rx1_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx2_mix2_inp1_mux = + SOC_DAPM_ENUM("RX2 MIX2 INP1 Mux", rx2_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx2_mix2_inp2_mux = + SOC_DAPM_ENUM("RX2 MIX2 INP2 Mux", rx2_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx3_mix2_inp1_mux = + SOC_DAPM_ENUM("RX3 MIX2 INP1 Mux", rx3_mix2_inp1_chain_enum); + +static const struct snd_kcontrol_new rx3_mix2_inp2_mux = + SOC_DAPM_ENUM("RX3 MIX2 INP2 Mux", rx3_mix2_inp2_chain_enum); + +static const struct snd_kcontrol_new rx4_dsm_mux = + SOC_DAPM_ENUM("RX4 DSM MUX Mux", rx4_dsm_enum); + +static const struct snd_kcontrol_new rx6_dsm_mux = + SOC_DAPM_ENUM("RX6 DSM MUX Mux", rx6_dsm_enum); + +static const struct snd_kcontrol_new sb_tx1_mux = + SOC_DAPM_ENUM("SLIM TX1 MUX Mux", sb_tx1_mux_enum); + +static const struct snd_kcontrol_new sb_tx2_mux = + SOC_DAPM_ENUM("SLIM TX2 MUX Mux", sb_tx2_mux_enum); + +static const struct snd_kcontrol_new sb_tx3_mux = + SOC_DAPM_ENUM("SLIM TX3 MUX Mux", sb_tx3_mux_enum); + +static const struct snd_kcontrol_new sb_tx4_mux = + SOC_DAPM_ENUM("SLIM TX4 MUX Mux", sb_tx4_mux_enum); + +static const struct snd_kcontrol_new sb_tx5_mux = + SOC_DAPM_ENUM("SLIM TX5 MUX Mux", sb_tx5_mux_enum); + +static const struct snd_kcontrol_new sb_tx6_mux = + SOC_DAPM_ENUM("SLIM TX6 MUX Mux", sb_tx6_mux_enum); + +static const struct snd_kcontrol_new sb_tx7_mux = + SOC_DAPM_ENUM("SLIM TX7 MUX Mux", sb_tx7_mux_enum); + +static const struct snd_kcontrol_new sb_tx8_mux = + SOC_DAPM_ENUM("SLIM TX8 MUX Mux", sb_tx8_mux_enum); + +static const struct snd_kcontrol_new sb_tx9_mux = + SOC_DAPM_ENUM("SLIM TX9 MUX Mux", sb_tx9_mux_enum); + +static const struct snd_kcontrol_new sb_tx10_mux = + SOC_DAPM_ENUM("SLIM TX10 MUX Mux", sb_tx10_mux_enum); + + +static int wcd9310_put_dec_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *w = wlist->widgets[0]; + struct snd_soc_codec *codec = w->codec; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int dec_mux, decimator; + char *dec_name = NULL; + char *widget_name = NULL; + char *temp; + u16 tx_mux_ctl_reg; + u8 adc_dmic_sel = 0x0; + int ret = 0; + + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + dec_mux = ucontrol->value.enumerated.item[0]; + + widget_name = kstrndup(w->name, 15, GFP_KERNEL); + if (!widget_name) + return -ENOMEM; + temp = widget_name; + + dec_name = strsep(&widget_name, " "); + widget_name = temp; + if (!dec_name) { + pr_err("%s: Invalid decimator = %s\n", __func__, w->name); + ret = -EINVAL; + goto out; + } + + ret = kstrtouint(strpbrk(dec_name, "123456789"), 10, &decimator); + if (ret < 0) { + pr_err("%s: Invalid decimator = %s\n", __func__, dec_name); + ret = -EINVAL; + goto out; + } + + dev_dbg(w->dapm->dev, "%s(): widget = %s dec_name = %s decimator = %u" + " dec_mux = %u\n", __func__, w->name, dec_name, decimator, + dec_mux); + + + switch (decimator) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + if (dec_mux == 1) + adc_dmic_sel = 0x1; + else + adc_dmic_sel = 0x0; + break; + case 7: + case 8: + case 9: + case 10: + if ((dec_mux == 1) || (dec_mux == 2)) + adc_dmic_sel = 0x1; + else + adc_dmic_sel = 0x0; + break; + default: + pr_err("%s: Invalid Decimator = %u\n", __func__, decimator); + ret = -EINVAL; + goto out; + } + + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + 8 * (decimator - 1); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x1, adc_dmic_sel); + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + +out: + kfree(widget_name); + return ret; +} + +#define WCD9310_DEC_ENUM(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = wcd9310_put_dec_enum, \ + .private_value = (unsigned long)&xenum } + +static const struct snd_kcontrol_new dec1_mux = + WCD9310_DEC_ENUM("DEC1 MUX Mux", dec1_mux_enum); + +static const struct snd_kcontrol_new dec2_mux = + WCD9310_DEC_ENUM("DEC2 MUX Mux", dec2_mux_enum); + +static const struct snd_kcontrol_new dec3_mux = + WCD9310_DEC_ENUM("DEC3 MUX Mux", dec3_mux_enum); + +static const struct snd_kcontrol_new dec4_mux = + WCD9310_DEC_ENUM("DEC4 MUX Mux", dec4_mux_enum); + +static const struct snd_kcontrol_new dec5_mux = + WCD9310_DEC_ENUM("DEC5 MUX Mux", dec5_mux_enum); + +static const struct snd_kcontrol_new dec6_mux = + WCD9310_DEC_ENUM("DEC6 MUX Mux", dec6_mux_enum); + +static const struct snd_kcontrol_new dec7_mux = + WCD9310_DEC_ENUM("DEC7 MUX Mux", dec7_mux_enum); + +static const struct snd_kcontrol_new dec8_mux = + WCD9310_DEC_ENUM("DEC8 MUX Mux", dec8_mux_enum); + +static const struct snd_kcontrol_new dec9_mux = + WCD9310_DEC_ENUM("DEC9 MUX Mux", dec9_mux_enum); + +static const struct snd_kcontrol_new dec10_mux = + WCD9310_DEC_ENUM("DEC10 MUX Mux", dec10_mux_enum); + +static const struct snd_kcontrol_new iir1_inp1_mux = + SOC_DAPM_ENUM("IIR1 INP1 Mux", iir1_inp1_mux_enum); + +static const struct snd_kcontrol_new anc1_mux = + SOC_DAPM_ENUM("ANC1 MUX Mux", anc1_mux_enum); + +static const struct snd_kcontrol_new anc2_mux = + SOC_DAPM_ENUM("ANC2 MUX Mux", anc2_mux_enum); + +static const struct snd_kcontrol_new anc1_fb_mux = + SOC_DAPM_ENUM("ANC1 FB MUX Mux", anc1_fb_mux_enum); + +static const struct snd_kcontrol_new dac1_switch[] = { + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_EAR_EN, 5, 1, 0) +}; +static const struct snd_kcontrol_new hphl_switch[] = { + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_HPH_L_DAC_CTL, 6, 1, 0) +}; + +static const struct snd_kcontrol_new hphl_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 7, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 7, 1, 0), +}; + +static const struct snd_kcontrol_new hphr_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 6, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lineout1_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 5, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 5, 1, 0), +}; + +static const struct snd_kcontrol_new lineout2_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 4, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 4, 1, 0), +}; + +static const struct snd_kcontrol_new lineout3_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 3, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lineout4_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 2, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lineout5_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 1, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 1, 1, 0), +}; + +static const struct snd_kcontrol_new ear_pa_mix[] = { + SOC_DAPM_SINGLE("AUX_PGA_L Switch", TABLA_A_AUX_L_PA_CONN, + 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R Switch", TABLA_A_AUX_R_PA_CONN, + 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_L_INV Switch", + TABLA_A_AUX_L_PA_CONN_INV, 0, 1, 0), + SOC_DAPM_SINGLE("AUX_PGA_R_INV Switch", + TABLA_A_AUX_R_PA_CONN_INV, 0, 1, 0), +}; + +static const struct snd_kcontrol_new lineout3_ground_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_3_DAC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new lineout4_ground_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_4_DAC_CTL, 6, 1, 0); + +static void tabla_codec_enable_adc_block(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s %d\n", __func__, enable); + + if (enable) { + tabla->adc_count++; + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x2, 0x2); + } else { + tabla->adc_count--; + if (!tabla->adc_count) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, + 0x2, 0x0); + } +} + +static int tabla_codec_enable_adc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 adc_reg; + u8 init_bit_shift; + + pr_debug("%s %d\n", __func__, event); + + if (w->reg == TABLA_A_TX_1_2_EN) + adc_reg = TABLA_A_TX_1_2_TEST_CTL; + else if (w->reg == TABLA_A_TX_3_4_EN) + adc_reg = TABLA_A_TX_3_4_TEST_CTL; + else if (w->reg == TABLA_A_TX_5_6_EN) + adc_reg = TABLA_A_TX_5_6_TEST_CTL; + else { + pr_err("%s: Error, invalid adc register\n", __func__); + return -EINVAL; + } + + if (w->shift == 3) + init_bit_shift = 6; + else if (w->shift == 7) + init_bit_shift = 7; + else { + pr_err("%s: Error, invalid init bit postion adc register\n", + __func__); + return -EINVAL; + } + + + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_codec_enable_adc_block(codec, 1); + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, + 1 << init_bit_shift); + break; + case SND_SOC_DAPM_POST_PMU: + + snd_soc_update_bits(codec, adc_reg, 1 << init_bit_shift, 0x00); + + break; + case SND_SOC_DAPM_POST_PMD: + tabla_codec_enable_adc_block(codec, 0); + break; + } + return 0; +} + +static void tabla_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec) +{ + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x04, + 0x04); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x01); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); +} + +static void tabla_codec_enable_bandgap(struct snd_soc_codec *codec, + enum tabla_bandgap_type choice) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + /* TODO lock resources accessed by audio streams and threaded + * interrupt handlers + */ + + pr_debug("%s, choice is %d, current is %d\n", __func__, choice, + tabla->bandgap_type); + + if (tabla->bandgap_type == choice) + return; + + if ((tabla->bandgap_type == TABLA_BANDGAP_OFF) && + (choice == TABLA_BANDGAP_AUDIO_MODE)) { + tabla_codec_enable_audio_mode_bandgap(codec); + } else if (choice == TABLA_BANDGAP_MBHC_MODE) { + /* bandgap mode becomes fast, + * mclk should be off or clk buff source souldn't be VBG + * Let's turn off mclk always */ + WARN_ON(snd_soc_read(codec, TABLA_A_CLK_BUFF_EN2) & (1 << 2)); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x2, + 0x2); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x4, + 0x4); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x01); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); + } else if ((tabla->bandgap_type == TABLA_BANDGAP_MBHC_MODE) && + (choice == TABLA_BANDGAP_AUDIO_MODE)) { + snd_soc_write(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x00); + usleep_range(100, 100); + tabla_codec_enable_audio_mode_bandgap(codec); + } else if (choice == TABLA_BANDGAP_OFF) { + snd_soc_write(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x00); + } else { + pr_err("%s: Error, Invalid bandgap settings\n", __func__); + } + tabla->bandgap_type = choice; +} + +static void tabla_codec_disable_clock_block(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + pr_debug("%s\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x04, 0x00); + usleep_range(50, 50); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x02); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x00); + usleep_range(50, 50); + tabla->clock_active = false; +} + +static int tabla_codec_mclk_index(const struct tabla_priv *tabla) +{ + if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_12288KHZ) + return 0; + else if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_9600KHZ) + return 1; + else { + BUG_ON(1); + return -EINVAL; + } +} + +static void tabla_enable_rx_bias(struct snd_soc_codec *codec, u32 enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (enable) { + tabla->rx_bias_count++; + if (tabla->rx_bias_count == 1) + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, + 0x80, 0x80); + } else { + tabla->rx_bias_count--; + if (!tabla->rx_bias_count) + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, + 0x80, 0x00); + } +} + +static int tabla_codec_enable_config_mode(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x10, 0); + /* bandgap mode to fast */ + snd_soc_write(codec, TABLA_A_BIAS_CONFIG_MODE_BG_CTL, 0x17); + usleep_range(5, 5); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_TEST, 0x80, + 0x80); + usleep_range(10, 10); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_TEST, 0x80, 0); + usleep_range(10000, 10000); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x08, 0x08); + } else { + snd_soc_update_bits(codec, TABLA_A_BIAS_CONFIG_MODE_BG_CTL, 0x1, + 0); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x80, 0); + /* clk source to ext clk and clk buff ref to VBG */ + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x0C, 0x04); + } + tabla->config_mode_active = enable ? true : false; + + return 0; +} + +static int tabla_codec_enable_clock_block(struct snd_soc_codec *codec, + int config_mode) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: config_mode = %d\n", __func__, config_mode); + + /* transit to RCO requires mclk off */ + WARN_ON(snd_soc_read(codec, TABLA_A_CLK_BUFF_EN2) & (1 << 2)); + if (config_mode) { + /* enable RCO and switch to it */ + tabla_codec_enable_config_mode(codec, 1); + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x02); + usleep_range(1000, 1000); + } else { + /* switch to MCLK */ + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x08, 0x00); + + if (tabla->mbhc_polling_active) { + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x02); + tabla_codec_enable_config_mode(codec, 0); + } + } + + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x01, 0x01); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x00); + /* on MCLK */ + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x04, 0x04); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_MCLK_CTL, 0x01, 0x01); + usleep_range(50, 50); + tabla->clock_active = true; + return 0; +} + +static int tabla_codec_enable_aux_pga(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_enable_rx_bias(codec, 1); + + snd_soc_update_bits(codec, TABLA_A_AUX_COM_CTL, + 0x08, 0x08); + /* Enable Zero Cross detect for AUX PGA channel + * and set the initial AUX PGA gain to NEG_0P0_DB + * to avoid glitches. + */ + if (w->reg == TABLA_A_AUX_L_EN) { + snd_soc_update_bits(codec, TABLA_A_AUX_L_EN, + 0x20, 0x20); + tabla->aux_l_gain = snd_soc_read(codec, + TABLA_A_AUX_L_GAIN); + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, 0x1F); + } else { + snd_soc_update_bits(codec, TABLA_A_AUX_R_EN, + 0x20, 0x20); + tabla->aux_r_gain = snd_soc_read(codec, + TABLA_A_AUX_R_GAIN); + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, 0x1F); + } + if (tabla->aux_pga_cnt++ == 1 + && !tabla->mclk_enabled) { + tabla_codec_enable_clock_block(codec, 1); + pr_debug("AUX PGA enabled RC osc\n"); + } + break; + + case SND_SOC_DAPM_POST_PMU: + if (w->reg == TABLA_A_AUX_L_EN) + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, + tabla->aux_l_gain); + else + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, + tabla->aux_r_gain); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Mute AUX PGA channel in use before disabling AUX PGA */ + if (w->reg == TABLA_A_AUX_L_EN) { + tabla->aux_l_gain = snd_soc_read(codec, + TABLA_A_AUX_L_GAIN); + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, 0x1F); + } else { + tabla->aux_r_gain = snd_soc_read(codec, + TABLA_A_AUX_R_GAIN); + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, 0x1F); + } + break; + + case SND_SOC_DAPM_POST_PMD: + tabla_enable_rx_bias(codec, 0); + + snd_soc_update_bits(codec, TABLA_A_AUX_COM_CTL, + 0x08, 0x00); + if (w->reg == TABLA_A_AUX_L_EN) { + snd_soc_write(codec, TABLA_A_AUX_L_GAIN, + tabla->aux_l_gain); + snd_soc_update_bits(codec, TABLA_A_AUX_L_EN, + 0x20, 0x00); + } else { + snd_soc_write(codec, TABLA_A_AUX_R_GAIN, + tabla->aux_r_gain); + snd_soc_update_bits(codec, TABLA_A_AUX_R_EN, + 0x20, 0x00); + } + + if (tabla->aux_pga_cnt-- == 0) { + if (tabla->mbhc_polling_active) + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_MBHC_MODE); + else + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_OFF); + + if (!tabla->mclk_enabled && + !tabla->mbhc_polling_active) { + tabla_codec_enable_clock_block(codec, 0); + } + } + break; + } + return 0; +} + +static int tabla_codec_enable_lineout(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 lineout_gain_reg; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (w->shift) { + case 0: + lineout_gain_reg = TABLA_A_RX_LINE_1_GAIN; + break; + case 1: + lineout_gain_reg = TABLA_A_RX_LINE_2_GAIN; + break; + case 2: + lineout_gain_reg = TABLA_A_RX_LINE_3_GAIN; + break; + case 3: + lineout_gain_reg = TABLA_A_RX_LINE_4_GAIN; + break; + case 4: + lineout_gain_reg = TABLA_A_RX_LINE_5_GAIN; + break; + default: + pr_err("%s: Error, incorrect lineout register value\n", + __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, lineout_gain_reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMU: + pr_debug("%s: sleeping 16 ms after %s PA turn on\n", + __func__, w->name); + usleep_range(16000, 16000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, lineout_gain_reg, 0x40, 0x00); + break; + } + return 0; +} + + +static int tabla_codec_enable_dmic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 dmic_clk_en; + s32 *dmic_clk_cnt; + unsigned int dmic; + int ret; + + ret = kstrtouint(strpbrk(w->name, "123456"), 10, &dmic); + if (ret < 0) { + pr_err("%s: Invalid DMIC line on the codec\n", __func__); + return -EINVAL; + } + + switch (dmic) { + case 1: + case 2: + dmic_clk_en = 0x01; + dmic_clk_cnt = &(tabla->dmic_1_2_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_1_2_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + + break; + + case 3: + case 4: + dmic_clk_en = 0x04; + dmic_clk_cnt = &(tabla->dmic_3_4_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_3_4_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + break; + + case 5: + case 6: + dmic_clk_en = 0x10; + dmic_clk_cnt = &(tabla->dmic_5_6_clk_cnt); + + pr_debug("%s() event %d DMIC%d dmic_5_6_clk_cnt %d\n", + __func__, event, dmic, *dmic_clk_cnt); + + break; + + default: + pr_err("%s: Invalid DMIC Selection\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + (*dmic_clk_cnt)++; + if (*dmic_clk_cnt == 1) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, dmic_clk_en); + + break; + case SND_SOC_DAPM_POST_PMD: + + (*dmic_clk_cnt)--; + if (*dmic_clk_cnt == 0) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, + dmic_clk_en, 0); + break; + } + return 0; +} + +static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + const char *filename; + const struct firmware *fw; + int i; + int ret; + int num_anc_slots; + struct anc_header *anc_head; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u32 anc_writes_size = 0; + int anc_size_remaining; + u32 *anc_ptr; + u16 reg; + u8 mask, val, old_val; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + filename = "wcd9310/wcd9310_anc.bin"; + + ret = request_firmware(&fw, filename, codec->dev); + if (ret != 0) { + dev_err(codec->dev, "Failed to acquire ANC data: %d\n", + ret); + return -ENODEV; + } + + if (fw->size < sizeof(struct anc_header)) { + dev_err(codec->dev, "Not enough data\n"); + release_firmware(fw); + return -ENOMEM; + } + + /* First number is the number of register writes */ + anc_head = (struct anc_header *)(fw->data); + anc_ptr = (u32 *)((u32)fw->data + sizeof(struct anc_header)); + anc_size_remaining = fw->size - sizeof(struct anc_header); + num_anc_slots = anc_head->num_anc_slots; + + if (tabla->anc_slot >= num_anc_slots) { + dev_err(codec->dev, "Invalid ANC slot selected\n"); + release_firmware(fw); + return -EINVAL; + } + + for (i = 0; i < num_anc_slots; i++) { + + if (anc_size_remaining < TABLA_PACKED_REG_SIZE) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -EINVAL; + } + anc_writes_size = (u32)(*anc_ptr); + anc_size_remaining -= sizeof(u32); + anc_ptr += 1; + + if (anc_writes_size * TABLA_PACKED_REG_SIZE + > anc_size_remaining) { + dev_err(codec->dev, "Invalid register format\n"); + release_firmware(fw); + return -ENOMEM; + } + + if (tabla->anc_slot == i) + break; + + anc_size_remaining -= (anc_writes_size * + TABLA_PACKED_REG_SIZE); + anc_ptr += anc_writes_size; + } + if (i == num_anc_slots) { + dev_err(codec->dev, "Selected ANC slot not present\n"); + release_firmware(fw); + return -ENOMEM; + } + + for (i = 0; i < anc_writes_size; i++) { + TABLA_CODEC_UNPACK_ENTRY(anc_ptr[i], reg, + mask, val); + old_val = snd_soc_read(codec, reg); + snd_soc_write(codec, reg, (old_val & ~mask) | + (val & mask)); + } + release_firmware(fw); + + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_write(codec, TABLA_A_CDC_CLK_ANC_RESET_CTL, 0xFF); + snd_soc_write(codec, TABLA_A_CDC_CLK_ANC_CLK_EN_CTL, 0); + break; + } + return 0; +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_start_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + int mbhc_state = tabla->mbhc_state; + + pr_debug("%s: enter\n", __func__); + if (!tabla->mbhc_polling_active) { + pr_debug("Polling is not active, do not start polling\n"); + return; + } + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + + if (!tabla->no_mic_headset_override) { + if (mbhc_state == MBHC_STATE_POTENTIAL) { + pr_debug("%s recovering MBHC state macine\n", __func__); + tabla->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY; + /* set to max button press threshold */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + 0x7F); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL, + (TABLA_IS_1_X(tabla_core->version) ? + 0x07 : 0x7F)); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, + 0xFF); + /* set to max */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B6_CTL, + 0x7F); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B5_CTL, + 0xFF); + } + } + + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1); + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_pause_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + if (!tabla->mbhc_polling_active) { + pr_debug("polling not active, nothing to pause\n"); + return; + } + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + pr_debug("%s: leave\n", __func__); +} + +static void tabla_codec_switch_cfilt_mode(struct snd_soc_codec *codec, int mode) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 reg_mode_val, cur_mode_val; + bool mbhc_was_polling = false; + + if (mode) + reg_mode_val = TABLA_CFILT_FAST_MODE; + else + reg_mode_val = TABLA_CFILT_SLOW_MODE; + + cur_mode_val = snd_soc_read(codec, + tabla->mbhc_bias_regs.cfilt_ctl) & 0x40; + + if (cur_mode_val != reg_mode_val) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + mbhc_was_polling = true; + } + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_ctl, 0x40, reg_mode_val); + if (mbhc_was_polling) + tabla_codec_start_hs_polling(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("%s: CFILT mode change (%x to %x)\n", __func__, + cur_mode_val, reg_mode_val); + } else { + pr_debug("%s: CFILT Value is already %x\n", + __func__, cur_mode_val); + } +} + +static void tabla_codec_update_cfilt_usage(struct snd_soc_codec *codec, + u8 cfilt_sel, int inc) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u32 *cfilt_cnt_ptr = NULL; + u16 micb_cfilt_reg; + + switch (cfilt_sel) { + case TABLA_CFILT1_SEL: + cfilt_cnt_ptr = &tabla->cfilt1_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_1_CTL; + break; + case TABLA_CFILT2_SEL: + cfilt_cnt_ptr = &tabla->cfilt2_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_2_CTL; + break; + case TABLA_CFILT3_SEL: + cfilt_cnt_ptr = &tabla->cfilt3_cnt; + micb_cfilt_reg = TABLA_A_MICB_CFILT_3_CTL; + break; + default: + return; /* should not happen */ + } + + if (inc) { + if (!(*cfilt_cnt_ptr)++) { + /* Switch CFILT to slow mode if MBHC CFILT being used */ + if (cfilt_sel == tabla->mbhc_bias_regs.cfilt_sel) + tabla_codec_switch_cfilt_mode(codec, 0); + + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0x80); + } + } else { + /* check if count not zero, decrement + * then check if zero, go ahead disable cfilter + */ + if ((*cfilt_cnt_ptr) && !--(*cfilt_cnt_ptr)) { + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0); + + /* Switch CFILT to fast mode if MBHC CFILT being used */ + if (cfilt_sel == tabla->mbhc_bias_regs.cfilt_sel) + tabla_codec_switch_cfilt_mode(codec, 1); + } + } +} + +static int tabla_find_k_value(unsigned int ldoh_v, unsigned int cfilt_mv) +{ + int rc = -EINVAL; + unsigned min_mv, max_mv; + + switch (ldoh_v) { + case TABLA_LDOH_1P95_V: + min_mv = 160; + max_mv = 1800; + break; + case TABLA_LDOH_2P35_V: + min_mv = 200; + max_mv = 2200; + break; + case TABLA_LDOH_2P75_V: + min_mv = 240; + max_mv = 2600; + break; + case TABLA_LDOH_2P85_V: + min_mv = 250; + max_mv = 2700; + break; + default: + goto done; + } + + if (cfilt_mv < min_mv || cfilt_mv > max_mv) + goto done; + + for (rc = 4; rc <= 44; rc++) { + min_mv = max_mv * (rc) / 44; + if (min_mv >= cfilt_mv) { + rc -= 4; + break; + } + } +done: + return rc; +} + +static bool tabla_is_hph_pa_on(struct snd_soc_codec *codec) +{ + u8 hph_reg_val = 0; + hph_reg_val = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_EN); + + return (hph_reg_val & 0x30) ? true : false; +} + +static bool tabla_is_hph_dac_on(struct snd_soc_codec *codec, int left) +{ + u8 hph_reg_val = 0; + if (left) + hph_reg_val = snd_soc_read(codec, + TABLA_A_RX_HPH_L_DAC_CTL); + else + hph_reg_val = snd_soc_read(codec, + TABLA_A_RX_HPH_R_DAC_CTL); + + return (hph_reg_val & 0xC0) ? true : false; +} + +static void tabla_turn_onoff_override(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, on << 2); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_drive_v_to_micbias(struct snd_soc_codec *codec, + int usec) +{ + int cfilt_k_val; + bool set = true; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + tabla->mbhc_micbias_switched) { + pr_debug("%s: set mic V to micbias V\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + tabla_turn_onoff_override(codec, true); + while (1) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + set ? tabla->mbhc_data.micb_mv : + VDDIO_MICBIAS_MV); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + if (!set) + break; + usleep_range(usec, usec); + set = false; + } + tabla_turn_onoff_override(codec, false); + } +} + +/* called under codec_resource_lock acquisition */ +static void __tabla_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch, bool restartpolling, + bool checkpolling) +{ + int cfilt_k_val; + bool override; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (vddio_switch && !tabla->mbhc_micbias_switched && + (!checkpolling || tabla->mbhc_polling_active)) { + if (restartpolling) + tabla_codec_pause_hs_polling(codec); + override = snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x04; + if (!override) + tabla_turn_onoff_override(codec, true); + /* Adjust threshold if Mic Bias voltage changes */ + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + VDDIO_MICBIAS_MV); + usleep_range(10000, 10000); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10000); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + tabla->mbhc_data.adj_v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (tabla->mbhc_data.adj_v_ins_hu >> 8) & + 0xFF); + pr_debug("%s: Programmed MBHC thresholds to VDDIO\n", + __func__); + } + + /* enable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x00); + if (!override) + tabla_turn_onoff_override(codec, false); + if (restartpolling) + tabla_codec_start_hs_polling(codec); + + tabla->mbhc_micbias_switched = true; + pr_debug("%s: VDDIO switch enabled\n", __func__); + } else if (!vddio_switch && tabla->mbhc_micbias_switched) { + if ((!checkpolling || tabla->mbhc_polling_active) && + restartpolling) + tabla_codec_pause_hs_polling(codec); + /* Reprogram thresholds */ + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = tabla_find_k_value( + tabla->pdata->micbias.ldoh_v, + tabla->mbhc_data.micb_mv); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10000); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + tabla->mbhc_data.v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (tabla->mbhc_data.v_ins_hu >> 8) & 0xFF); + pr_debug("%s: Programmed MBHC thresholds to MICBIAS\n", + __func__); + } + + /* Disable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x00); + + if ((!checkpolling || tabla->mbhc_polling_active) && + restartpolling) + tabla_codec_start_hs_polling(codec); + + tabla->mbhc_micbias_switched = false; + pr_debug("%s: VDDIO switch disabled\n", __func__); + } +} + +static void tabla_codec_switch_micbias(struct snd_soc_codec *codec, + int vddio_switch) +{ + return __tabla_codec_switch_micbias(codec, vddio_switch, true, true); +} + +static int tabla_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u16 micb_int_reg; + int micb_line; + u8 cfilt_sel_val = 0; + char *internal1_text = "Internal1"; + char *internal2_text = "Internal2"; + char *internal3_text = "Internal3"; + + pr_debug("%s %d\n", __func__, event); + switch (w->reg) { + case TABLA_A_MICB_1_CTL: + micb_int_reg = TABLA_A_MICB_1_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias1_cfilt_sel; + micb_line = TABLA_MICBIAS1; + break; + case TABLA_A_MICB_2_CTL: + micb_int_reg = TABLA_A_MICB_2_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias2_cfilt_sel; + micb_line = TABLA_MICBIAS2; + break; + case TABLA_A_MICB_3_CTL: + micb_int_reg = TABLA_A_MICB_3_INT_RBIAS; + cfilt_sel_val = tabla->pdata->micbias.bias3_cfilt_sel; + micb_line = TABLA_MICBIAS3; + break; + case TABLA_1_A_MICB_4_CTL: + case TABLA_2_A_MICB_4_CTL: + micb_int_reg = tabla->reg_addr.micb_4_int_rbias; + cfilt_sel_val = tabla->pdata->micbias.bias4_cfilt_sel; + micb_line = TABLA_MICBIAS4; + break; + default: + pr_err("%s: Error, invalid micbias register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Decide whether to switch the micbias for MBHC */ + if (w->reg == tabla->mbhc_bias_regs.ctl_reg) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 0); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + + snd_soc_update_bits(codec, w->reg, 0x0E, 0x0A); + tabla_codec_update_cfilt_usage(codec, cfilt_sel_val, 1); + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0xE0, 0xE0); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x1C, 0x1C); + else if (strnstr(w->name, internal3_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x3, 0x3); + + break; + case SND_SOC_DAPM_POST_PMU: + + usleep_range(20000, 20000); + + if (tabla->mbhc_polling_active && + tabla->mbhc_cfg.micbias == micb_line) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_pause_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + break; + + case SND_SOC_DAPM_POST_PMD: + if ((w->reg == tabla->mbhc_bias_regs.ctl_reg) && + tabla_is_hph_pa_on(codec)) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 1); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + + if (strnstr(w->name, internal1_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x80, 0x00); + else if (strnstr(w->name, internal2_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x10, 0x00); + else if (strnstr(w->name, internal3_text, 30)) + snd_soc_update_bits(codec, micb_int_reg, 0x2, 0x0); + + tabla_codec_update_cfilt_usage(codec, cfilt_sel_val, 0); + break; + } + + return 0; +} + + +static void tx_hpf_corner_freq_callback(struct work_struct *work) +{ + struct delayed_work *hpf_delayed_work; + struct hpf_work *hpf_work; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + u16 tx_mux_ctl_reg; + u8 hpf_cut_of_freq; + + hpf_delayed_work = to_delayed_work(work); + hpf_work = container_of(hpf_delayed_work, struct hpf_work, dwork); + tabla = hpf_work->tabla; + codec = hpf_work->tabla->codec; + hpf_cut_of_freq = hpf_work->tx_hpf_cut_of_freq; + + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + + (hpf_work->decimator - 1) * 8; + + pr_debug("%s(): decimator %u hpf_cut_of_freq 0x%x\n", __func__, + hpf_work->decimator, (unsigned int)hpf_cut_of_freq); + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, hpf_cut_of_freq << 4); +} + +#define TX_MUX_CTL_CUT_OFF_FREQ_MASK 0x30 +#define CF_MIN_3DB_4HZ 0x0 +#define CF_MIN_3DB_75HZ 0x1 +#define CF_MIN_3DB_150HZ 0x2 + +static int tabla_codec_enable_dec(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int decimator; + char *dec_name = NULL; + char *widget_name = NULL; + char *temp; + int ret = 0; + u16 dec_reset_reg, tx_vol_ctl_reg, tx_mux_ctl_reg; + u8 dec_hpf_cut_of_freq; + int offset; + + + pr_debug("%s %d\n", __func__, event); + + widget_name = kstrndup(w->name, 15, GFP_KERNEL); + if (!widget_name) + return -ENOMEM; + temp = widget_name; + + dec_name = strsep(&widget_name, " "); + widget_name = temp; + if (!dec_name) { + pr_err("%s: Invalid decimator = %s\n", __func__, w->name); + ret = -EINVAL; + goto out; + } + + ret = kstrtouint(strpbrk(dec_name, "123456789"), 10, &decimator); + if (ret < 0) { + pr_err("%s: Invalid decimator = %s\n", __func__, dec_name); + ret = -EINVAL; + goto out; + } + + pr_debug("%s(): widget = %s dec_name = %s decimator = %u\n", __func__, + w->name, dec_name, decimator); + + if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL) { + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B1_CTL; + offset = 0; + } else if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL) { + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B2_CTL; + offset = 8; + } else { + pr_err("%s: Error, incorrect dec\n", __func__); + return -EINVAL; + } + + tx_vol_ctl_reg = TABLA_A_CDC_TX1_VOL_CTL_CFG + 8 * (decimator -1); + tx_mux_ctl_reg = TABLA_A_CDC_TX1_MUX_CTL + 8 * (decimator - 1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + // Enableable TX digital mute */ + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x01); + + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, + 1 << w->shift); + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, 0x0); + + dec_hpf_cut_of_freq = snd_soc_read(codec, tx_mux_ctl_reg); + + dec_hpf_cut_of_freq = (dec_hpf_cut_of_freq & 0x30) >> 4; + + tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq = + dec_hpf_cut_of_freq; + + if ((dec_hpf_cut_of_freq != CF_MIN_3DB_150HZ)) { + + /* set cut of freq to CF_MIN_3DB_150HZ (0x1); */ + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, + CF_MIN_3DB_150HZ << 4); + } + + /* enable HPF */ + snd_soc_update_bits(codec, tx_mux_ctl_reg , 0x08, 0x00); + + break; + + case SND_SOC_DAPM_POST_PMU: + + /* Disable TX digital mute */ + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x00); + + if (tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq != + CF_MIN_3DB_150HZ) { + + schedule_delayed_work(&tx_hpf_work[decimator - 1].dwork, + msecs_to_jiffies(300)); + } + /* apply the digital gain after the decimator is enabled*/ + if ((w->shift) < ARRAY_SIZE(rx_digital_gain_reg)) + snd_soc_write(codec, + tx_digital_gain_reg[w->shift + offset], + snd_soc_read(codec, + tx_digital_gain_reg[w->shift + offset]) + ); + + break; + + case SND_SOC_DAPM_PRE_PMD: + + snd_soc_update_bits(codec, tx_vol_ctl_reg, 0x01, 0x01); + cancel_delayed_work_sync(&tx_hpf_work[decimator - 1].dwork); + break; + + case SND_SOC_DAPM_POST_PMD: + + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x08, 0x08); + snd_soc_update_bits(codec, tx_mux_ctl_reg, 0x30, + (tx_hpf_work[decimator - 1].tx_hpf_cut_of_freq) << 4); + + break; + } +out: + kfree(widget_name); + return ret; +} + +static int tabla_codec_reset_interpolator(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d %s\n", __func__, event, w->name); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 1 << w->shift); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 0x0); + break; + case SND_SOC_DAPM_POST_PMU: + /* apply the digital gain after the interpolator is enabled*/ + if ((w->shift) < ARRAY_SIZE(rx_digital_gain_reg)) + snd_soc_write(codec, + rx_digital_gain_reg[w->shift], + snd_soc_read(codec, + rx_digital_gain_reg[w->shift]) + ); + break; + } + return 0; +} + +static int tabla_codec_enable_ldo_h(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + usleep_range(1000, 1000); + break; + } + return 0; +} + +static int tabla_codec_enable_rx_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_enable_rx_bias(codec, 1); + break; + case SND_SOC_DAPM_POST_PMD: + tabla_enable_rx_bias(codec, 0); + break; + } + return 0; +} +static int tabla_hphr_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static void tabla_snd_soc_jack_report(struct tabla_priv *tabla, + struct snd_soc_jack *jack, int status, + int mask) +{ + /* XXX: wake_lock_timeout()? */ + snd_soc_jack_report_no_dapm(jack, status, mask); +} + +static void hphocp_off_report(struct tabla_priv *tabla, + u32 jack_status, int irq) +{ + struct snd_soc_codec *codec; + if (!tabla) { + pr_err("%s: Bad tabla private data\n", __func__); + return; + } + + pr_debug("%s: clear ocp status %x\n", __func__, jack_status); + codec = tabla->codec; + if (tabla->hph_status & jack_status) { + tabla->hph_status &= ~jack_status; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x10); + /* reset retry counter as PA is turned off signifying + * start of new OCP detection session + */ + if (TABLA_IRQ_HPH_PA_OCPL_FAULT) + tabla->hphlocp_cnt = 0; + else + tabla->hphrocp_cnt = 0; + wcd9xxx_enable_irq(codec->control_data, irq); + } +} + +static void hphlocp_off_report(struct work_struct *work) +{ + struct tabla_priv *tabla = container_of(work, struct tabla_priv, + hphlocp_work); + hphocp_off_report(tabla, SND_JACK_OC_HPHL, TABLA_IRQ_HPH_PA_OCPL_FAULT); +} + +static void hphrocp_off_report(struct work_struct *work) +{ + struct tabla_priv *tabla = container_of(work, struct tabla_priv, + hphrocp_work); + hphocp_off_report(tabla, SND_JACK_OC_HPHR, TABLA_IRQ_HPH_PA_OCPR_FAULT); +} + +static int tabla_hph_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 mbhc_micb_ctl_val; + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mbhc_micb_ctl_val = snd_soc_read(codec, + tabla->mbhc_bias_regs.ctl_reg); + + if (!(mbhc_micb_ctl_val & 0x80)) { + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 1); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + } + break; + + case SND_SOC_DAPM_POST_PMD: + /* schedule work is required because at the time HPH PA DAPM + * event callback is called by DAPM framework, CODEC dapm mutex + * would have been locked while snd_soc_jack_report also + * attempts to acquire same lock. + */ + if (w->shift == 5) { + clear_bit(TABLA_HPHL_PA_OFF_ACK, + &tabla->hph_pa_dac_state); + clear_bit(TABLA_HPHL_DAC_OFF_ACK, + &tabla->hph_pa_dac_state); + if (tabla->hph_status & SND_JACK_OC_HPHL) + schedule_work(&tabla->hphlocp_work); + } else if (w->shift == 4) { + clear_bit(TABLA_HPHR_PA_OFF_ACK, + &tabla->hph_pa_dac_state); + clear_bit(TABLA_HPHR_DAC_OFF_ACK, + &tabla->hph_pa_dac_state); + if (tabla->hph_status & SND_JACK_OC_HPHR) + schedule_work(&tabla->hphrocp_work); + } + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_switch_micbias(codec, 0); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + + pr_debug("%s: sleep 10 ms after %s PA disable.\n", __func__, + w->name); + usleep_range(10000, 10000); + break; + } + return 0; +} + +static void tabla_get_mbhc_micbias_regs(struct snd_soc_codec *codec, + struct mbhc_micbias_regs *micbias_regs) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + unsigned int cfilt; + + switch (tabla->mbhc_cfg.micbias) { + case TABLA_MICBIAS1: + cfilt = tabla->pdata->micbias.bias1_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_1_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_1_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_1_CTL; + break; + case TABLA_MICBIAS2: + cfilt = tabla->pdata->micbias.bias2_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_2_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_2_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_2_CTL; + break; + case TABLA_MICBIAS3: + cfilt = tabla->pdata->micbias.bias3_cfilt_sel; + micbias_regs->mbhc_reg = TABLA_A_MICB_3_MBHC; + micbias_regs->int_rbias = TABLA_A_MICB_3_INT_RBIAS; + micbias_regs->ctl_reg = TABLA_A_MICB_3_CTL; + break; + case TABLA_MICBIAS4: + cfilt = tabla->pdata->micbias.bias4_cfilt_sel; + micbias_regs->mbhc_reg = tabla->reg_addr.micb_4_mbhc; + micbias_regs->int_rbias = tabla->reg_addr.micb_4_int_rbias; + micbias_regs->ctl_reg = tabla->reg_addr.micb_4_ctl; + break; + default: + /* Should never reach here */ + pr_err("%s: Invalid MIC BIAS for MBHC\n", __func__); + return; + } + + micbias_regs->cfilt_sel = cfilt; + + switch (cfilt) { + case TABLA_CFILT1_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_1_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_1_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt1_mv; + break; + case TABLA_CFILT2_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_2_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_2_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt2_mv; + break; + case TABLA_CFILT3_SEL: + micbias_regs->cfilt_val = TABLA_A_MICB_CFILT_3_VAL; + micbias_regs->cfilt_ctl = TABLA_A_MICB_CFILT_3_CTL; + tabla->mbhc_data.micb_mv = tabla->pdata->micbias.cfilt3_mv; + break; + } +} +static const struct snd_soc_dapm_widget tabla_dapm_i2s_widgets[] = { + SND_SOC_DAPM_SUPPLY("RX_I2S_CLK", TABLA_A_CDC_CLK_RX_I2S_CTL, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TX_I2S_CLK", TABLA_A_CDC_CLK_TX_I2S_CTL, 4, + 0, NULL, 0), +}; + +static int tabla_lineout_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, w->reg, 0x40, 0x40); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, w->reg, 0x40, 0x00); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget tabla_1_x_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS_E("MIC BIAS4 External", TABLA_1_A_MICB_4_CTL, 7, + 0, tabla_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_widget tabla_2_higher_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS_E("MIC BIAS4 External", TABLA_2_A_MICB_4_CTL, 7, + 0, tabla_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route audio_i2s_map[] = { + {"RX_I2S_CLK", NULL, "CDC_CONN"}, + {"SLIM RX1", NULL, "RX_I2S_CLK"}, + {"SLIM RX2", NULL, "RX_I2S_CLK"}, + {"SLIM RX3", NULL, "RX_I2S_CLK"}, + {"SLIM RX4", NULL, "RX_I2S_CLK"}, + + {"SLIM TX7", NULL, "TX_I2S_CLK"}, + {"SLIM TX8", NULL, "TX_I2S_CLK"}, + {"SLIM TX9", NULL, "TX_I2S_CLK"}, + {"SLIM TX10", NULL, "TX_I2S_CLK"}, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* SLIMBUS Connections */ + + {"SLIM TX1", NULL, "SLIM TX1 MUX"}, + {"SLIM TX1 MUX", "DEC1", "DEC1 MUX"}, + + {"SLIM TX2", NULL, "SLIM TX2 MUX"}, + {"SLIM TX2 MUX", "DEC2", "DEC2 MUX"}, + + {"SLIM TX3", NULL, "SLIM TX3 MUX"}, + {"SLIM TX3 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX3 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX3 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX3 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX3 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX3 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX3 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX3 MUX", "RMIX7", "RX7 MIX1"}, + + {"SLIM TX4", NULL, "SLIM TX4 MUX"}, + {"SLIM TX4 MUX", "DEC4", "DEC4 MUX"}, + + {"SLIM TX5", NULL, "SLIM TX5 MUX"}, + {"SLIM TX5 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX5 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX5 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX5 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX5 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX5 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX5 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX5 MUX", "RMIX7", "RX7 MIX1"}, + + {"SLIM TX6", NULL, "SLIM TX6 MUX"}, + {"SLIM TX6 MUX", "DEC6", "DEC6 MUX"}, + + {"SLIM TX7", NULL, "SLIM TX7 MUX"}, + {"SLIM TX7 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX7 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX7 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX7 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX7 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX7 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX7 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX7 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX7 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX7 MUX", "DEC10", "DEC10 MUX"}, + {"SLIM TX7 MUX", "RMIX1", "RX1 MIX1"}, + {"SLIM TX7 MUX", "RMIX2", "RX2 MIX1"}, + {"SLIM TX7 MUX", "RMIX3", "RX3 MIX1"}, + {"SLIM TX7 MUX", "RMIX4", "RX4 MIX1"}, + {"SLIM TX7 MUX", "RMIX5", "RX5 MIX1"}, + {"SLIM TX7 MUX", "RMIX6", "RX6 MIX1"}, + {"SLIM TX7 MUX", "RMIX7", "RX7 MIX1"}, + + {"SLIM TX8", NULL, "SLIM TX8 MUX"}, + {"SLIM TX8 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX8 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX8 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX8 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX8 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX8 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX8 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX8 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX8 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX8 MUX", "DEC10", "DEC10 MUX"}, + + {"SLIM TX9", NULL, "SLIM TX9 MUX"}, + {"SLIM TX9 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX9 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX9 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX9 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX9 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX9 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX9 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX9 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX9 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX9 MUX", "DEC10", "DEC10 MUX"}, + + {"SLIM TX10", NULL, "SLIM TX10 MUX"}, + {"SLIM TX10 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX10 MUX", "DEC2", "DEC2 MUX"}, + {"SLIM TX10 MUX", "DEC3", "DEC3 MUX"}, + {"SLIM TX10 MUX", "DEC4", "DEC4 MUX"}, + {"SLIM TX10 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX10 MUX", "DEC6", "DEC6 MUX"}, + {"SLIM TX10 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX10 MUX", "DEC8", "DEC8 MUX"}, + {"SLIM TX10 MUX", "DEC9", "DEC9 MUX"}, + {"SLIM TX10 MUX", "DEC10", "DEC10 MUX"}, + + /* Earpiece (RX MIX1) */ + {"EAR", NULL, "EAR PA"}, + {"EAR PA", NULL, "EAR_PA_MIXER"}, + {"EAR_PA_MIXER", NULL, "DAC1"}, + {"DAC1", NULL, "CP"}, + + {"ANC1 FB MUX", "EAR_HPH_L", "RX1 MIX2"}, + {"ANC1 FB MUX", "EAR_LINE_1", "RX2 MIX2"}, + {"ANC", NULL, "ANC1 FB MUX"}, + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL"}, + {"HEADPHONE", NULL, "HPHR"}, + + {"HPHL", NULL, "HPHL_PA_MIXER"}, + {"HPHL_PA_MIXER", NULL, "HPHL DAC"}, + + {"HPHR", NULL, "HPHR_PA_MIXER"}, + {"HPHR_PA_MIXER", NULL, "HPHR DAC"}, + + {"HPHL DAC", NULL, "CP"}, + {"HPHR DAC", NULL, "CP"}, + + {"ANC", NULL, "ANC1 MUX"}, + {"ANC", NULL, "ANC2 MUX"}, + {"ANC1 MUX", "ADC1", "ADC1"}, + {"ANC1 MUX", "ADC2", "ADC2"}, + {"ANC1 MUX", "ADC3", "ADC3"}, + {"ANC1 MUX", "ADC4", "ADC4"}, + {"ANC2 MUX", "ADC1", "ADC1"}, + {"ANC2 MUX", "ADC2", "ADC2"}, + {"ANC2 MUX", "ADC3", "ADC3"}, + {"ANC2 MUX", "ADC4", "ADC4"}, + + {"ANC", NULL, "CDC_CONN"}, + + {"DAC1", "Switch", "RX1 CHAIN"}, + {"HPHL DAC", "Switch", "RX1 CHAIN"}, + {"HPHR DAC", NULL, "RX2 CHAIN"}, + + {"LINEOUT1", NULL, "LINEOUT1 PA"}, + {"LINEOUT2", NULL, "LINEOUT2 PA"}, + {"LINEOUT3", NULL, "LINEOUT3 PA"}, + {"LINEOUT4", NULL, "LINEOUT4 PA"}, + {"LINEOUT5", NULL, "LINEOUT5 PA"}, + + {"LINEOUT1 PA", NULL, "LINEOUT1_PA_MIXER"}, + {"LINEOUT1_PA_MIXER", NULL, "LINEOUT1 DAC"}, + {"LINEOUT2 PA", NULL, "LINEOUT2_PA_MIXER"}, + {"LINEOUT2_PA_MIXER", NULL, "LINEOUT2 DAC"}, + {"LINEOUT3 PA", NULL, "LINEOUT3_PA_MIXER"}, + {"LINEOUT3_PA_MIXER", NULL, "LINEOUT3 DAC"}, + {"LINEOUT4 PA", NULL, "LINEOUT4_PA_MIXER"}, + {"LINEOUT4_PA_MIXER", NULL, "LINEOUT4 DAC"}, + {"LINEOUT5 PA", NULL, "LINEOUT5_PA_MIXER"}, + {"LINEOUT5_PA_MIXER", NULL, "LINEOUT5 DAC"}, + + {"LINEOUT1 DAC", NULL, "RX3 MIX2"}, + {"LINEOUT5 DAC", NULL, "RX7 MIX1"}, + + {"RX1 CHAIN", NULL, "RX1 MIX2"}, + {"RX2 CHAIN", NULL, "RX2 MIX2"}, + {"RX1 CHAIN", NULL, "ANC"}, + {"RX2 CHAIN", NULL, "ANC"}, + + {"CP", NULL, "RX_BIAS"}, + {"LINEOUT1 DAC", NULL, "RX_BIAS"}, + {"LINEOUT2 DAC", NULL, "RX_BIAS"}, + {"LINEOUT3 DAC", NULL, "RX_BIAS"}, + {"LINEOUT4 DAC", NULL, "RX_BIAS"}, + {"LINEOUT5 DAC", NULL, "RX_BIAS"}, + + {"RX1 MIX1", NULL, "COMP1_CLK"}, + {"RX2 MIX1", NULL, "COMP1_CLK"}, + {"RX3 MIX1", NULL, "COMP2_CLK"}, + {"RX5 MIX1", NULL, "COMP2_CLK"}, + + + {"RX1 MIX1", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP2"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP3"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP2"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP1"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP2"}, + {"RX4 MIX1", NULL, "RX4 MIX1 INP1"}, + {"RX4 MIX1", NULL, "RX4 MIX1 INP2"}, + {"RX5 MIX1", NULL, "RX5 MIX1 INP1"}, + {"RX5 MIX1", NULL, "RX5 MIX1 INP2"}, + {"RX6 MIX1", NULL, "RX6 MIX1 INP1"}, + {"RX6 MIX1", NULL, "RX6 MIX1 INP2"}, + {"RX7 MIX1", NULL, "RX7 MIX1 INP1"}, + {"RX7 MIX1", NULL, "RX7 MIX1 INP2"}, + {"RX1 MIX2", NULL, "RX1 MIX1"}, + {"RX1 MIX2", NULL, "RX1 MIX2 INP1"}, + {"RX1 MIX2", NULL, "RX1 MIX2 INP2"}, + {"RX2 MIX2", NULL, "RX2 MIX1"}, + {"RX2 MIX2", NULL, "RX2 MIX2 INP1"}, + {"RX2 MIX2", NULL, "RX2 MIX2 INP2"}, + {"RX3 MIX2", NULL, "RX3 MIX1"}, + {"RX3 MIX2", NULL, "RX3 MIX2 INP1"}, + {"RX3 MIX2", NULL, "RX3 MIX2 INP2"}, + + {"RX1 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX1 MIX1 INP1", "IIR1", "IIR1"}, + {"RX1 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX1 MIX1 INP2", "IIR1", "IIR1"}, + {"RX1 MIX1 INP3", "RX1", "SLIM RX1"}, + {"RX1 MIX1 INP3", "RX2", "SLIM RX2"}, + {"RX1 MIX1 INP3", "RX3", "SLIM RX3"}, + {"RX1 MIX1 INP3", "RX4", "SLIM RX4"}, + {"RX1 MIX1 INP3", "RX5", "SLIM RX5"}, + {"RX1 MIX1 INP3", "RX6", "SLIM RX6"}, + {"RX1 MIX1 INP3", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX2 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX2 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP1", "IIR1", "IIR1"}, + {"RX2 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX2 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX2 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX2 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX2 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX2 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX2 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX2 MIX1 INP2", "IIR1", "IIR1"}, + {"RX3 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX3 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX3 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX3 MIX1 INP1", "IIR1", "IIR1"}, + {"RX3 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX3 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX3 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX3 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX3 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX3 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX3 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX3 MIX1 INP2", "IIR1", "IIR1"}, + {"RX4 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX4 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX4 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX4 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX4 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX4 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX4 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX4 MIX1 INP1", "IIR1", "IIR1"}, + {"RX4 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX4 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX4 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX4 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX4 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX4 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX4 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX4 MIX1 INP2", "IIR1", "IIR1"}, + {"RX5 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX5 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX5 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX5 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX5 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX5 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX5 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX5 MIX1 INP1", "IIR1", "IIR1"}, + {"RX5 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX5 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX5 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX5 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX5 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX5 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX5 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX5 MIX1 INP2", "IIR1", "IIR1"}, + {"RX6 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX6 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX6 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX6 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX6 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX6 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX6 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX6 MIX1 INP1", "IIR1", "IIR1"}, + {"RX6 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX6 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX6 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX6 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX6 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX6 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX6 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX6 MIX1 INP2", "IIR1", "IIR1"}, + {"RX7 MIX1 INP1", "RX1", "SLIM RX1"}, + {"RX7 MIX1 INP1", "RX2", "SLIM RX2"}, + {"RX7 MIX1 INP1", "RX3", "SLIM RX3"}, + {"RX7 MIX1 INP1", "RX4", "SLIM RX4"}, + {"RX7 MIX1 INP1", "RX5", "SLIM RX5"}, + {"RX7 MIX1 INP1", "RX6", "SLIM RX6"}, + {"RX7 MIX1 INP1", "RX7", "SLIM RX7"}, + {"RX7 MIX1 INP1", "IIR1", "IIR1"}, + {"RX7 MIX1 INP2", "RX1", "SLIM RX1"}, + {"RX7 MIX1 INP2", "RX2", "SLIM RX2"}, + {"RX7 MIX1 INP2", "RX3", "SLIM RX3"}, + {"RX7 MIX1 INP2", "RX4", "SLIM RX4"}, + {"RX7 MIX1 INP2", "RX5", "SLIM RX5"}, + {"RX7 MIX1 INP2", "RX6", "SLIM RX6"}, + {"RX7 MIX1 INP2", "RX7", "SLIM RX7"}, + {"RX7 MIX1 INP2", "IIR1", "IIR1"}, + {"RX1 MIX2 INP1", "IIR1", "IIR1"}, + {"RX1 MIX2 INP2", "IIR1", "IIR1"}, + {"RX2 MIX2 INP1", "IIR1", "IIR1"}, + {"RX2 MIX2 INP2", "IIR1", "IIR1"}, + {"RX3 MIX2 INP1", "IIR1", "IIR1"}, + {"RX3 MIX2 INP2", "IIR1", "IIR1"}, + + /* Decimator Inputs */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC1 MUX", "ADC6", "ADC6"}, + {"DEC1 MUX", NULL, "CDC_CONN"}, + {"DEC2 MUX", "DMIC2", "DMIC2"}, + {"DEC2 MUX", "ADC5", "ADC5"}, + {"DEC2 MUX", NULL, "CDC_CONN"}, + {"DEC3 MUX", "DMIC3", "DMIC3"}, + {"DEC3 MUX", "ADC4", "ADC4"}, + {"DEC3 MUX", NULL, "CDC_CONN"}, + {"DEC4 MUX", "DMIC4", "DMIC4"}, + {"DEC4 MUX", "ADC3", "ADC3"}, + {"DEC4 MUX", NULL, "CDC_CONN"}, + {"DEC5 MUX", "DMIC5", "DMIC5"}, + {"DEC5 MUX", "ADC2", "ADC2"}, + {"DEC5 MUX", NULL, "CDC_CONN"}, + {"DEC6 MUX", "DMIC6", "DMIC6"}, + {"DEC6 MUX", "ADC1", "ADC1"}, + {"DEC6 MUX", NULL, "CDC_CONN"}, + {"DEC7 MUX", "DMIC1", "DMIC1"}, + {"DEC7 MUX", "DMIC6", "DMIC6"}, + {"DEC7 MUX", "ADC1", "ADC1"}, + {"DEC7 MUX", "ADC6", "ADC6"}, + {"DEC7 MUX", NULL, "CDC_CONN"}, + {"DEC8 MUX", "DMIC2", "DMIC2"}, + {"DEC8 MUX", "DMIC5", "DMIC5"}, + {"DEC8 MUX", "ADC2", "ADC2"}, + {"DEC8 MUX", "ADC5", "ADC5"}, + {"DEC8 MUX", NULL, "CDC_CONN"}, + {"DEC9 MUX", "DMIC4", "DMIC4"}, + {"DEC9 MUX", "DMIC5", "DMIC5"}, + {"DEC9 MUX", "ADC2", "ADC2"}, + {"DEC9 MUX", "ADC3", "ADC3"}, + {"DEC9 MUX", NULL, "CDC_CONN"}, + {"DEC10 MUX", "DMIC3", "DMIC3"}, + {"DEC10 MUX", "DMIC6", "DMIC6"}, + {"DEC10 MUX", "ADC1", "ADC1"}, + {"DEC10 MUX", "ADC4", "ADC4"}, + {"DEC10 MUX", NULL, "CDC_CONN"}, + + /* ADC Connections */ + {"ADC1", NULL, "AMIC1"}, + {"ADC2", NULL, "AMIC2"}, + {"ADC3", NULL, "AMIC3"}, + {"ADC4", NULL, "AMIC4"}, + {"ADC5", NULL, "AMIC5"}, + {"ADC6", NULL, "AMIC6"}, + + /* AUX PGA Connections */ + {"HPHL_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"HPHL_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"HPHL_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"HPHL_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"HPHR_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"HPHR_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"HPHR_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"HPHR_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT1_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT2_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT3_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT4_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"LINEOUT5_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"EAR_PA_MIXER", "AUX_PGA_L Switch", "AUX_PGA_Left"}, + {"EAR_PA_MIXER", "AUX_PGA_R Switch", "AUX_PGA_Right"}, + {"EAR_PA_MIXER", "AUX_PGA_L_INV Switch", "AUX_PGA_Left"}, + {"EAR_PA_MIXER", "AUX_PGA_R_INV Switch", "AUX_PGA_Right"}, + {"AUX_PGA_Left", NULL, "AMIC5"}, + {"AUX_PGA_Right", NULL, "AMIC6"}, + + {"IIR1", NULL, "IIR1 INP1 MUX"}, + {"IIR1 INP1 MUX", "DEC1", "DEC1 MUX"}, + {"IIR1 INP1 MUX", "DEC2", "DEC2 MUX"}, + {"IIR1 INP1 MUX", "DEC3", "DEC3 MUX"}, + {"IIR1 INP1 MUX", "DEC4", "DEC4 MUX"}, + {"IIR1 INP1 MUX", "DEC5", "DEC5 MUX"}, + {"IIR1 INP1 MUX", "DEC6", "DEC6 MUX"}, + {"IIR1 INP1 MUX", "DEC7", "DEC7 MUX"}, + {"IIR1 INP1 MUX", "DEC8", "DEC8 MUX"}, + {"IIR1 INP1 MUX", "DEC9", "DEC9 MUX"}, + {"IIR1 INP1 MUX", "DEC10", "DEC10 MUX"}, + + {"MIC BIAS1 Internal1", NULL, "LDO_H"}, + {"MIC BIAS1 Internal2", NULL, "LDO_H"}, + {"MIC BIAS1 External", NULL, "LDO_H"}, + {"MIC BIAS2 Internal1", NULL, "LDO_H"}, + {"MIC BIAS2 Internal2", NULL, "LDO_H"}, + {"MIC BIAS2 Internal3", NULL, "LDO_H"}, + {"MIC BIAS2 External", NULL, "LDO_H"}, + {"MIC BIAS3 Internal1", NULL, "LDO_H"}, + {"MIC BIAS3 Internal2", NULL, "LDO_H"}, + {"MIC BIAS3 External", NULL, "LDO_H"}, + {"MIC BIAS4 External", NULL, "LDO_H"}, +}; + +static const struct snd_soc_dapm_route tabla_1_x_lineout_2_to_4_map[] = { + + {"RX4 DSM MUX", "DSM_INV", "RX3 MIX2"}, + {"RX4 DSM MUX", "CIC_OUT", "RX4 MIX1"}, + + {"LINEOUT2 DAC", NULL, "RX4 DSM MUX"}, + + {"LINEOUT3 DAC", NULL, "RX5 MIX1"}, + {"LINEOUT3 DAC GROUND", "Switch", "RX3 MIX2"}, + {"LINEOUT3 DAC", NULL, "LINEOUT3 DAC GROUND"}, + + {"RX6 DSM MUX", "DSM_INV", "RX5 MIX1"}, + {"RX6 DSM MUX", "CIC_OUT", "RX6 MIX1"}, + + {"LINEOUT4 DAC", NULL, "RX6 DSM MUX"}, + {"LINEOUT4 DAC GROUND", "Switch", "RX4 DSM MUX"}, + {"LINEOUT4 DAC", NULL, "LINEOUT4 DAC GROUND"}, +}; + + +static const struct snd_soc_dapm_route tabla_2_x_lineout_2_to_4_map[] = { + + {"RX4 DSM MUX", "DSM_INV", "RX3 MIX2"}, + {"RX4 DSM MUX", "CIC_OUT", "RX4 MIX1"}, + + {"LINEOUT3 DAC", NULL, "RX4 DSM MUX"}, + + {"LINEOUT2 DAC", NULL, "RX5 MIX1"}, + + {"RX6 DSM MUX", "DSM_INV", "RX5 MIX1"}, + {"RX6 DSM MUX", "CIC_OUT", "RX6 MIX1"}, + + {"LINEOUT4 DAC", NULL, "RX6 DSM MUX"}, +}; + +static int tabla_readable(struct snd_soc_codec *ssc, unsigned int reg) +{ + int i; + struct wcd9xxx *tabla_core = dev_get_drvdata(ssc->dev->parent); + + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_reg_readable); i++) { + if (tabla_1_reg_readable[i] == reg) + return 1; + } + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_reg_readable); i++) { + if (tabla_2_reg_readable[i] == reg) + return 1; + } + } + + return tabla_reg_readable[reg]; +} +static bool tabla_is_digital_gain_register(unsigned int reg) +{ + bool rtn = false; + switch (reg) { + case TABLA_A_CDC_RX1_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX2_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX3_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX4_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX5_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX6_VOL_CTL_B2_CTL: + case TABLA_A_CDC_RX7_VOL_CTL_B2_CTL: + case TABLA_A_CDC_TX1_VOL_CTL_GAIN: + case TABLA_A_CDC_TX2_VOL_CTL_GAIN: + case TABLA_A_CDC_TX3_VOL_CTL_GAIN: + case TABLA_A_CDC_TX4_VOL_CTL_GAIN: + case TABLA_A_CDC_TX5_VOL_CTL_GAIN: + case TABLA_A_CDC_TX6_VOL_CTL_GAIN: + case TABLA_A_CDC_TX7_VOL_CTL_GAIN: + case TABLA_A_CDC_TX8_VOL_CTL_GAIN: + case TABLA_A_CDC_TX9_VOL_CTL_GAIN: + case TABLA_A_CDC_TX10_VOL_CTL_GAIN: + rtn = true; + break; + default: + break; + } + return rtn; +} +static int tabla_volatile(struct snd_soc_codec *ssc, unsigned int reg) +{ + /* Registers lower than 0x100 are top level registers which can be + * written by the Tabla core driver. + */ + + if ((reg >= TABLA_A_CDC_MBHC_EN_CTL) || (reg < 0x100)) + return 1; + + /* IIR Coeff registers are not cacheable */ + if ((reg >= TABLA_A_CDC_IIR1_COEF_B1_CTL) && + (reg <= TABLA_A_CDC_IIR2_COEF_B5_CTL)) + return 1; + + /* Digital gain register is not cacheable so we have to write + * the setting even it is the same + */ + if (tabla_is_digital_gain_register(reg)) + return 1; + + /* HPH status registers */ + if (reg == TABLA_A_RX_HPH_L_STATUS || reg == TABLA_A_RX_HPH_R_STATUS) + return 1; + + return 0; +} + +#define TABLA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +static int tabla_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + BUG_ON(reg > TABLA_MAX_REGISTER); + + if (!tabla_volatile(codec, reg)) { + ret = snd_soc_cache_write(codec, reg, value); + if (ret != 0) + dev_err(codec->dev, "Cache write to %x failed: %d\n", + reg, ret); + } + + return wcd9xxx_reg_write(codec->control_data, reg, value); +} +static unsigned int tabla_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + BUG_ON(reg > TABLA_MAX_REGISTER); + + if (!tabla_volatile(codec, reg) && tabla_readable(codec, reg) && + reg < codec->driver->reg_cache_size) { + ret = snd_soc_cache_read(codec, reg, &val); + if (ret >= 0) { + return val; + } else + dev_err(codec->dev, "Cache read from %x failed: %d\n", + reg, ret); + } + + val = wcd9xxx_reg_read(codec->control_data, reg); + return val; +} + +static s16 tabla_get_current_v_ins(struct tabla_priv *tabla, bool hu) +{ + s16 v_ins; + if ((tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + tabla->mbhc_micbias_switched) + v_ins = hu ? (s16)tabla->mbhc_data.adj_v_ins_hu : + (s16)tabla->mbhc_data.adj_v_ins_h; + else + v_ins = hu ? (s16)tabla->mbhc_data.v_ins_hu : + (s16)tabla->mbhc_data.v_ins_h; + return v_ins; +} + +static s16 tabla_get_current_v_hs_max(struct tabla_priv *tabla) +{ + s16 v_hs_max; + struct tabla_mbhc_plug_type_cfg *plug_type; + + plug_type = TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + if ((tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + tabla->mbhc_micbias_switched) + v_hs_max = tabla->mbhc_data.adj_v_hs_max; + else + v_hs_max = plug_type->v_hs_max; + return v_hs_max; +} + +static void tabla_codec_calibrate_hs_polling(struct snd_soc_codec *codec) +{ + u8 *n_ready, *n_cic; + struct tabla_mbhc_btn_detect_cfg *btn_det; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const s16 v_ins_hu = tabla_get_current_v_ins(tabla, true); + + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, + v_ins_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, + (v_ins_hu >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, + tabla->mbhc_data.v_b1_hu & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL, + (tabla->mbhc_data.v_b1_hu >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B5_CTL, + tabla->mbhc_data.v_b1_h & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B6_CTL, + (tabla->mbhc_data.v_b1_h >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B9_CTL, + tabla->mbhc_data.v_brh & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B10_CTL, + (tabla->mbhc_data.v_brh >> 8) & 0xFF); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B11_CTL, + tabla->mbhc_data.v_brl & 0xFF); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B12_CTL, + (tabla->mbhc_data.v_brl >> 8) & 0xFF); + + n_ready = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_READY); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B1_CTL, + n_ready[tabla_codec_mclk_index(tabla)]); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B2_CTL, + tabla->mbhc_data.npoll); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B3_CTL, + tabla->mbhc_data.nbounce_wait); + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL, + n_cic[tabla_codec_mclk_index(tabla)]); +} + +static int tabla_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(dai->codec->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + if ((tabla_core != NULL) && + (tabla_core->dev != NULL) && + (tabla_core->dev->parent != NULL)) + pm_runtime_get_sync(tabla_core->dev->parent); + + return 0; +} + +static void tabla_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(dai->codec->dev->parent); + pr_debug("%s(): substream = %s stream = %d\n" , __func__, + substream->name, substream->stream); + if ((tabla_core != NULL) && + (tabla_core->dev != NULL) && + (tabla_core->dev->parent != NULL)) { + pm_runtime_mark_last_busy(tabla_core->dev->parent); + pm_runtime_put(tabla_core->dev->parent); + } +} + +int tabla_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, bool dapm) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: mclk_enable = %u, dapm = %d\n", __func__, mclk_enable, + dapm); + if (dapm) + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + if (mclk_enable) { + tabla->mclk_enabled = true; + + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + tabla_codec_calibrate_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + } else { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + } + } else { + + if (!tabla->mclk_enabled) { + if (dapm) + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_err("Error, MCLK already diabled\n"); + return -EINVAL; + } + tabla->mclk_enabled = false; + + if (tabla->mbhc_polling_active) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_MBHC_MODE); + tabla_enable_rx_bias(codec, 1); + tabla_codec_enable_clock_block(codec, 1); + tabla_codec_calibrate_hs_polling(codec); + tabla_codec_start_hs_polling(codec); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, + 0x05, 0x01); + } else { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_OFF); + } + } + if (dapm) + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + return 0; +} + +static int tabla_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int tabla_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + u8 val = 0; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + + pr_debug("%s\n", __func__); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + TABLA_I2S_MASTER_MODE_MASK, 0); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + TABLA_I2S_MASTER_MODE_MASK, 0); + } + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + val = TABLA_I2S_MASTER_MODE_MASK; + if (dai->id == AIF1_CAP) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, val, val); + else if (dai->id == AIF1_PB) + snd_soc_update_bits(dai->codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, val, val); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int tabla_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + u32 i = 0; + if (!tx_slot && !rx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + pr_debug("%s(): dai_name = %s DAI-ID %x tx_ch %d rx_ch %d\n", + __func__, dai->name, dai->id, tx_num, rx_num); + + if (dai->id == AIF1_PB || dai->id == AIF2_PB || dai->id == AIF3_PB) { + for (i = 0; i < rx_num; i++) { + tabla->dai[dai->id - 1].ch_num[i] = rx_slot[i]; + tabla->dai[dai->id - 1].ch_act = 0; + tabla->dai[dai->id - 1].ch_tot = rx_num; + } + } else if (dai->id == AIF1_CAP || dai->id == AIF2_CAP || + dai->id == AIF3_CAP) { + for (i = 0; i < tx_num; i++) { + tabla->dai[dai->id - 1].ch_num[i] = tx_slot[i]; + tabla->dai[dai->id - 1].ch_act = 0; + tabla->dai[dai->id - 1].ch_tot = tx_num; + } + } + return 0; +} + +static int tabla_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) + +{ + struct wcd9xxx *tabla = dev_get_drvdata(dai->codec->control_data); + + u32 cnt = 0; + u32 tx_ch[SLIM_MAX_TX_PORTS]; + u32 rx_ch[SLIM_MAX_RX_PORTS]; + + if (!rx_slot && !tx_slot) { + pr_err("%s: Invalid\n", __func__); + return -EINVAL; + } + + /* for virtual port, codec driver needs to do + * housekeeping, for now should be ok + */ + wcd9xxx_get_channel(tabla, rx_ch, tx_ch); + if (dai->id == AIF1_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[cnt]; + cnt++; + } + } else if (dai->id == AIF1_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + while (cnt < *tx_num) { + tx_slot[cnt] = tx_ch[6 + cnt]; + cnt++; + } + } else if (dai->id == AIF2_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + while (cnt < *rx_num) { + rx_slot[cnt] = rx_ch[5 + cnt]; + cnt++; + } + } else if (dai->id == AIF2_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + tx_slot[0] = tx_ch[cnt]; + tx_slot[1] = tx_ch[1 + cnt]; + tx_slot[2] = tx_ch[5 + cnt]; + tx_slot[3] = tx_ch[3 + cnt]; + + } else if (dai->id == AIF3_PB) { + *rx_num = tabla_dai[dai->id - 1].playback.channels_max; + rx_slot[0] = rx_ch[3]; + rx_slot[1] = rx_ch[4]; + + } else if (dai->id == AIF3_CAP) { + *tx_num = tabla_dai[dai->id - 1].capture.channels_max; + tx_slot[cnt] = tx_ch[2 + cnt]; + tx_slot[cnt + 1] = tx_ch[4 + cnt]; + } + pr_debug("%s(): dai_name = %s DAI-ID %x tx_ch %d rx_ch %d\n", + __func__, dai->name, dai->id, *tx_num, *rx_num); + + + return 0; +} + +static int tabla_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(dai->codec); + u8 path, shift; + u16 tx_fs_reg, rx_fs_reg; + u8 tx_fs_rate, rx_fs_rate, rx_state, tx_state; + u32 compander_fs; + + pr_debug("%s: dai_name = %s DAI-ID %x rate %d num_ch %d\n", __func__, + dai->name, dai->id, params_rate(params), + params_channels(params)); + + switch (params_rate(params)) { + case 8000: + tx_fs_rate = 0x00; + rx_fs_rate = 0x00; + compander_fs = COMPANDER_FS_8KHZ; + break; + case 16000: + tx_fs_rate = 0x01; + rx_fs_rate = 0x20; + compander_fs = COMPANDER_FS_16KHZ; + break; + case 32000: + tx_fs_rate = 0x02; + rx_fs_rate = 0x40; + compander_fs = COMPANDER_FS_32KHZ; + break; + case 48000: + tx_fs_rate = 0x03; + rx_fs_rate = 0x60; + compander_fs = COMPANDER_FS_48KHZ; + break; + case 96000: + tx_fs_rate = 0x04; + rx_fs_rate = 0x80; + compander_fs = COMPANDER_FS_96KHZ; + break; + case 192000: + tx_fs_rate = 0x05; + rx_fs_rate = 0xA0; + compander_fs = COMPANDER_FS_192KHZ; + break; + default: + pr_err("%s: Invalid sampling rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + + /** + * If current dai is a tx dai, set sample rate to + * all the txfe paths that are currently not active + */ + if ((dai->id == AIF1_CAP) || (dai->id == AIF2_CAP) || + (dai->id == AIF3_CAP)) { + + tx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_DECIMATORS; path++, shift++) { + + if (path == BITS_PER_REG + 1) { + shift = 0; + tx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL); + } + + if (!(tx_state & (1 << shift))) { + tx_fs_reg = TABLA_A_CDC_TX1_CLK_FS_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, tx_fs_reg, + 0x07, tx_fs_rate); + } + } + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_TX_I2S_CTL, + 0x07, tx_fs_rate); + } else { + tabla->dai[dai->id - 1].rate = params_rate(params); + } + } + /** + * TODO: Need to handle case where same RX chain takes 2 or more inputs + * with varying sample rates + */ + + /** + * If current dai is a rx dai, set sample rate to + * all the rx paths that are currently not active + */ + if (dai->id == AIF1_PB || dai->id == AIF2_PB || dai->id == AIF3_PB) { + + rx_state = snd_soc_read(codec, + TABLA_A_CDC_CLK_RX_B1_CTL); + + for (path = 1, shift = 0; + path <= NUM_INTERPOLATORS; path++, shift++) { + + if (!(rx_state & (1 << shift))) { + rx_fs_reg = TABLA_A_CDC_RX1_B5_CTL + + (BITS_PER_REG*(path-1)); + snd_soc_update_bits(codec, rx_fs_reg, + 0xE0, rx_fs_rate); + if (comp_rx_path[shift] < COMPANDER_MAX) + tabla->comp_fs[comp_rx_path[shift]] + = compander_fs; + } + } + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x20); + break; + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_update_bits(codec, + TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x20, 0x00); + break; + default: + pr_err("invalid format\n"); + break; + } + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_I2S_CTL, + 0x03, (rx_fs_rate >> 0x05)); + } else { + tabla->dai[dai->id - 1].rate = params_rate(params); + } + } + + return 0; +} + +static struct snd_soc_dai_ops tabla_dai_ops = { + .startup = tabla_startup, + .shutdown = tabla_shutdown, + .hw_params = tabla_hw_params, + .set_sysclk = tabla_set_dai_sysclk, + .set_fmt = tabla_set_dai_fmt, + .set_channel_map = tabla_set_channel_map, + .get_channel_map = tabla_get_channel_map, +}; + +static struct snd_soc_dai_driver tabla_dai[] = { + { + .name = "tabla_rx1", + .id = AIF1_PB, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx1", + .id = AIF1_CAP, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_rx2", + .id = AIF2_PB, + .playback = { + .stream_name = "AIF2 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx2", + .id = AIF2_CAP, + .capture = { + .stream_name = "AIF2 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx3", + .id = AIF3_CAP, + .capture = { + .stream_name = "AIF3 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_rx3", + .id = AIF3_PB, + .playback = { + .stream_name = "AIF3 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, +}; + +static struct snd_soc_dai_driver tabla_i2s_dai[] = { + { + .name = "tabla_i2s_rx1", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_i2s_tx1", + .id = 2, + .capture = { + .stream_name = "AIF1 Capture", + .rates = WCD9310_RATES, + .formats = TABLA_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &tabla_dai_ops, + }, +}; + +static int tabla_codec_enable_slimrx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *tabla; + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla_p = snd_soc_codec_get_drvdata(codec); + u32 j = 0; + u32 ret = 0; + codec->control_data = dev_get_drvdata(codec->dev->parent); + tabla = codec->control_data; + /* Execute the callback only if interface type is slimbus */ + if (tabla_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + + pr_debug("%s: %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if ((tabla_dai[j].id == AIF1_CAP) || + (tabla_dai[j].id == AIF2_CAP) || + (tabla_dai[j].id == AIF3_CAP)) + continue; + if (!strncmp(w->sname, + tabla_dai[j].playback.stream_name, 13)) { + ++tabla_p->dai[j].ch_act; + break; + } + } + if (tabla_p->dai[j].ch_act == tabla_p->dai[j].ch_tot) + ret = wcd9xxx_cfg_slim_sch_rx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot, + tabla_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if ((tabla_dai[j].id == AIF1_CAP) || + (tabla_dai[j].id == AIF2_CAP) || + (tabla_dai[j].id == AIF3_CAP)) + continue; + if (!strncmp(w->sname, + tabla_dai[j].playback.stream_name, 13)) { + --tabla_p->dai[j].ch_act; + break; + } + } + if (!tabla_p->dai[j].ch_act) { + ret = wcd9xxx_close_slim_sch_rx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot); + usleep_range(15000, 15000); + tabla_p->dai[j].rate = 0; + memset(tabla_p->dai[j].ch_num, 0, (sizeof(u32)* + tabla_p->dai[j].ch_tot)); + tabla_p->dai[j].ch_tot = 0; + } + } + return ret; +} + +static int tabla_codec_enable_slimtx(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wcd9xxx *tabla; + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla_p = snd_soc_codec_get_drvdata(codec); + /* index to the DAI ID, for now hardcoding */ + u32 j = 0; + u32 ret = 0; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + tabla = codec->control_data; + + /* Execute the callback only if interface type is slimbus */ + if (tabla_p->intf_type != WCD9XXX_INTERFACE_TYPE_SLIMBUS) + return 0; + + pr_debug("%s(): %s %d\n", __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if (tabla_dai[j].id == AIF1_PB || + tabla_dai[j].id == AIF2_PB || + tabla_dai[j].id == AIF3_PB) + continue; + if (!strncmp(w->sname, + tabla_dai[j].capture.stream_name, 13)) { + ++tabla_p->dai[j].ch_act; + break; + } + } + if (tabla_p->dai[j].ch_act == tabla_p->dai[j].ch_tot) + ret = wcd9xxx_cfg_slim_sch_tx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot, + tabla_p->dai[j].rate); + break; + case SND_SOC_DAPM_POST_PMD: + for (j = 0; j < ARRAY_SIZE(tabla_dai); j++) { + if (tabla_dai[j].id == AIF1_PB || + tabla_dai[j].id == AIF2_PB || + tabla_dai[j].id == AIF3_PB) + continue; + if (!strncmp(w->sname, + tabla_dai[j].capture.stream_name, 13)) { + --tabla_p->dai[j].ch_act; + break; + } + } + if (!tabla_p->dai[j].ch_act) { + ret = wcd9xxx_close_slim_sch_tx(tabla, + tabla_p->dai[j].ch_num, + tabla_p->dai[j].ch_tot); + tabla_p->dai[j].rate = 0; + memset(tabla_p->dai[j].ch_num, 0, (sizeof(u32)* + tabla_p->dai[j].ch_tot)); + tabla_p->dai[j].ch_tot = 0; + } + } + return ret; +} + +/* Todo: Have seperate dapm widgets for I2S and Slimbus. + * Might Need to have callbacks registered only for slimbus + */ +static const struct snd_soc_dapm_widget tabla_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_PGA("EAR PA", TABLA_A_RX_EAR_EN, 4, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("DAC1", TABLA_A_RX_EAR_EN, 6, 0, dac1_switch, + ARRAY_SIZE(dac1_switch)), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX1", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX2", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX3", "AIF1 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX4", "AIF3 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX5", "AIF3 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_IN_E("SLIM RX6", "AIF2 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SLIM RX7", "AIF2 Playback", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimrx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Headphone */ + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + SND_SOC_DAPM_PGA_E("HPHL", TABLA_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, + tabla_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("HPHL DAC", TABLA_A_RX_HPH_L_DAC_CTL, 7, 0, + hphl_switch, ARRAY_SIZE(hphl_switch)), + + SND_SOC_DAPM_PGA_E("HPHR", TABLA_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, + tabla_hph_pa_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("HPHR DAC", NULL, TABLA_A_RX_HPH_R_DAC_CTL, 7, 0, + tabla_hphr_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + SND_SOC_DAPM_OUTPUT("LINEOUT3"), + SND_SOC_DAPM_OUTPUT("LINEOUT4"), + SND_SOC_DAPM_OUTPUT("LINEOUT5"), + + SND_SOC_DAPM_PGA_E("LINEOUT1 PA", TABLA_A_RX_LINE_CNP_EN, 0, 0, NULL, + 0, tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT2 PA", TABLA_A_RX_LINE_CNP_EN, 1, 0, NULL, + 0, tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT3 PA", TABLA_A_RX_LINE_CNP_EN, 2, 0, NULL, + 0, tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT4 PA", TABLA_A_RX_LINE_CNP_EN, 3, 0, NULL, + 0, tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LINEOUT5 PA", TABLA_A_RX_LINE_CNP_EN, 4, 0, NULL, 0, + tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("LINEOUT1 DAC", NULL, TABLA_A_RX_LINE_1_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT2 DAC", NULL, TABLA_A_RX_LINE_2_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("LINEOUT3 DAC", NULL, TABLA_A_RX_LINE_3_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT3 DAC GROUND", SND_SOC_NOPM, 0, 0, + &lineout3_ground_switch), + SND_SOC_DAPM_DAC_E("LINEOUT4 DAC", NULL, TABLA_A_RX_LINE_4_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT4 DAC GROUND", SND_SOC_NOPM, 0, 0, + &lineout4_ground_switch), + SND_SOC_DAPM_DAC_E("LINEOUT5 DAC", NULL, TABLA_A_RX_LINE_5_DAC_CTL, 7, 0 + , tabla_lineout_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("RX1 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 0, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX2 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 1, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX3 MIX2", TABLA_A_CDC_CLK_RX_B1_CTL, 2, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX4 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 3, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX5 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 4, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX6 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 5, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER_E("RX7 MIX1", TABLA_A_CDC_CLK_RX_B1_CTL, 6, 0, NULL, + 0, tabla_codec_reset_interpolator, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_MIXER("RX1 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX3 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX_E("RX4 DSM MUX", TABLA_A_CDC_CLK_RX_B1_CTL, 3, 0, + &rx4_dsm_mux, tabla_codec_reset_interpolator, + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MUX_E("RX6 DSM MUX", TABLA_A_CDC_CLK_RX_B1_CTL, 5, 0, + &rx6_dsm_mux, tabla_codec_reset_interpolator, + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MIXER("RX1 CHAIN", TABLA_A_CDC_RX1_B6_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 CHAIN", TABLA_A_CDC_RX2_B6_CTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MUX("RX1 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP3", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp3_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX4 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx4_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX4 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx4_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX5 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx5_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX5 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx5_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX6 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx6_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX6 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx6_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX7 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx7_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX7 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx7_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX1 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx1_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx1_mix2_inp2_mux), + SND_SOC_DAPM_MUX("RX2 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix2_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX2 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix2_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX2 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix2_inp2_mux), + + SND_SOC_DAPM_SUPPLY("CP", TABLA_A_CP_EN, 0, 0, + tabla_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY("RX_BIAS", SND_SOC_NOPM, 0, 0, + tabla_codec_enable_rx_bias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* TX */ + + SND_SOC_DAPM_SUPPLY("CDC_CONN", TABLA_A_CDC_CLK_OTHR_CTL, 2, 0, NULL, + 0), + + SND_SOC_DAPM_SUPPLY("LDO_H", TABLA_A_LDO_H_MODE_1, 7, 0, + tabla_codec_enable_ldo_h, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("COMP1_CLK", SND_SOC_NOPM, 0, 0, + tabla_config_compander, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_SUPPLY("COMP2_CLK", SND_SOC_NOPM, 1, 0, + tabla_config_compander, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_POST_PMD), + + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 External", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal1", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal2", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC1", NULL, TABLA_A_TX_1_2_EN, 7, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("AMIC3"), + SND_SOC_DAPM_ADC_E("ADC3", NULL, TABLA_A_TX_3_4_EN, 7, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("AMIC4"), + SND_SOC_DAPM_ADC_E("ADC4", NULL, TABLA_A_TX_3_4_EN, 3, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("AMIC5"), + SND_SOC_DAPM_ADC_E("ADC5", NULL, TABLA_A_TX_5_6_EN, 7, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_INPUT("AMIC6"), + SND_SOC_DAPM_ADC_E("ADC6", NULL, TABLA_A_TX_5_6_EN, 3, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_MUX_E("DEC1 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0, 0, + &dec1_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC2 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 1, 0, + &dec2_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC3 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 2, 0, + &dec3_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC4 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 3, 0, + &dec4_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC5 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 4, 0, + &dec5_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC6 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 5, 0, + &dec6_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC7 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 6, 0, + &dec7_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC8 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 7, 0, + &dec8_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC9 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL, 0, 0, + &dec9_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC10 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL, 1, 0, + &dec10_mux, tabla_codec_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("ANC1 MUX", SND_SOC_NOPM, 0, 0, &anc1_mux), + SND_SOC_DAPM_MUX("ANC2 MUX", SND_SOC_NOPM, 0, 0, &anc2_mux), + + SND_SOC_DAPM_MIXER_E("ANC", SND_SOC_NOPM, 0, 0, NULL, 0, + tabla_codec_enable_anc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("ANC1 FB MUX", SND_SOC_NOPM, 0, 0, &anc1_fb_mux), + + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 External", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal1", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal2", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal3", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 External", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 Internal1", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 Internal2", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2", NULL, TABLA_A_TX_1_2_EN, 3, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX1 MUX", SND_SOC_NOPM, 0, 0, &sb_tx1_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX1", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX2 MUX", SND_SOC_NOPM, 0, 0, &sb_tx2_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX2", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX3 MUX", SND_SOC_NOPM, 0, 0, &sb_tx3_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX3", "AIF3 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX4 MUX", SND_SOC_NOPM, 0, 0, &sb_tx4_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX4", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX5 MUX", SND_SOC_NOPM, 0, 0, &sb_tx5_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX5", "AIF3 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX6 MUX", SND_SOC_NOPM, 0, 0, &sb_tx6_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX6", "AIF2 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX7 MUX", SND_SOC_NOPM, 0, 0, &sb_tx7_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX7", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX8 MUX", SND_SOC_NOPM, 0, 0, &sb_tx8_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX8", "AIF1 Capture", 0, SND_SOC_NOPM, 0, + 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX9 MUX", SND_SOC_NOPM, 0, 0, &sb_tx9_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX9", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX10 MUX", SND_SOC_NOPM, 0, 0, &sb_tx10_mux), + SND_SOC_DAPM_AIF_OUT_E("SLIM TX10", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0, tabla_codec_enable_slimtx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Digital Mic Inputs */ + SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC3", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC4", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("DMIC5", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC6", NULL, SND_SOC_NOPM, 0, 0, + tabla_codec_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Sidetone */ + SND_SOC_DAPM_MUX("IIR1 INP1 MUX", SND_SOC_NOPM, 0, 0, &iir1_inp1_mux), + SND_SOC_DAPM_PGA("IIR1", TABLA_A_CDC_CLK_SD_CTL, 0, 0, NULL, 0), + + /* AUX PGA */ + SND_SOC_DAPM_ADC_E("AUX_PGA_Left", NULL, TABLA_A_AUX_L_EN, 7, 0, + tabla_codec_enable_aux_pga, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("AUX_PGA_Right", NULL, TABLA_A_AUX_R_EN, 7, 0, + tabla_codec_enable_aux_pga, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + + /* Lineout, ear and HPH PA Mixers */ + SND_SOC_DAPM_MIXER("HPHL_PA_MIXER", SND_SOC_NOPM, 0, 0, + hphl_pa_mix, ARRAY_SIZE(hphl_pa_mix)), + + SND_SOC_DAPM_MIXER("HPHR_PA_MIXER", SND_SOC_NOPM, 0, 0, + hphr_pa_mix, ARRAY_SIZE(hphr_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT1_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout1_pa_mix, ARRAY_SIZE(lineout1_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT2_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout2_pa_mix, ARRAY_SIZE(lineout2_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT3_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout3_pa_mix, ARRAY_SIZE(lineout3_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT4_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout4_pa_mix, ARRAY_SIZE(lineout4_pa_mix)), + + SND_SOC_DAPM_MIXER("LINEOUT5_PA_MIXER", SND_SOC_NOPM, 0, 0, + lineout5_pa_mix, ARRAY_SIZE(lineout5_pa_mix)), + + SND_SOC_DAPM_MIXER("EAR_PA_MIXER", SND_SOC_NOPM, 0, 0, + ear_pa_mix, ARRAY_SIZE(ear_pa_mix)), +}; + +static short tabla_codec_read_sta_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B3_STATUS); + bias_lsb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B2_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static short tabla_codec_read_dce_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B5_STATUS); + bias_lsb = snd_soc_read(codec, TABLA_A_CDC_MBHC_B4_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static void tabla_turn_onoff_rel_detection(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, on << 1); +} + +static short __tabla_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool override_bypass, bool noreldetection) +{ + short bias_value; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + if (noreldetection) + tabla_turn_onoff_rel_detection(codec, false); + + /* Turn on the override */ + if (!override_bypass) + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + if (dce) { + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x4); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(tabla->mbhc_data.t_sta_dce, + tabla->mbhc_data.t_sta_dce); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x4); + usleep_range(tabla->mbhc_data.t_dce, + tabla->mbhc_data.t_dce); + bias_value = tabla_codec_read_dce_result(codec); + } else { + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + usleep_range(tabla->mbhc_data.t_sta_dce, + tabla->mbhc_data.t_sta_dce); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x2); + usleep_range(tabla->mbhc_data.t_sta, + tabla->mbhc_data.t_sta); + bias_value = tabla_codec_read_sta_result(codec); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x0); + } + /* Turn off the override after measuring mic voltage */ + if (!override_bypass) + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + + if (noreldetection) + tabla_turn_onoff_rel_detection(codec, true); + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + + return bias_value; +} + +static short tabla_codec_sta_dce(struct snd_soc_codec *codec, int dce, + bool norel) +{ + return __tabla_codec_sta_dce(codec, dce, false, norel); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static short tabla_codec_setup_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + short bias_value; + u8 cfilt_mode; + + pr_debug("%s: enter, mclk_enabled %d\n", __func__, tabla->mclk_enabled); + if (!tabla->mbhc_cfg.calibration) { + pr_err("Error, no tabla calibration\n"); + return -ENODEV; + } + + if (!tabla->mclk_enabled) { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_MBHC_MODE); + tabla_enable_rx_bias(codec, 1); + tabla_codec_enable_clock_block(codec, 1); + } + + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* Make sure CFILT is in fast mode, save current mode */ + cfilt_mode = snd_soc_read(codec, tabla->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x70, 0x00); + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x1F, 0x16); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x80, 0x80); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x1F, 0x1C); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_TEST_CTL, 0x40, 0x40); + + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x80, 0x00); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x00); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + tabla_codec_calibrate_hs_polling(codec); + + /* don't flip override */ + bias_value = __tabla_codec_sta_dce(codec, 1, true, true); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + + return bias_value; +} + +static int tabla_cancel_btn_work(struct tabla_priv *tabla) +{ + int r = 0; + struct wcd9xxx *core = dev_get_drvdata(tabla->codec->dev->parent); + + if (cancel_delayed_work_sync(&tabla->mbhc_btn_dwork)) { + /* if scheduled mbhc_btn_dwork is canceled from here, + * we have to unlock from here instead btn_work */ + wcd9xxx_unlock_sleep(core); + r = 1; + } + return r; +} + +/* called under codec_resource_lock acquisition */ +void tabla_set_and_turnoff_hph_padac(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + u8 wg_time; + + wg_time = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + /* If headphone PA is on, check if userspace receives + * removal event to sync-up PA's state */ + if (tabla_is_hph_pa_on(codec)) { + pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__); + set_bit(TABLA_HPHL_PA_OFF_ACK, &tabla->hph_pa_dac_state); + set_bit(TABLA_HPHR_PA_OFF_ACK, &tabla->hph_pa_dac_state); + } else { + pr_debug("%s PA is off\n", __func__); + } + + if (tabla_is_hph_dac_on(codec, 1)) + set_bit(TABLA_HPHL_DAC_OFF_ACK, &tabla->hph_pa_dac_state); + if (tabla_is_hph_dac_on(codec, 0)) + set_bit(TABLA_HPHR_DAC_OFF_ACK, &tabla->hph_pa_dac_state); + + snd_soc_update_bits(codec, TABLA_A_RX_HPH_CNP_EN, 0x30, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_DAC_CTL, + 0xC0, 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_DAC_CTL, + 0xC0, 0x00); + usleep_range(wg_time * 1000, wg_time * 1000); +} + +static void tabla_clr_and_turnon_hph_padac(struct tabla_priv *tabla) +{ + bool pa_turned_on = false; + struct snd_soc_codec *codec = tabla->codec; + u8 wg_time; + + wg_time = snd_soc_read(codec, TABLA_A_RX_HPH_CNP_WG_TIME) ; + wg_time += 1; + + if (test_and_clear_bit(TABLA_HPHR_DAC_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable DAC\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_R_DAC_CTL, + 0xC0, 0xC0); + } + if (test_and_clear_bit(TABLA_HPHL_DAC_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable DAC\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_L_DAC_CTL, + 0xC0, 0xC0); + } + + if (test_and_clear_bit(TABLA_HPHR_PA_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable PA\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x10, + 1 << 4); + pa_turned_on = true; + } + if (test_and_clear_bit(TABLA_HPHL_PA_OFF_ACK, + &tabla->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable PA\n", __func__); + snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x20, + 1 << 5); + pa_turned_on = true; + } + + if (pa_turned_on) { + pr_debug("%s: PA was turned off by MBHC and not by DAPM\n", + __func__); + usleep_range(wg_time * 1000, wg_time * 1000); + } +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_report_plug(struct snd_soc_codec *codec, int insertion, + enum snd_jack_types jack_type) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (!insertion) { + /* Report removal */ + tabla->hph_status &= ~jack_type; + if (tabla->mbhc_cfg.headset_jack) { + /* cancel possibly scheduled btn work and + * report release if we reported button press */ + if (tabla_cancel_btn_work(tabla)) { + pr_debug("%s: button press is canceled\n", + __func__); + } else if (tabla->buttons_pressed) { + pr_debug("%s: Reporting release for reported " + "button press %d\n", __func__, + jack_type); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.button_jack, 0, + tabla->buttons_pressed); + tabla->buttons_pressed &= + ~TABLA_JACK_BUTTON_MASK; + } + pr_debug("%s: Reporting removal %d(%x)\n", __func__, + jack_type, tabla->hph_status); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + tabla_set_and_turnoff_hph_padac(codec); + hphocp_off_report(tabla, SND_JACK_OC_HPHR, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + hphocp_off_report(tabla, SND_JACK_OC_HPHL, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + tabla->current_plug = PLUG_TYPE_NONE; + tabla->mbhc_polling_active = false; + } else { + /* Report insertion */ + tabla->hph_status |= jack_type; + + if (jack_type == SND_JACK_HEADPHONE) + tabla->current_plug = PLUG_TYPE_HEADPHONE; + else if (jack_type == SND_JACK_UNSUPPORTED) + tabla->current_plug = PLUG_TYPE_GND_MIC_SWAP; + else if (jack_type == SND_JACK_HEADSET) { + tabla->mbhc_polling_active = true; + tabla->current_plug = PLUG_TYPE_HEADSET; + } + if (tabla->mbhc_cfg.headset_jack) { + pr_debug("%s: Reporting insertion %d(%x)\n", __func__, + jack_type, tabla->hph_status); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + tabla_clr_and_turnon_hph_padac(tabla); + } +} + +static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec, + int insertion, int trigger, + bool padac_off) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + int central_bias_enabled = 0; + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + const struct tabla_mbhc_plug_detect_cfg *plug_det = + TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration); + + if (!tabla->mbhc_cfg.calibration) { + pr_err("Error, no tabla calibration\n"); + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0); + + /* Make sure mic bias and Mic line schmitt trigger + * are turned OFF + */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x01); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + + if (insertion) { + tabla_codec_switch_micbias(codec, 0); + + /* DAPM can manipulate PA/DAC bits concurrently */ + if (padac_off == true) { + tabla_set_and_turnoff_hph_padac(codec); + } + + if (trigger & MBHC_USE_HPHL_TRIGGER) { + /* Enable HPH Schmitt Trigger */ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x11, + 0x11); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x0C, + plug_det->hph_current << 2); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x02, + 0x02); + } + if (trigger & MBHC_USE_MB_TRIGGER) { + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x60, plug_det->mic_current << 5); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + } + + /* setup for insetion detection */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0); + } else { + pr_debug("setup for removal detection\n"); + /* Make sure the HPH schmitt trigger is OFF */ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x12, 0x00); + + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x60, + plug_det->mic_current << 5); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + + /* Setup for low power removal detection */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0x2); + } + + if (snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x4) { + /* called called by interrupt */ + if (!(tabla->clock_active)) { + tabla_codec_enable_config_mode(codec, 1); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x06, 0); + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + tabla_codec_enable_config_mode(codec, 0); + } else + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x06, 0); + } + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.int_rbias, 0x80, 0); + + /* If central bandgap disabled */ + if (!(snd_soc_read(codec, TABLA_A_PIN_CTL_OE1) & 1)) { + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE1, 0x3, 0x3); + usleep_range(generic->t_bg_fast_settle, + generic->t_bg_fast_settle); + central_bias_enabled = 1; + } + + /* If LDO_H disabled */ + if (snd_soc_read(codec, TABLA_A_PIN_CTL_OE0) & 0x80) { + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x10, 0); + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x80, 0x80); + usleep_range(generic->t_ldoh, generic->t_ldoh); + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x80, 0); + + if (central_bias_enabled) + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE1, 0x1, 0); + } + + snd_soc_update_bits(codec, tabla->reg_addr.micb_4_mbhc, 0x3, + tabla->mbhc_cfg.micbias); + + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0x1); + return 0; +} + +static u16 tabla_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce, + s16 vin_mv) +{ + struct tabla_priv *tabla; + s16 diff, zero; + u32 mb_mv, in; + u16 value; + + tabla = snd_soc_codec_get_drvdata(codec); + mb_mv = tabla->mbhc_data.micb_mv; + + if (mb_mv == 0) { + pr_err("%s: Mic Bias voltage is set to zero\n", __func__); + return -EINVAL; + } + + if (dce) { + diff = (tabla->mbhc_data.dce_mb) - (tabla->mbhc_data.dce_z); + zero = (tabla->mbhc_data.dce_z); + } else { + diff = (tabla->mbhc_data.sta_mb) - (tabla->mbhc_data.sta_z); + zero = (tabla->mbhc_data.sta_z); + } + in = (u32) diff * vin_mv; + + value = (u16) (in / mb_mv) + zero; + return value; +} + +static s32 tabla_codec_sta_dce_v(struct snd_soc_codec *codec, s8 dce, + u16 bias_value) +{ + struct tabla_priv *tabla; + s16 value, z, mb; + s32 mv; + + tabla = snd_soc_codec_get_drvdata(codec); + value = bias_value; + if (dce) { + z = (tabla->mbhc_data.dce_z); + mb = (tabla->mbhc_data.dce_mb); + mv = (value - z) * (s32)tabla->mbhc_data.micb_mv / (mb - z); + } else { + z = (tabla->mbhc_data.sta_z); + mb = (tabla->mbhc_data.sta_mb); + mv = (value - z) * (s32)tabla->mbhc_data.micb_mv / (mb - z); + } + + return mv; +} + +static void btn_lpress_fn(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct tabla_priv *tabla; + short bias_value; + int dce_mv, sta_mv; + struct wcd9xxx *core; + + pr_debug("%s:\n", __func__); + + delayed_work = to_delayed_work(work); + tabla = container_of(delayed_work, struct tabla_priv, mbhc_btn_dwork); + core = dev_get_drvdata(tabla->codec->dev->parent); + + if (tabla) { + if (tabla->mbhc_cfg.button_jack) { + bias_value = tabla_codec_read_sta_result(tabla->codec); + sta_mv = tabla_codec_sta_dce_v(tabla->codec, 0, + bias_value); + bias_value = tabla_codec_read_dce_result(tabla->codec); + dce_mv = tabla_codec_sta_dce_v(tabla->codec, 1, + bias_value); + pr_debug("%s: Reporting long button press event" + " STA: %d, DCE: %d\n", __func__, + sta_mv, dce_mv); + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.button_jack, + tabla->buttons_pressed, + tabla->buttons_pressed); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + pr_debug("%s: leave\n", __func__); + wcd9xxx_unlock_sleep(core); +} + +void tabla_mbhc_cal(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + struct tabla_mbhc_btn_detect_cfg *btn_det; + u8 cfilt_mode, bg_mode; + u8 ncic, nmeas, navg; + u32 mclk_rate; + u32 dce_wait, sta_wait; + u8 *n_cic; + void *calibration; + + tabla = snd_soc_codec_get_drvdata(codec); + calibration = tabla->mbhc_cfg.calibration; + + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + tabla_turn_onoff_rel_detection(codec, false); + + /* First compute the DCE / STA wait times + * depending on tunable parameters. + * The value is computed in microseconds + */ + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(calibration); + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + ncic = n_cic[tabla_codec_mclk_index(tabla)]; + nmeas = TABLA_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas; + navg = TABLA_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg; + mclk_rate = tabla->mbhc_cfg.mclk_rate; + dce_wait = (1000 * 512 * ncic * (nmeas + 1)) / (mclk_rate / 1000); + sta_wait = (1000 * 128 * (navg + 1)) / (mclk_rate / 1000); + + tabla->mbhc_data.t_dce = dce_wait; + tabla->mbhc_data.t_sta = sta_wait; + + /* LDOH and CFILT are already configured during pdata handling. + * Only need to make sure CFILT and bandgap are in Fast mode. + * Need to restore defaults once calculation is done. + */ + cfilt_mode = snd_soc_read(codec, tabla->mbhc_bias_regs.cfilt_ctl); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, 0x00); + bg_mode = snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x02, + 0x02); + + /* Micbias, CFILT, LDOH, MBHC MUX mode settings + * to perform ADC calibration + */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x60, + tabla->mbhc_cfg.micbias << 5); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x60, 0x60); + snd_soc_write(codec, TABLA_A_TX_7_MBHC_TEST_CTL, 0x78); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x04); + + /* DCE measurement for 0 volts */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(tabla->mbhc_data.t_dce, tabla->mbhc_data.t_dce); + tabla->mbhc_data.dce_z = tabla_codec_read_dce_result(codec); + + /* DCE measurment for MB voltage */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(tabla->mbhc_data.t_dce, tabla->mbhc_data.t_dce); + tabla->mbhc_data.dce_mb = tabla_codec_read_dce_result(codec); + + /* Sta measuremnt for 0 volts */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + snd_soc_write(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x81); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(tabla->mbhc_data.t_sta, tabla->mbhc_data.t_sta); + tabla->mbhc_data.sta_z = tabla_codec_read_sta_result(codec); + + /* STA Measurement for MB Voltage */ + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x82); + usleep_range(100, 100); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(tabla->mbhc_data.t_sta, tabla->mbhc_data.t_sta); + tabla->mbhc_data.sta_mb = tabla_codec_read_sta_result(codec); + + /* Restore default settings. */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, 0x40, + cfilt_mode); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x02, bg_mode); + + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84); + usleep_range(100, 100); + + wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + tabla_turn_onoff_rel_detection(codec, true); +} + +void *tabla_mbhc_cal_btn_det_mp(const struct tabla_mbhc_btn_detect_cfg* btn_det, + const enum tabla_mbhc_btn_det_mem mem) +{ + void *ret = &btn_det->_v_btn_low; + + switch (mem) { + case TABLA_BTN_DET_GAIN: + ret += sizeof(btn_det->_n_cic); + case TABLA_BTN_DET_N_CIC: + ret += sizeof(btn_det->_n_ready); + case TABLA_BTN_DET_N_READY: + ret += sizeof(btn_det->_v_btn_high[0]) * btn_det->num_btn; + case TABLA_BTN_DET_V_BTN_HIGH: + ret += sizeof(btn_det->_v_btn_low[0]) * btn_det->num_btn; + case TABLA_BTN_DET_V_BTN_LOW: + /* do nothing */ + break; + default: + ret = NULL; + } + + return ret; +} + +static s16 tabla_scale_v_micb_vddio(struct tabla_priv *tabla, int v, + bool tovddio) +{ + int r; + int vddio_k, mb_k; + vddio_k = tabla_find_k_value(tabla->pdata->micbias.ldoh_v, + VDDIO_MICBIAS_MV); + mb_k = tabla_find_k_value(tabla->pdata->micbias.ldoh_v, + tabla->mbhc_data.micb_mv); + if (tovddio) + r = v * vddio_k / mb_k; + else + r = v * mb_k / vddio_k; + return r; +} + +static void tabla_mbhc_calc_thres(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + s16 btn_mv = 0, btn_delta_mv; + struct tabla_mbhc_btn_detect_cfg *btn_det; + struct tabla_mbhc_plug_type_cfg *plug_type; + u16 *btn_high; + u8 *n_ready; + int i; + + tabla = snd_soc_codec_get_drvdata(codec); + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + plug_type = TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + + n_ready = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_READY); + if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_12288KHZ) { + tabla->mbhc_data.npoll = 4; + tabla->mbhc_data.nbounce_wait = 30; + } else if (tabla->mbhc_cfg.mclk_rate == TABLA_MCLK_RATE_9600KHZ) { + tabla->mbhc_data.npoll = 7; + tabla->mbhc_data.nbounce_wait = 23; + } + + tabla->mbhc_data.t_sta_dce = ((1000 * 256) / + (tabla->mbhc_cfg.mclk_rate / 1000) * + n_ready[tabla_codec_mclk_index(tabla)]) + + 10; + tabla->mbhc_data.v_ins_hu = + tabla_codec_v_sta_dce(codec, STA, plug_type->v_hs_max); + tabla->mbhc_data.v_ins_h = + tabla_codec_v_sta_dce(codec, DCE, plug_type->v_hs_max); + + tabla->mbhc_data.v_inval_ins_low = TABLA_MBHC_FAKE_INSERT_LOW; + if (tabla->mbhc_cfg.gpio) + tabla->mbhc_data.v_inval_ins_high = + TABLA_MBHC_FAKE_INSERT_HIGH; + else + tabla->mbhc_data.v_inval_ins_high = + TABLA_MBHC_FAKE_INS_HIGH_NO_GPIO; + + if (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + tabla->mbhc_data.adj_v_hs_max = + tabla_scale_v_micb_vddio(tabla, plug_type->v_hs_max, true); + tabla->mbhc_data.adj_v_ins_hu = + tabla_codec_v_sta_dce(codec, STA, + tabla->mbhc_data.adj_v_hs_max); + tabla->mbhc_data.adj_v_ins_h = + tabla_codec_v_sta_dce(codec, DCE, + tabla->mbhc_data.adj_v_hs_max); + tabla->mbhc_data.v_inval_ins_low = + tabla_scale_v_micb_vddio(tabla, + tabla->mbhc_data.v_inval_ins_low, + false); + tabla->mbhc_data.v_inval_ins_high = + tabla_scale_v_micb_vddio(tabla, + tabla->mbhc_data.v_inval_ins_high, + false); + } + + btn_high = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) + btn_mv = btn_high[i] > btn_mv ? btn_high[i] : btn_mv; + + tabla->mbhc_data.v_b1_h = tabla_codec_v_sta_dce(codec, DCE, btn_mv); + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_sta; + tabla->mbhc_data.v_b1_hu = + tabla_codec_v_sta_dce(codec, STA, btn_delta_mv); + + btn_delta_mv = btn_mv + btn_det->v_btn_press_delta_cic; + + tabla->mbhc_data.v_b1_huc = + tabla_codec_v_sta_dce(codec, DCE, btn_delta_mv); + + tabla->mbhc_data.v_brh = tabla->mbhc_data.v_b1_h; + tabla->mbhc_data.v_brl = TABLA_MBHC_BUTTON_MIN; + + tabla->mbhc_data.v_no_mic = + tabla_codec_v_sta_dce(codec, STA, plug_type->v_no_mic); +} + +void tabla_mbhc_init(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla; + struct tabla_mbhc_general_cfg *generic; + struct tabla_mbhc_btn_detect_cfg *btn_det; + int n; + u8 *n_cic, *gain; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + tabla = snd_soc_codec_get_drvdata(codec); + generic = TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(tabla->mbhc_cfg.calibration); + + for (n = 0; n < 8; n++) { + if ((!TABLA_IS_1_X(tabla_core->version)) || n != 7) { + snd_soc_update_bits(codec, + TABLA_A_CDC_MBHC_FEATURE_B1_CFG, + 0x07, n); + snd_soc_write(codec, TABLA_A_CDC_MBHC_FEATURE_B2_CFG, + btn_det->c[n]); + } + } + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B2_CTL, 0x07, + btn_det->nc); + + n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL, 0xFF, + n_cic[tabla_codec_mclk_index(tabla)]); + + gain = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_GAIN); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B2_CTL, 0x78, + gain[tabla_codec_mclk_index(tabla)] << 3); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B4_CTL, 0x70, + generic->mbhc_nsa << 4); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B4_CTL, 0x0F, + btn_det->n_meas); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B5_CTL, generic->mbhc_navg); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x80, 0x80); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + + snd_soc_update_bits(codec, tabla->reg_addr.micb_4_mbhc, 0x03, + TABLA_MICBIAS2); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + + snd_soc_update_bits(codec, TABLA_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0); +} + +static bool tabla_mbhc_fw_validate(const struct firmware *fw) +{ + u32 cfg_offset; + struct tabla_mbhc_imped_detect_cfg *imped_cfg; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + + if (fw->size < TABLA_MBHC_CAL_MIN_SIZE) + return false; + + /* previous check guarantees that there is enough fw data up + * to num_btn + */ + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data); + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_BTN_SZ(btn_cfg))) + return false; + + /* previous check guarantees that there is enough fw data up + * to start of impedance detection configuration + */ + imped_cfg = TABLA_MBHC_CAL_IMPED_DET_PTR(fw->data); + cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data); + + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_MIN_SZ)) + return false; + + if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_SZ(imped_cfg))) + return false; + + return true; +} + +/* called under codec_resource_lock acquisition */ +static int tabla_determine_button(const struct tabla_priv *priv, + const s32 micmv) +{ + s16 *v_btn_low, *v_btn_high; + struct tabla_mbhc_btn_detect_cfg *btn_det; + int i, btn = -1; + + btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + v_btn_low = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_V_BTN_LOW); + v_btn_high = tabla_mbhc_cal_btn_det_mp(btn_det, + TABLA_BTN_DET_V_BTN_HIGH); + + for (i = 0; i < btn_det->num_btn; i++) { + if ((v_btn_low[i] <= micmv) && (v_btn_high[i] >= micmv)) { + btn = i; + break; + } + } + + if (btn == -1) + pr_debug("%s: couldn't find button number for mic mv %d\n", + __func__, micmv); + + return btn; +} + +static int tabla_get_button_mask(const int btn) +{ + int mask = 0; + switch (btn) { + case 0: + mask = SND_JACK_BTN_0; + break; + case 1: + mask = SND_JACK_BTN_1; + break; + case 2: + mask = SND_JACK_BTN_2; + break; + case 3: + mask = SND_JACK_BTN_3; + break; + case 4: + mask = SND_JACK_BTN_4; + break; + case 5: + mask = SND_JACK_BTN_5; + break; + case 6: + mask = SND_JACK_BTN_6; + break; + case 7: + mask = SND_JACK_BTN_7; + break; + } + return mask; +} + +static irqreturn_t tabla_dce_handler(int irq, void *data) +{ + int i, mask; + short dce, sta; + s32 mv, mv_s, stamv_s; + bool vddio; + int btn = -1, meas = 0; + struct tabla_priv *priv = data; + const struct tabla_mbhc_btn_detect_cfg *d = + TABLA_MBHC_CAL_BTN_DET_PTR(priv->mbhc_cfg.calibration); + short btnmeas[d->n_btn_meas + 1]; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + int n_btn_meas = d->n_btn_meas; + u8 mbhc_status = snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_STATUS) & 0x3E; + + pr_debug("%s: enter\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + if (priv->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) { + pr_debug("%s: mbhc is being recovered, skip button press\n", + __func__); + goto done; + } + + priv->mbhc_state = MBHC_STATE_POTENTIAL; + + if (!priv->mbhc_polling_active) { + pr_warn("%s: mbhc polling is not active, skip button press\n", + __func__); + goto done; + } + + dce = tabla_codec_read_dce_result(codec); + mv = tabla_codec_sta_dce_v(codec, 1, dce); + + /* If GPIO interrupt already kicked in, ignore button press */ + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO State Changed, ignore button press\n", + __func__); + btn = -1; + goto done; + } + + vddio = (priv->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + priv->mbhc_micbias_switched); + mv_s = vddio ? tabla_scale_v_micb_vddio(priv, mv, false) : mv; + + if (mbhc_status != TABLA_MBHC_STATUS_REL_DETECTION) { + if (priv->mbhc_last_resume && + !time_after(jiffies, priv->mbhc_last_resume + HZ)) { + pr_debug("%s: Button is already released shortly after " + "resume\n", __func__); + n_btn_meas = 0; + } else { + pr_debug("%s: Button is already released without " + "resume", __func__); + sta = tabla_codec_read_sta_result(codec); + stamv_s = tabla_codec_sta_dce_v(codec, 0, sta); + if (vddio) + stamv_s = tabla_scale_v_micb_vddio(priv, + stamv_s, + false); + btn = tabla_determine_button(priv, mv_s); + if (btn != tabla_determine_button(priv, stamv_s)) + btn = -1; + goto done; + } + } + + /* determine pressed button */ + btnmeas[meas++] = tabla_determine_button(priv, mv_s); + pr_debug("%s: meas %d - DCE %d,%d,%d button %d\n", __func__, + meas - 1, dce, mv, mv_s, btnmeas[meas - 1]); + if (n_btn_meas == 0) + btn = btnmeas[0]; + for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) { + dce = tabla_codec_sta_dce(codec, 1, false); + mv = tabla_codec_sta_dce_v(codec, 1, dce); + mv_s = vddio ? tabla_scale_v_micb_vddio(priv, mv, false) : mv; + + btnmeas[meas] = tabla_determine_button(priv, mv_s); + pr_debug("%s: meas %d - DCE %d,%d,%d button %d\n", + __func__, meas, dce, mv, mv_s, btnmeas[meas]); + /* if large enough measurements are collected, + * start to check if last all n_btn_con measurements were + * in same button low/high range */ + if (meas + 1 >= d->n_btn_con) { + for (i = 0; i < d->n_btn_con; i++) + if ((btnmeas[meas] < 0) || + (btnmeas[meas] != btnmeas[meas - i])) + break; + if (i == d->n_btn_con) { + /* button pressed */ + btn = btnmeas[meas]; + break; + } else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) { + /* if left measurements are less than n_btn_con, + * it's impossible to find button number */ + break; + } + } + } + + if (btn >= 0) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO already triggered, ignore button " + "press\n", __func__); + goto done; + } + mask = tabla_get_button_mask(btn); + priv->buttons_pressed |= mask; + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_btn_dwork, + msecs_to_jiffies(400)) == 0) { + WARN(1, "Button pressed twice without release" + "event\n"); + wcd9xxx_unlock_sleep(core); + } + } else { + pr_debug("%s: bogus button press, too short press?\n", + __func__); + } + + done: + pr_debug("%s: leave\n", __func__); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static int tabla_is_fake_press(struct tabla_priv *priv) +{ + int i; + int r = 0; + struct snd_soc_codec *codec = priv->codec; + const int dces = MBHC_NUM_DCE_PLUG_DETECT; + s16 mb_v, v_ins_hu, v_ins_h; + + v_ins_hu = tabla_get_current_v_ins(priv, true); + v_ins_h = tabla_get_current_v_ins(priv, false); + + for (i = 0; i < dces; i++) { + usleep_range(10000, 10000); + if (i == 0) { + mb_v = tabla_codec_sta_dce(codec, 0, true); + pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v, + tabla_codec_sta_dce_v(codec, 0, mb_v)); + if (mb_v < (s16)priv->mbhc_data.v_b1_hu || + mb_v > v_ins_hu) { + r = 1; + break; + } + } else { + mb_v = tabla_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v, + tabla_codec_sta_dce_v(codec, 1, mb_v)); + if (mb_v < (s16)priv->mbhc_data.v_b1_h || + mb_v > v_ins_h) { + r = 1; + break; + } + } + } + + return r; +} + +static irqreturn_t tabla_release_handler(int irq, void *data) +{ + int ret; + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + priv->mbhc_state = MBHC_STATE_RELEASE; + + tabla_codec_drive_v_to_micbias(codec, 10000); + + if (priv->buttons_pressed & TABLA_JACK_BUTTON_MASK) { + ret = tabla_cancel_btn_work(priv); + if (ret == 0) { + pr_debug("%s: Reporting long button release event\n", + __func__); + if (priv->mbhc_cfg.button_jack) + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } else { + if (tabla_is_fake_press(priv)) { + pr_debug("%s: Fake button press interrupt\n", + __func__); + } else if (priv->mbhc_cfg.button_jack) { + if (priv->in_gpio_handler) { + pr_debug("%s: GPIO kicked in, ignore\n", + __func__); + } else { + pr_debug("%s: Reporting short button " + "press and release\n", + __func__); + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, + priv->buttons_pressed, + priv->buttons_pressed); + tabla_snd_soc_jack_report(priv, + priv->mbhc_cfg.button_jack, 0, + priv->buttons_pressed); + } + } + } + + priv->buttons_pressed &= ~TABLA_JACK_BUTTON_MASK; + } + + tabla_codec_calibrate_hs_polling(codec); + + if (priv->mbhc_cfg.gpio) + msleep(TABLA_MBHC_GPIO_REL_DEBOUNCE_TIME_MS); + + tabla_codec_start_hs_polling(codec); + + pr_debug("%s: leave\n", __func__); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static void tabla_codec_shutdown_hs_removal_detect(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(tabla->mbhc_cfg.calibration); + + if (!tabla->mclk_enabled && !tabla->mbhc_polling_active) + tabla_codec_enable_config_mode(codec, 1); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x6, 0x0); + + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x80, 0x00); + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0xA, 0x8); + if (!tabla->mclk_enabled && !tabla->mbhc_polling_active) + tabla_codec_enable_config_mode(codec, 0); + + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x00); +} + +static void tabla_codec_cleanup_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + tabla_codec_shutdown_hs_removal_detect(codec); + + if (!tabla->mclk_enabled) { + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF); + } + + tabla->mbhc_polling_active = false; + tabla->mbhc_state = MBHC_STATE_NONE; +} + +static irqreturn_t tabla_hphl_ocp_irq(int irq, void *data) +{ + struct tabla_priv *tabla = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHL OCP irq\n", __func__); + + if (tabla) { + codec = tabla->codec; + if (tabla->hphlocp_cnt++ < TABLA_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + tabla->hphlocp_cnt = 0; + tabla->hph_status |= SND_JACK_OC_HPHL; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t tabla_hphr_ocp_irq(int irq, void *data) +{ + struct tabla_priv *tabla = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHR OCP irq\n", __func__); + + if (tabla) { + codec = tabla->codec; + if (tabla->hphrocp_cnt++ < TABLA_OCP_ATTEMPT) { + pr_info("%s: retry\n", __func__); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + tabla->hphrocp_cnt = 0; + tabla->hph_status |= SND_JACK_OC_HPHR; + if (tabla->mbhc_cfg.headset_jack) + tabla_snd_soc_jack_report(tabla, + tabla->mbhc_cfg.headset_jack, + tabla->hph_status, + TABLA_JACK_MASK); + } + } else { + pr_err("%s: Bad tabla private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static bool tabla_is_inval_ins_range(struct snd_soc_codec *codec, + s32 mic_volt, bool highhph, bool *highv) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + bool invalid = false; + s16 v_hs_max; + + /* Perform this check only when the high voltage headphone + * needs to be considered as invalid + */ + v_hs_max = tabla_get_current_v_hs_max(tabla); + *highv = mic_volt > v_hs_max; + if (!highhph && *highv) + invalid = true; + else if (mic_volt < tabla->mbhc_data.v_inval_ins_high && + (mic_volt > tabla->mbhc_data.v_inval_ins_low)) + invalid = true; + + return invalid; +} + +static bool tabla_is_inval_ins_delta(struct snd_soc_codec *codec, + int mic_volt, int mic_volt_prev, + int threshold) +{ + return abs(mic_volt - mic_volt_prev) > threshold; +} + +/* called under codec_resource_lock acquisition */ +void tabla_find_plug_and_report(struct snd_soc_codec *codec, + enum tabla_mbhc_plug_type plug_type) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (plug_type == PLUG_TYPE_HEADPHONE && + tabla->current_plug == PLUG_TYPE_NONE) { + /* Nothing was reported previously + * report a headphone or unsupported + */ + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + if (tabla->current_plug == PLUG_TYPE_HEADSET) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + else if (tabla->current_plug == PLUG_TYPE_HEADPHONE) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + + tabla_codec_report_plug(codec, 1, SND_JACK_UNSUPPORTED); + tabla_codec_cleanup_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HEADSET) { + /* If Headphone was reported previously, this will + * only report the mic line + */ + tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET); + msleep(100); + tabla_codec_start_hs_polling(codec); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + if (tabla->current_plug == PLUG_TYPE_NONE) + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + pr_debug("setup mic trigger for further detection\n"); + tabla->lpi_enabled = true; + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + false); + } else { + WARN(1, "Unexpected current plug_type %d, plug_type %d\n", + tabla->current_plug, plug_type); + } +} + +/* should be called under interrupt context that hold suspend */ +static void tabla_schedule_hs_detect_plug(struct tabla_priv *tabla) +{ + pr_debug("%s: scheduling tabla_hs_correct_gpio_plug\n", __func__); + tabla->hs_detect_work_stop = false; + wcd9xxx_lock_sleep(tabla->codec->control_data); + schedule_work(&tabla->hs_correct_plug_work); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_cancel_hs_detect_plug(struct tabla_priv *tabla) +{ + pr_debug("%s: canceling hs_correct_plug_work\n", __func__); + tabla->hs_detect_work_stop = true; + wmb(); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + if (cancel_work_sync(&tabla->hs_correct_plug_work)) { + pr_debug("%s: hs_correct_plug_work is canceled\n", __func__); + wcd9xxx_unlock_sleep(tabla->codec->control_data); + } + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); +} + +static bool tabla_hs_gpio_level_remove(struct tabla_priv *tabla) +{ + return (gpio_get_value_cansleep(tabla->mbhc_cfg.gpio) != + tabla->mbhc_cfg.gpio_level_insert); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_hphr_gnd_switch(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, on); + if (on) + usleep_range(5000, 5000); +} + +/* called under codec_resource_lock acquisition and mbhc override = 1 */ +static enum tabla_mbhc_plug_type +tabla_codec_get_plug_type(struct snd_soc_codec *codec, bool highhph) +{ + int i; + bool gndswitch, vddioswitch; + int scaled; + struct tabla_mbhc_plug_type_cfg *plug_type_ptr; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const bool vddio = (tabla->mbhc_data.micb_mv != VDDIO_MICBIAS_MV); + int num_det = (MBHC_NUM_DCE_PLUG_DETECT + vddio); + enum tabla_mbhc_plug_type plug_type[num_det]; + s16 mb_v[num_det]; + s32 mic_mv[num_det]; + bool inval; + bool highdelta; + bool ahighv = false, highv; + + /* make sure override is on */ + WARN_ON(!(snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x04)); + + /* GND and MIC swap detection requires at least 2 rounds of DCE */ + BUG_ON(num_det < 2); + + plug_type_ptr = + TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + + plug_type[0] = PLUG_TYPE_INVALID; + + /* performs DCEs for N times + * 1st: check if voltage is in invalid range + * 2nd - N-2nd: check voltage range and delta + * N-1st: check voltage range, delta with HPHR GND switch + * Nth: check voltage range with VDDIO switch if micbias V != vddio V*/ + for (i = 0; i < num_det; i++) { + gndswitch = (i == (num_det - 1 - vddio)); + vddioswitch = (vddio && ((i == num_det - 1) || + (i == num_det - 2))); + if (i == 0) { + mb_v[i] = tabla_codec_setup_hs_polling(codec); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + inval = tabla_is_inval_ins_range(codec, mic_mv[i], + highhph, &highv); + ahighv |= highv; + scaled = mic_mv[i]; + } else { + if (vddioswitch) + __tabla_codec_switch_micbias(tabla->codec, 1, + false, false); + if (gndswitch) + tabla_codec_hphr_gnd_switch(codec, true); + mb_v[i] = __tabla_codec_sta_dce(codec, 1, true, true); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + if (vddioswitch) + scaled = tabla_scale_v_micb_vddio(tabla, + mic_mv[i], + false); + else + scaled = mic_mv[i]; + /* !gndswitch & vddioswitch means the previous DCE + * was done with gndswitch, don't compare with DCE + * with gndswitch */ + highdelta = tabla_is_inval_ins_delta(codec, scaled, + mic_mv[i - !gndswitch - vddioswitch], + TABLA_MBHC_FAKE_INS_DELTA_SCALED_MV); + inval = (tabla_is_inval_ins_range(codec, mic_mv[i], + highhph, &highv) || + highdelta); + ahighv |= highv; + if (gndswitch) + tabla_codec_hphr_gnd_switch(codec, false); + if (vddioswitch) + __tabla_codec_switch_micbias(tabla->codec, 0, + false, false); + /* claim UNSUPPORTED plug insertion when + * good headset is detected but HPHR GND switch makes + * delta difference */ + if (i == (num_det - 2) && highdelta && !ahighv) + plug_type[0] = PLUG_TYPE_GND_MIC_SWAP; + else if (i == (num_det - 1) && inval) + plug_type[0] = PLUG_TYPE_INVALID; + } + pr_debug("%s: DCE #%d, %04x, V %d, scaled V %d, GND %d, " + "VDDIO %d, inval %d\n", __func__, + i + 1, mb_v[i] & 0xffff, mic_mv[i], scaled, gndswitch, + vddioswitch, inval); + /* don't need to run further DCEs */ + if (ahighv && inval) + break; + mic_mv[i] = scaled; + } + + for (i = 0; (plug_type[0] != PLUG_TYPE_GND_MIC_SWAP && !inval) && + i < num_det; i++) { + /* + * If we are here, means none of the all + * measurements are fake, continue plug type detection. + * If all three measurements do not produce same + * plug type, restart insertion detection + */ + if (mic_mv[i] < plug_type_ptr->v_no_mic) { + plug_type[i] = PLUG_TYPE_HEADPHONE; + pr_debug("%s: Detect attempt %d, detected Headphone\n", + __func__, i); + } else if (highhph && (mic_mv[i] > plug_type_ptr->v_hs_max)) { + plug_type[i] = PLUG_TYPE_HIGH_HPH; + pr_debug("%s: Detect attempt %d, detected High " + "Headphone\n", __func__, i); + } else { + plug_type[i] = PLUG_TYPE_HEADSET; + pr_debug("%s: Detect attempt %d, detected Headset\n", + __func__, i); + } + + if (i > 0 && (plug_type[i - 1] != plug_type[i])) { + pr_err("%s: Detect attempt %d and %d are not same", + __func__, i - 1, i); + plug_type[0] = PLUG_TYPE_INVALID; + inval = true; + break; + } + } + + pr_debug("%s: Detected plug type %d\n", __func__, plug_type[0]); + return plug_type[0]; +} + +static void tabla_hs_correct_gpio_plug(struct work_struct *work) +{ + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + int retry = 0, pt_gnd_mic_swap_cnt = 0; + bool correction = false; + enum tabla_mbhc_plug_type plug_type; + unsigned long timeout; + + tabla = container_of(work, struct tabla_priv, hs_correct_plug_work); + codec = tabla->codec; + + pr_debug("%s: enter\n", __func__); + tabla->mbhc_cfg.mclk_cb_fn(codec, 1, false); + + /* Keep override on during entire plug type correction work. + * + * This is okay under the assumption that any GPIO irqs which use + * MBHC block cancel and sync this work so override is off again + * prior to GPIO interrupt handler's MBHC block usage. + * Also while this correction work is running, we can guarantee + * DAPM doesn't use any MBHC block as this work only runs with + * headphone detection. + */ + tabla_turn_onoff_override(codec, true); + + timeout = jiffies + msecs_to_jiffies(TABLA_HS_DETECT_PLUG_TIME_MS); + while (!time_after(jiffies, timeout)) { + ++retry; + rmb(); + if (tabla->hs_detect_work_stop) { + pr_debug("%s: stop requested\n", __func__); + break; + } + + msleep(TABLA_HS_DETECT_PLUG_INERVAL_MS); + if (tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO value is low\n", __func__); + break; + } + + /* can race with removal interrupt */ + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + plug_type = tabla_codec_get_plug_type(codec, true); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + + if (plug_type == PLUG_TYPE_INVALID) { + pr_debug("Invalid plug in attempt # %d\n", retry); + if (retry == NUM_ATTEMPTS_TO_REPORT && + tabla->current_plug == PLUG_TYPE_NONE) { + tabla_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + pr_debug("Good headphone detected, continue polling mic\n"); + if (tabla->current_plug == PLUG_TYPE_NONE) + tabla_codec_report_plug(codec, 1, + SND_JACK_HEADPHONE); + } else { + if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + pt_gnd_mic_swap_cnt++; + if (pt_gnd_mic_swap_cnt < + TABLA_MBHC_GND_MIC_SWAP_THRESHOLD) + continue; + else if (pt_gnd_mic_swap_cnt > + TABLA_MBHC_GND_MIC_SWAP_THRESHOLD) { + /* This is due to GND/MIC switch didn't + * work, Report unsupported plug */ + } else if (tabla->mbhc_cfg.swap_gnd_mic) { + /* if switch is toggled, check again, + * otherwise report unsupported plug */ + if (tabla->mbhc_cfg.swap_gnd_mic(codec)) + continue; + } + } else + pt_gnd_mic_swap_cnt = 0; + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + /* Turn off override */ + tabla_turn_onoff_override(codec, false); + /* The valid plug also includes PLUG_TYPE_GND_MIC_SWAP + */ + tabla_find_plug_and_report(codec, plug_type); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("Attempt %d found correct plug %d\n", retry, + plug_type); + correction = true; + break; + } + } + + /* Turn off override */ + if (!correction) + tabla_turn_onoff_override(codec, false); + + tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false); + pr_debug("%s: leave\n", __func__); + /* unlock sleep */ + wcd9xxx_unlock_sleep(tabla->codec->control_data); +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_decide_gpio_plug(struct snd_soc_codec *codec) +{ + enum tabla_mbhc_plug_type plug_type; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s: enter\n", __func__); + + tabla_turn_onoff_override(codec, true); + plug_type = tabla_codec_get_plug_type(codec, true); + tabla_turn_onoff_override(codec, false); + + if (tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO value is low when determining plug\n", + __func__); + return; + } + + if (plug_type == PLUG_TYPE_INVALID || + plug_type == PLUG_TYPE_GND_MIC_SWAP) { + tabla_schedule_hs_detect_plug(tabla); + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + + tabla_schedule_hs_detect_plug(tabla); + } else { + pr_debug("%s: Valid plug found, determine plug type %d\n", + __func__, plug_type); + tabla_find_plug_and_report(codec, plug_type); + } +} + +/* called under codec_resource_lock acquisition */ +static void tabla_codec_detect_plug_type(struct snd_soc_codec *codec) +{ + enum tabla_mbhc_plug_type plug_type; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_plug_detect_cfg *plug_det = + TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration); + + /* Turn on the override, + * tabla_codec_setup_hs_polling requires override on */ + tabla_turn_onoff_override(codec, true); + + if (plug_det->t_ins_complete > 20) + msleep(plug_det->t_ins_complete); + else + usleep_range(plug_det->t_ins_complete * 1000, + plug_det->t_ins_complete * 1000); + + if (tabla->mbhc_cfg.gpio) { + /* Turn off the override */ + tabla_turn_onoff_override(codec, false); + if (tabla_hs_gpio_level_remove(tabla)) + pr_debug("%s: GPIO value is low when determining " + "plug\n", __func__); + else + tabla_codec_decide_gpio_plug(codec); + return; + } + + plug_type = tabla_codec_get_plug_type(codec, false); + tabla_turn_onoff_override(codec, false); + + if (plug_type == PLUG_TYPE_INVALID) { + pr_debug("%s: Invalid plug type detected\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, false); + } else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + pr_debug("%s: GND-MIC swapped plug type detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_UNSUPPORTED); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 0, 0, false); + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + pr_debug("%s: Headphone Detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 0, 0, false); + } else if (plug_type == PLUG_TYPE_HEADSET) { + pr_debug("%s: Headset detected\n", __func__); + tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET); + + /* avoid false button press detect */ + msleep(50); + tabla_codec_start_hs_polling(codec); + } +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_insert_irq_gpio(struct tabla_priv *priv, bool is_removal) +{ + struct snd_soc_codec *codec = priv->codec; + + if (!is_removal) { + pr_debug("%s: MIC trigger insertion interrupt\n", __func__); + + rmb(); + if (priv->lpi_enabled) + msleep(100); + + rmb(); + if (!priv->lpi_enabled) { + pr_debug("%s: lpi is disabled\n", __func__); + } else if (gpio_get_value_cansleep(priv->mbhc_cfg.gpio) == + priv->mbhc_cfg.gpio_level_insert) { + pr_debug("%s: Valid insertion, " + "detect plug type\n", __func__); + tabla_codec_decide_gpio_plug(codec); + } else { + pr_debug("%s: Invalid insertion, " + "stop plug detection\n", __func__); + } + } else { + pr_err("%s: GPIO used, invalid MBHC Removal\n", __func__); + } +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_insert_irq_nogpio(struct tabla_priv *priv, bool is_removal, + bool is_mb_trigger) +{ + int ret; + struct snd_soc_codec *codec = priv->codec; + struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent); + + if (is_removal) { + /* cancel possiblely running hs detect work */ + tabla_cancel_hs_detect_plug(priv); + + /* + * If headphone is removed while playback is in progress, + * it is possible that micbias will be switched to VDDIO. + */ + tabla_codec_switch_micbias(codec, 0); + if (priv->current_plug == PLUG_TYPE_HEADPHONE) + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + else if (priv->current_plug == PLUG_TYPE_GND_MIC_SWAP) + tabla_codec_report_plug(codec, 0, SND_JACK_UNSUPPORTED); + else + WARN(1, "%s: Unexpected current plug type %d\n", + __func__, priv->current_plug); + tabla_codec_shutdown_hs_removal_detect(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + true); + } else if (is_mb_trigger && !is_removal) { + pr_debug("%s: Waiting for Headphone left trigger\n", + __func__); + wcd9xxx_lock_sleep(core); + if (schedule_delayed_work(&priv->mbhc_insert_dwork, + usecs_to_jiffies(1000000)) == 0) { + pr_err("%s: mbhc_insert_dwork is already scheduled\n", + __func__); + wcd9xxx_unlock_sleep(core); + } + tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_HPHL_TRIGGER, + false); + } else { + ret = cancel_delayed_work(&priv->mbhc_insert_dwork); + if (ret != 0) { + pr_debug("%s: Complete plug insertion, Detecting plug " + "type\n", __func__); + tabla_codec_detect_plug_type(codec); + wcd9xxx_unlock_sleep(core); + } else { + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_MBHC_INSERTION); + pr_err("%s: Error detecting plug insertion\n", + __func__); + } + } +} + +static irqreturn_t tabla_hs_insert_irq(int irq, void *data) +{ + bool is_mb_trigger, is_removal; + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + pr_debug("%s: enter\n", __func__); + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + is_mb_trigger = !!(snd_soc_read(codec, priv->mbhc_bias_regs.mbhc_reg) & + 0x10); + is_removal = !!(snd_soc_read(codec, TABLA_A_CDC_MBHC_INT_CTL) & 0x02); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x03, 0x00); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, priv->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + + if (priv->mbhc_cfg.gpio) + tabla_hs_insert_irq_gpio(priv, is_removal); + else + tabla_hs_insert_irq_nogpio(priv, is_removal, is_mb_trigger); + + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + return IRQ_HANDLED; +} + +static bool is_valid_mic_voltage(struct snd_soc_codec *codec, s32 mic_mv) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const struct tabla_mbhc_plug_type_cfg *plug_type = + TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla->mbhc_cfg.calibration); + const s16 v_hs_max = tabla_get_current_v_hs_max(tabla); + + return (!(mic_mv > 10 && mic_mv < 80) && (mic_mv > plug_type->v_no_mic) + && (mic_mv < v_hs_max)) ? true : false; +} + +/* called under codec_resource_lock acquisition + * returns true if mic voltage range is back to normal insertion + * returns false either if timedout or removed */ +static bool tabla_hs_remove_settle(struct snd_soc_codec *codec) +{ + int i; + bool timedout, settled = false; + s32 mic_mv[MBHC_NUM_DCE_PLUG_DETECT]; + short mb_v[MBHC_NUM_DCE_PLUG_DETECT]; + unsigned long retry = 0, timeout; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + const s16 v_hs_max = tabla_get_current_v_hs_max(tabla); + + timeout = jiffies + msecs_to_jiffies(TABLA_HS_DETECT_PLUG_TIME_MS); + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (tabla->mbhc_cfg.gpio) { + if (retry > 1) + msleep(250); + else + msleep(50); + } + + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = tabla_codec_sta_dce(codec, 1, true); + mic_mv[i] = tabla_codec_sta_dce_v(codec, 1 , mb_v[i]); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + + if (tabla->mbhc_cfg.gpio && tabla_hs_gpio_level_remove(tabla)) { + pr_debug("%s: GPIO indicates removal\n", __func__); + break; + } + + if (tabla->current_plug == PLUG_TYPE_NONE) { + pr_debug("%s : headset/headphone is removed\n", + __func__); + break; + } + + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (!is_valid_mic_voltage(codec, mic_mv[i])) + break; + + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: MIC voltage settled\n", __func__); + settled = true; + msleep(200); + break; + } + + /* only for non-GPIO remove irq */ + if (!tabla->mbhc_cfg.gpio) { + for (i = 0; i < MBHC_NUM_DCE_PLUG_DETECT; i++) + if (mic_mv[i] < v_hs_max) + break; + if (i == MBHC_NUM_DCE_PLUG_DETECT) { + pr_debug("%s: Headset is removed\n", __func__); + break; + } + } + } + + if (timedout) + pr_debug("%s: Microphone did not settle in %d seconds\n", + __func__, TABLA_HS_DETECT_PLUG_TIME_MS); + return settled; +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_remove_irq_gpio(struct tabla_priv *priv) +{ + struct snd_soc_codec *codec = priv->codec; + + if (tabla_hs_remove_settle(codec)) + tabla_codec_start_hs_polling(codec); + pr_debug("%s: remove settle done\n", __func__); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void tabla_hs_remove_irq_nogpio(struct tabla_priv *priv) +{ + short bias_value; + bool removed = true; + struct snd_soc_codec *codec = priv->codec; + const struct tabla_mbhc_general_cfg *generic = + TABLA_MBHC_CAL_GENERAL_PTR(priv->mbhc_cfg.calibration); + int min_us = TABLA_FAKE_REMOVAL_MIN_PERIOD_MS * 1000; + + if (priv->current_plug != PLUG_TYPE_HEADSET) { + pr_debug("%s(): Headset is not inserted, ignore removal\n", + __func__); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + return; + } + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem); + + do { + bias_value = tabla_codec_sta_dce(codec, 1, true); + pr_debug("%s: DCE %d,%d, %d us left\n", __func__, bias_value, + tabla_codec_sta_dce_v(codec, 1, bias_value), min_us); + if (bias_value < tabla_get_current_v_ins(priv, false)) { + pr_debug("%s: checking false removal\n", __func__); + msleep(500); + removed = !tabla_hs_remove_settle(codec); + pr_debug("%s: headset %sactually removed\n", __func__, + removed ? "" : "not "); + break; + } + min_us -= priv->mbhc_data.t_dce; + } while (min_us > 0); + + if (removed) { + /* cancel possiblely running hs detect work */ + tabla_cancel_hs_detect_plug(priv); + /* + * If this removal is not false, first check the micbias + * switch status and switch it to LDOH if it is already + * switched to VDDIO. + */ + tabla_codec_switch_micbias(codec, 0); + + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + true); + } else { + tabla_codec_start_hs_polling(codec); + } +} + +static irqreturn_t tabla_hs_remove_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + bool vddio; + pr_debug("%s: enter, removal interrupt\n", __func__); + + TABLA_ACQUIRE_LOCK(priv->codec_resource_lock); + vddio = (priv->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + priv->mbhc_micbias_switched); + if (vddio) + __tabla_codec_switch_micbias(priv->codec, 0, false, true); + + if (priv->mbhc_cfg.gpio) + tabla_hs_remove_irq_gpio(priv); + else + tabla_hs_remove_irq_nogpio(priv); + + /* if driver turned off vddio switch and headset is not removed, + * turn on the vddio switch back, if headset is removed then vddio + * switch is off by time now and shouldn't be turn on again from here */ + if (vddio && priv->current_plug == PLUG_TYPE_HEADSET) + __tabla_codec_switch_micbias(priv->codec, 1, true, true); + TABLA_RELEASE_LOCK(priv->codec_resource_lock); + + return IRQ_HANDLED; +} + +void mbhc_insert_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + struct wcd9xxx *tabla_core; + + dwork = to_delayed_work(work); + tabla = container_of(dwork, struct tabla_priv, mbhc_insert_dwork); + codec = tabla->codec; + tabla_core = dev_get_drvdata(codec->dev->parent); + + pr_debug("%s:\n", __func__); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + wcd9xxx_disable_irq_sync(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + tabla_codec_detect_plug_type(codec); + wcd9xxx_unlock_sleep(tabla_core); +} + +static void tabla_hs_gpio_handler(struct snd_soc_codec *codec) +{ + bool insert; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + bool is_removed = false; + + pr_debug("%s: enter\n", __func__); + + tabla->in_gpio_handler = true; + /* Wait here for debounce time */ + usleep_range(TABLA_GPIO_IRQ_DEBOUNCE_TIME_US, + TABLA_GPIO_IRQ_DEBOUNCE_TIME_US); + + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + + /* cancel pending button press */ + if (tabla_cancel_btn_work(tabla)) + pr_debug("%s: button press is canceled\n", __func__); + + insert = (gpio_get_value_cansleep(tabla->mbhc_cfg.gpio) == + tabla->mbhc_cfg.gpio_level_insert); + if ((tabla->current_plug == PLUG_TYPE_NONE) && insert) { + tabla->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + tabla_cancel_hs_detect_plug(tabla); + + /* Disable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, 0x00); + tabla_codec_detect_plug_type(codec); + } else if ((tabla->current_plug != PLUG_TYPE_NONE) && !insert) { + tabla->lpi_enabled = false; + wmb(); + + /* cancel detect plug */ + tabla_cancel_hs_detect_plug(tabla); + + if (tabla->current_plug == PLUG_TYPE_HEADPHONE) { + tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE); + is_removed = true; + } else if (tabla->current_plug == PLUG_TYPE_GND_MIC_SWAP) { + tabla_codec_report_plug(codec, 0, SND_JACK_UNSUPPORTED); + is_removed = true; + } else if (tabla->current_plug == PLUG_TYPE_HEADSET) { + tabla_codec_pause_hs_polling(codec); + tabla_codec_cleanup_hs_polling(codec); + tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET); + is_removed = true; + } + + if (is_removed) { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, 0x01, + 0x01); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, + 0x01); + /* Make sure mic trigger is turned off */ + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, + tabla->mbhc_bias_regs.mbhc_reg, + 0x90, 0x00); + /* Reset MBHC State Machine */ + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, + 0x08, 0x00); + /* Turn off override */ + tabla_turn_onoff_override(codec, false); + } + } + + tabla->in_gpio_handler = false; + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + pr_debug("%s: leave\n", __func__); +} + +static irqreturn_t tabla_mechanical_plug_detect_irq(int irq, void *data) +{ + int r = IRQ_HANDLED; + struct snd_soc_codec *codec = data; + + if (unlikely(wcd9xxx_lock_sleep(codec->control_data) == false)) { + pr_warn("%s: failed to hold suspend\n", __func__); + r = IRQ_NONE; + } else { + tabla_hs_gpio_handler(codec); + wcd9xxx_unlock_sleep(codec->control_data); + } + + return r; +} + +static int tabla_mbhc_init_and_calibrate(struct tabla_priv *tabla) +{ + int ret = 0; + struct snd_soc_codec *codec = tabla->codec; + + tabla->mbhc_cfg.mclk_cb_fn(codec, 1, false); + tabla_mbhc_init(codec); + tabla_mbhc_cal(codec); + tabla_mbhc_calc_thres(codec); + tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false); + tabla_codec_calibrate_hs_polling(codec); + if (!tabla->mbhc_cfg.gpio) { + ret = tabla_codec_enable_hs_detect(codec, 1, + MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + false); + + if (IS_ERR_VALUE(ret)) + pr_err("%s: Failed to setup MBHC detection\n", + __func__); + } else { + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x01, 0x01); + INIT_WORK(&tabla->hs_correct_plug_work, + tabla_hs_correct_gpio_plug); + } + + if (!IS_ERR_VALUE(ret)) { + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10, 0x10); + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + wcd9xxx_enable_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + + if (tabla->mbhc_cfg.gpio) { + ret = request_threaded_irq(tabla->mbhc_cfg.gpio_irq, + NULL, + tabla_mechanical_plug_detect_irq, + (IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING), + "tabla-gpio", codec); + if (!IS_ERR_VALUE(ret)) { + ret = enable_irq_wake(tabla->mbhc_cfg.gpio_irq); + /* Bootup time detection */ + tabla_hs_gpio_handler(codec); + } + } + } + + return ret; +} + +static void mbhc_fw_read(struct work_struct *work) +{ + struct delayed_work *dwork; + struct tabla_priv *tabla; + struct snd_soc_codec *codec; + const struct firmware *fw; + int ret = -1, retry = 0; + + dwork = to_delayed_work(work); + tabla = container_of(dwork, struct tabla_priv, mbhc_firmware_dwork); + codec = tabla->codec; + + while (retry < MBHC_FW_READ_ATTEMPTS) { + retry++; + pr_info("%s:Attempt %d to request MBHC firmware\n", + __func__, retry); + ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin", + codec->dev); + + if (ret != 0) { + usleep_range(MBHC_FW_READ_TIMEOUT, + MBHC_FW_READ_TIMEOUT); + } else { + pr_info("%s: MBHC Firmware read succesful\n", __func__); + break; + } + } + + if (ret != 0) { + pr_err("%s: Cannot load MBHC firmware use default cal\n", + __func__); + } else if (tabla_mbhc_fw_validate(fw) == false) { + pr_err("%s: Invalid MBHC cal data size use default cal\n", + __func__); + release_firmware(fw); + } else { + tabla->mbhc_cfg.calibration = (void *)fw->data; + tabla->mbhc_fw = fw; + } + + (void) tabla_mbhc_init_and_calibrate(tabla); +} + +int tabla_hs_detect(struct snd_soc_codec *codec, + const struct tabla_mbhc_config *cfg) +{ + struct tabla_priv *tabla; + int rc = 0; + + if (!codec || !cfg->calibration) { + pr_err("Error: no codec or calibration\n"); + return -EINVAL; + } + + if (cfg->mclk_rate != TABLA_MCLK_RATE_12288KHZ) { + if (cfg->mclk_rate == TABLA_MCLK_RATE_9600KHZ) + pr_err("Error: clock rate %dHz is not yet supported\n", + cfg->mclk_rate); + else + pr_err("Error: unsupported clock rate %d\n", + cfg->mclk_rate); + return -EINVAL; + } + + tabla = snd_soc_codec_get_drvdata(codec); + tabla->mbhc_cfg = *cfg; + tabla->in_gpio_handler = false; + tabla->current_plug = PLUG_TYPE_NONE; + tabla->lpi_enabled = false; + tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs); + + /* Put CFILT in fast mode by default */ + snd_soc_update_bits(codec, tabla->mbhc_bias_regs.cfilt_ctl, + 0x40, TABLA_CFILT_FAST_MODE); + INIT_DELAYED_WORK(&tabla->mbhc_firmware_dwork, mbhc_fw_read); + INIT_DELAYED_WORK(&tabla->mbhc_btn_dwork, btn_lpress_fn); + INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report); + INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report); + INIT_DELAYED_WORK(&tabla->mbhc_insert_dwork, mbhc_insert_work); + + if (!tabla->mbhc_cfg.read_fw_bin) + rc = tabla_mbhc_init_and_calibrate(tabla); + else + schedule_delayed_work(&tabla->mbhc_firmware_dwork, + usecs_to_jiffies(MBHC_FW_READ_TIMEOUT)); + + return rc; +} +EXPORT_SYMBOL_GPL(tabla_hs_detect); + +static unsigned long slimbus_value; + +static irqreturn_t tabla_slimbus_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + int i, j; + u8 val; + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) { + slimbus_value = wcd9xxx_interface_reg_read(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_STATUS0 + i); + for_each_set_bit(j, &slimbus_value, BITS_PER_BYTE) { + val = wcd9xxx_interface_reg_read(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_SOURCE0 + i*8 + j); + if (val & 0x1) + pr_err_ratelimited("overflow error on port %x," + " value %x\n", i*8 + j, val); + if (val & 0x2) + pr_err_ratelimited("underflow error on port %x," + " value %x\n", i*8 + j, val); + } + wcd9xxx_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); + } + + return IRQ_HANDLED; +} + +static int tabla_handle_pdata(struct tabla_priv *tabla) +{ + struct snd_soc_codec *codec = tabla->codec; + struct wcd9xxx_pdata *pdata = tabla->pdata; + int k1, k2, k3, rc = 0; + u8 leg_mode = pdata->amic_settings.legacy_mode; + u8 txfe_bypass = pdata->amic_settings.txfe_enable; + u8 txfe_buff = pdata->amic_settings.txfe_buff; + u8 flag = pdata->amic_settings.use_pdata; + u8 i = 0, j = 0; + u8 val_txfe = 0, value = 0; + + if (!pdata) { + rc = -ENODEV; + goto done; + } + + /* Make sure settings are correct */ + if ((pdata->micbias.ldoh_v > TABLA_LDOH_2P85_V) || + (pdata->micbias.bias1_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias2_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias3_cfilt_sel > TABLA_CFILT3_SEL) || + (pdata->micbias.bias4_cfilt_sel > TABLA_CFILT3_SEL)) { + rc = -EINVAL; + goto done; + } + + /* figure out k value */ + k1 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt1_mv); + k2 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt2_mv); + k3 = tabla_find_k_value(pdata->micbias.ldoh_v, + pdata->micbias.cfilt3_mv); + + if (IS_ERR_VALUE(k1) || IS_ERR_VALUE(k2) || IS_ERR_VALUE(k3)) { + rc = -EINVAL; + goto done; + } + + /* Set voltage level and always use LDO */ + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x0C, + (pdata->micbias.ldoh_v << 2)); + + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_1_VAL, 0xFC, + (k1 << 2)); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_2_VAL, 0xFC, + (k2 << 2)); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_3_VAL, 0xFC, + (k3 << 2)); + + snd_soc_update_bits(codec, TABLA_A_MICB_1_CTL, 0x60, + (pdata->micbias.bias1_cfilt_sel << 5)); + snd_soc_update_bits(codec, TABLA_A_MICB_2_CTL, 0x60, + (pdata->micbias.bias2_cfilt_sel << 5)); + snd_soc_update_bits(codec, TABLA_A_MICB_3_CTL, 0x60, + (pdata->micbias.bias3_cfilt_sel << 5)); + snd_soc_update_bits(codec, tabla->reg_addr.micb_4_ctl, 0x60, + (pdata->micbias.bias4_cfilt_sel << 5)); + + for (i = 0; i < 6; j++, i += 2) { + if (flag & (0x01 << i)) { + value = (leg_mode & (0x01 << i)) ? 0x10 : 0x00; + val_txfe = (txfe_bypass & (0x01 << i)) ? 0x20 : 0x00; + val_txfe = val_txfe | + ((txfe_buff & (0x01 << i)) ? 0x10 : 0x00); + snd_soc_update_bits(codec, TABLA_A_TX_1_2_EN + j * 10, + 0x10, value); + snd_soc_update_bits(codec, + TABLA_A_TX_1_2_TEST_EN + j * 10, + 0x30, val_txfe); + } + if (flag & (0x01 << (i + 1))) { + value = (leg_mode & (0x01 << (i + 1))) ? 0x01 : 0x00; + val_txfe = (txfe_bypass & + (0x01 << (i + 1))) ? 0x02 : 0x00; + val_txfe |= (txfe_buff & + (0x01 << (i + 1))) ? 0x01 : 0x00; + snd_soc_update_bits(codec, TABLA_A_TX_1_2_EN + j * 10, + 0x01, value); + snd_soc_update_bits(codec, + TABLA_A_TX_1_2_TEST_EN + j * 10, + 0x03, val_txfe); + } + } + if (flag & 0x40) { + value = (leg_mode & 0x40) ? 0x10 : 0x00; + value = value | ((txfe_bypass & 0x40) ? 0x02 : 0x00); + value = value | ((txfe_buff & 0x40) ? 0x01 : 0x00); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, + 0x13, value); + } + + if (pdata->ocp.use_pdata) { + /* not defined in CODEC specification */ + if (pdata->ocp.hph_ocp_limit == 1 || + pdata->ocp.hph_ocp_limit == 5) { + rc = -EINVAL; + goto done; + } + snd_soc_update_bits(codec, TABLA_A_RX_COM_OCP_CTL, + 0x0F, pdata->ocp.num_attempts); + snd_soc_write(codec, TABLA_A_RX_COM_OCP_COUNT, + ((pdata->ocp.run_time << 4) | pdata->ocp.wait_time)); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, + 0xE0, (pdata->ocp.hph_ocp_limit << 5)); + } + + for (i = 0; i < ARRAY_SIZE(pdata->regulator); i++) { + if (!strncmp(pdata->regulator[i].name, "CDC_VDDA_RX", 11)) { + if (pdata->regulator[i].min_uV == 1800000 && + pdata->regulator[i].max_uV == 1800000) { + snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, + 0x1C); + } else if (pdata->regulator[i].min_uV == 2200000 && + pdata->regulator[i].max_uV == 2200000) { + snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, + 0x1E); + } else { + pr_err("%s: unsupported CDC_VDDA_RX voltage " + "min %d, max %d\n", __func__, + pdata->regulator[i].min_uV, + pdata->regulator[i].max_uV); + rc = -EINVAL; + } + break; + } + } +done: + return rc; +} + +static const struct tabla_reg_mask_val tabla_1_1_reg_defaults[] = { + + /* Tabla 1.1 MICBIAS changes */ + TABLA_REG_VAL(TABLA_A_MICB_1_INT_RBIAS, 0x24), + TABLA_REG_VAL(TABLA_A_MICB_2_INT_RBIAS, 0x24), + TABLA_REG_VAL(TABLA_A_MICB_3_INT_RBIAS, 0x24), + + /* Tabla 1.1 HPH changes */ + TABLA_REG_VAL(TABLA_A_RX_HPH_BIAS_PA, 0x57), + TABLA_REG_VAL(TABLA_A_RX_HPH_BIAS_LDO, 0x56), + + /* Tabla 1.1 EAR PA changes */ + TABLA_REG_VAL(TABLA_A_RX_EAR_BIAS_PA, 0xA6), + TABLA_REG_VAL(TABLA_A_RX_EAR_GAIN, 0x02), + TABLA_REG_VAL(TABLA_A_RX_EAR_VCM, 0x03), + + /* Tabla 1.1 Lineout_5 Changes */ + TABLA_REG_VAL(TABLA_A_RX_LINE_5_GAIN, 0x10), + + /* Tabla 1.1 RX Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX1_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX2_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX3_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX4_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX5_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX6_B5_CTL, 0x78), + TABLA_REG_VAL(TABLA_A_CDC_RX7_B5_CTL, 0x78), + + /* Tabla 1.1 RX1 and RX2 Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX1_B6_CTL, 0xA0), + TABLA_REG_VAL(TABLA_A_CDC_RX2_B6_CTL, 0xA0), + + /* Tabla 1.1 RX3 to RX7 Changes */ + TABLA_REG_VAL(TABLA_A_CDC_RX3_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX4_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX5_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX6_B6_CTL, 0x80), + TABLA_REG_VAL(TABLA_A_CDC_RX7_B6_CTL, 0x80), + + /* Tabla 1.1 CLASSG Changes */ + TABLA_REG_VAL(TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL, 0x1B), +}; + +static const struct tabla_reg_mask_val tabla_2_0_reg_defaults[] = { + /* Tabla 2.0 MICBIAS changes */ + TABLA_REG_VAL(TABLA_A_MICB_2_MBHC, 0x02), +}; + +static const struct tabla_reg_mask_val tabla_1_x_only_reg_2_0_defaults[] = { + TABLA_REG_VAL(TABLA_1_A_MICB_4_INT_RBIAS, 0x24), +}; + +static const struct tabla_reg_mask_val tabla_2_only_reg_2_0_defaults[] = { + TABLA_REG_VAL(TABLA_2_A_MICB_4_INT_RBIAS, 0x24), +}; + +static void tabla_update_reg_defaults(struct snd_soc_codec *codec) +{ + u32 i; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + for (i = 0; i < ARRAY_SIZE(tabla_1_1_reg_defaults); i++) + snd_soc_write(codec, tabla_1_1_reg_defaults[i].reg, + tabla_1_1_reg_defaults[i].val); + + for (i = 0; i < ARRAY_SIZE(tabla_2_0_reg_defaults); i++) + snd_soc_write(codec, tabla_2_0_reg_defaults[i].reg, + tabla_2_0_reg_defaults[i].val); + + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_x_only_reg_2_0_defaults); + i++) + snd_soc_write(codec, + tabla_1_x_only_reg_2_0_defaults[i].reg, + tabla_1_x_only_reg_2_0_defaults[i].val); + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_only_reg_2_0_defaults); i++) + snd_soc_write(codec, + tabla_2_only_reg_2_0_defaults[i].reg, + tabla_2_only_reg_2_0_defaults[i].val); + } +} + +static const struct tabla_reg_mask_val tabla_codec_reg_init_val[] = { + /* Initialize current threshold to 350MA + * number of wait and run cycles to 4096 + */ + {TABLA_A_RX_HPH_OCP_CTL, 0xE0, 0x60}, + {TABLA_A_RX_COM_OCP_COUNT, 0xFF, 0xFF}, + + {TABLA_A_QFUSE_CTL, 0xFF, 0x03}, + + /* Initialize gain registers to use register gain */ + {TABLA_A_RX_HPH_L_GAIN, 0x10, 0x10}, + {TABLA_A_RX_HPH_R_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_1_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_2_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_3_GAIN, 0x10, 0x10}, + {TABLA_A_RX_LINE_4_GAIN, 0x10, 0x10}, + + /* Initialize mic biases to differential mode */ + {TABLA_A_MICB_1_INT_RBIAS, 0x24, 0x24}, + {TABLA_A_MICB_2_INT_RBIAS, 0x24, 0x24}, + {TABLA_A_MICB_3_INT_RBIAS, 0x24, 0x24}, + + {TABLA_A_CDC_CONN_CLSG_CTL, 0x3C, 0x14}, + + /* Use 16 bit sample size for TX1 to TX6 */ + {TABLA_A_CDC_CONN_TX_SB_B1_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B2_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B3_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B4_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B5_CTL, 0x30, 0x20}, + {TABLA_A_CDC_CONN_TX_SB_B6_CTL, 0x30, 0x20}, + + /* Use 16 bit sample size for TX7 to TX10 */ + {TABLA_A_CDC_CONN_TX_SB_B7_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B8_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B9_CTL, 0x60, 0x40}, + {TABLA_A_CDC_CONN_TX_SB_B10_CTL, 0x60, 0x40}, + + /* Use 16 bit sample size for RX */ + {TABLA_A_CDC_CONN_RX_SB_B1_CTL, 0xFF, 0xAA}, + {TABLA_A_CDC_CONN_RX_SB_B2_CTL, 0xFF, 0xAA}, + + /*enable HPF filter for TX paths */ + {TABLA_A_CDC_TX1_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX2_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX3_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX4_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX5_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX6_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX7_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX8_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX9_MUX_CTL, 0x8, 0x0}, + {TABLA_A_CDC_TX10_MUX_CTL, 0x8, 0x0}, + + /* config Decimator for DMIC CLK_MODE_1(3.072Mhz@12.88Mhz mclk) */ + {TABLA_A_CDC_TX1_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX2_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX3_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX4_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX5_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX6_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX7_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX8_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX9_DMIC_CTL, 0x1, 0x1}, + {TABLA_A_CDC_TX10_DMIC_CTL, 0x1, 0x1}, + + /* config DMIC clk to CLK_MODE_1 (3.072Mhz@12.88Mhz mclk) */ + {TABLA_A_CDC_CLK_DMIC_CTL, 0x2A, 0x2A}, + +}; + +static const struct tabla_reg_mask_val tabla_1_x_codec_reg_init_val[] = { + /* Initialize mic biases to differential mode */ + {TABLA_1_A_MICB_4_INT_RBIAS, 0x24, 0x24}, +}; + +static const struct tabla_reg_mask_val tabla_2_higher_codec_reg_init_val[] = { + /* Initialize mic biases to differential mode */ + {TABLA_2_A_MICB_4_INT_RBIAS, 0x24, 0x24}, +}; + +static void tabla_codec_init_reg(struct snd_soc_codec *codec) +{ + u32 i; + struct wcd9xxx *tabla_core = dev_get_drvdata(codec->dev->parent); + + for (i = 0; i < ARRAY_SIZE(tabla_codec_reg_init_val); i++) + snd_soc_update_bits(codec, tabla_codec_reg_init_val[i].reg, + tabla_codec_reg_init_val[i].mask, + tabla_codec_reg_init_val[i].val); + if (TABLA_IS_1_X(tabla_core->version)) { + for (i = 0; i < ARRAY_SIZE(tabla_1_x_codec_reg_init_val); i++) + snd_soc_update_bits(codec, + tabla_1_x_codec_reg_init_val[i].reg, + tabla_1_x_codec_reg_init_val[i].mask, + tabla_1_x_codec_reg_init_val[i].val); + } else { + for (i = 0; i < ARRAY_SIZE(tabla_2_higher_codec_reg_init_val); + i++) + snd_soc_update_bits(codec, + tabla_2_higher_codec_reg_init_val[i].reg, + tabla_2_higher_codec_reg_init_val[i].mask, + tabla_2_higher_codec_reg_init_val[i].val); + } +} + +static void tabla_update_reg_address(struct tabla_priv *priv) +{ + struct wcd9xxx *tabla_core = dev_get_drvdata(priv->codec->dev->parent); + struct tabla_reg_address *reg_addr = &priv->reg_addr; + + if (TABLA_IS_1_X(tabla_core->version)) { + reg_addr->micb_4_mbhc = TABLA_1_A_MICB_4_MBHC; + reg_addr->micb_4_int_rbias = TABLA_1_A_MICB_4_INT_RBIAS; + reg_addr->micb_4_ctl = TABLA_1_A_MICB_4_CTL; + } else if (TABLA_IS_2_0(tabla_core->version)) { + reg_addr->micb_4_mbhc = TABLA_2_A_MICB_4_MBHC; + reg_addr->micb_4_int_rbias = TABLA_2_A_MICB_4_INT_RBIAS; + reg_addr->micb_4_ctl = TABLA_2_A_MICB_4_CTL; + } +} + +#ifdef CONFIG_DEBUG_FS +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char lbuf[32]; + char *buf; + int rc; + struct tabla_priv *tabla = filp->private_data; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + buf = (char *)lbuf; + tabla->no_mic_headset_override = (*strsep(&buf, " ") == '0') ? + false : true; + return rc; +} + +static ssize_t codec_mbhc_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + const int size = 768; + char buffer[size]; + int n = 0; + struct tabla_priv *tabla = file->private_data; + struct snd_soc_codec *codec = tabla->codec; + const struct mbhc_internal_cal_data *p = &tabla->mbhc_data; + const s16 v_ins_hu_cur = tabla_get_current_v_ins(tabla, true); + const s16 v_ins_h_cur = tabla_get_current_v_ins(tabla, false); + + n = scnprintf(buffer, size - n, "dce_z = %x(%dmv)\n", p->dce_z, + tabla_codec_sta_dce_v(codec, 1, p->dce_z)); + n += scnprintf(buffer + n, size - n, "dce_mb = %x(%dmv)\n", + p->dce_mb, tabla_codec_sta_dce_v(codec, 1, p->dce_mb)); + n += scnprintf(buffer + n, size - n, "sta_z = %x(%dmv)\n", + p->sta_z, tabla_codec_sta_dce_v(codec, 0, p->sta_z)); + n += scnprintf(buffer + n, size - n, "sta_mb = %x(%dmv)\n", + p->sta_mb, tabla_codec_sta_dce_v(codec, 0, p->sta_mb)); + n += scnprintf(buffer + n, size - n, "t_dce = %x\n", p->t_dce); + n += scnprintf(buffer + n, size - n, "t_sta = %x\n", p->t_sta); + n += scnprintf(buffer + n, size - n, "micb_mv = %dmv\n", + p->micb_mv); + n += scnprintf(buffer + n, size - n, "v_ins_hu = %x(%dmv)%s\n", + p->v_ins_hu, + tabla_codec_sta_dce_v(codec, 0, p->v_ins_hu), + p->v_ins_hu == v_ins_hu_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "v_ins_h = %x(%dmv)%s\n", + p->v_ins_h, tabla_codec_sta_dce_v(codec, 1, p->v_ins_h), + p->v_ins_h == v_ins_h_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "adj_v_ins_hu = %x(%dmv)%s\n", + p->adj_v_ins_hu, + tabla_codec_sta_dce_v(codec, 0, p->adj_v_ins_hu), + p->adj_v_ins_hu == v_ins_hu_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "adj_v_ins_h = %x(%dmv)%s\n", + p->adj_v_ins_h, + tabla_codec_sta_dce_v(codec, 1, p->adj_v_ins_h), + p->adj_v_ins_h == v_ins_h_cur ? "*" : ""); + n += scnprintf(buffer + n, size - n, "v_b1_hu = %x(%dmv)\n", + p->v_b1_hu, tabla_codec_sta_dce_v(codec, 0, p->v_b1_hu)); + n += scnprintf(buffer + n, size - n, "v_b1_h = %x(%dmv)\n", + p->v_b1_h, tabla_codec_sta_dce_v(codec, 1, p->v_b1_h)); + n += scnprintf(buffer + n, size - n, "v_b1_huc = %x(%dmv)\n", + p->v_b1_huc, + tabla_codec_sta_dce_v(codec, 1, p->v_b1_huc)); + n += scnprintf(buffer + n, size - n, "v_brh = %x(%dmv)\n", + p->v_brh, tabla_codec_sta_dce_v(codec, 1, p->v_brh)); + n += scnprintf(buffer + n, size - n, "v_brl = %x(%dmv)\n", p->v_brl, + tabla_codec_sta_dce_v(codec, 0, p->v_brl)); + n += scnprintf(buffer + n, size - n, "v_no_mic = %x(%dmv)\n", + p->v_no_mic, + tabla_codec_sta_dce_v(codec, 0, p->v_no_mic)); + n += scnprintf(buffer + n, size - n, "npoll = %d\n", p->npoll); + n += scnprintf(buffer + n, size - n, "nbounce_wait = %d\n", + p->nbounce_wait); + n += scnprintf(buffer + n, size - n, "v_inval_ins_low = %d\n", + p->v_inval_ins_low); + n += scnprintf(buffer + n, size - n, "v_inval_ins_high = %d\n", + p->v_inval_ins_high); + if (tabla->mbhc_cfg.gpio) + n += scnprintf(buffer + n, size - n, "GPIO insert = %d\n", + tabla_hs_gpio_level_remove(tabla)); + buffer[n] = 0; + + return simple_read_from_buffer(buf, count, pos, buffer, n); +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, +}; + +static const struct file_operations codec_mbhc_debug_ops = { + .open = codec_debug_open, + .read = codec_mbhc_debug_read, +}; +#endif + +static int tabla_codec_probe(struct snd_soc_codec *codec) +{ + struct wcd9xxx *control; + struct tabla_priv *tabla; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret = 0; + int i; + int ch_cnt; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + control = codec->control_data; + + tabla = kzalloc(sizeof(struct tabla_priv), GFP_KERNEL); + if (!tabla) { + dev_err(codec->dev, "Failed to allocate private data\n"); + return -ENOMEM; + } + for (i = 0 ; i < NUM_DECIMATORS; i++) { + tx_hpf_work[i].tabla = tabla; + tx_hpf_work[i].decimator = i + 1; + INIT_DELAYED_WORK(&tx_hpf_work[i].dwork, + tx_hpf_corner_freq_callback); + } + + /* Make sure mbhc micbias register addresses are zeroed out */ + memset(&tabla->mbhc_bias_regs, 0, + sizeof(struct mbhc_micbias_regs)); + tabla->mbhc_micbias_switched = false; + + /* Make sure mbhc intenal calibration data is zeroed out */ + memset(&tabla->mbhc_data, 0, + sizeof(struct mbhc_internal_cal_data)); + tabla->mbhc_data.t_sta_dce = DEFAULT_DCE_STA_WAIT; + tabla->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + tabla->mbhc_data.t_sta = DEFAULT_STA_WAIT; + snd_soc_codec_set_drvdata(codec, tabla); + + tabla->mclk_enabled = false; + tabla->bandgap_type = TABLA_BANDGAP_OFF; + tabla->clock_active = false; + tabla->config_mode_active = false; + tabla->mbhc_polling_active = false; + tabla->mbhc_fake_ins_start = 0; + tabla->no_mic_headset_override = false; + tabla->hs_polling_irq_prepared = false; + mutex_init(&tabla->codec_resource_lock); + tabla->codec = codec; + tabla->mbhc_state = MBHC_STATE_NONE; + tabla->mbhc_last_resume = 0; + for (i = 0; i < COMPANDER_MAX; i++) { + tabla->comp_enabled[i] = 0; + tabla->comp_fs[i] = COMPANDER_FS_48KHZ; + } + tabla->pdata = dev_get_platdata(codec->dev->parent); + tabla->intf_type = wcd9xxx_get_intf_type(); + tabla->aux_pga_cnt = 0; + tabla->aux_l_gain = 0x1F; + tabla->aux_r_gain = 0x1F; + tabla_update_reg_address(tabla); + tabla_update_reg_defaults(codec); + tabla_codec_init_reg(codec); + ret = tabla_handle_pdata(tabla); + if (IS_ERR_VALUE(ret)) { + pr_err("%s: bad pdata\n", __func__); + goto err_pdata; + } + +// snd_soc_add_codec_controls(codec, tabla_snd_controls, +// ARRAY_SIZE(tabla_snd_controls)); + if (TABLA_IS_1_X(control->version)) + snd_soc_add_codec_controls(codec, tabla_1_x_snd_controls, + ARRAY_SIZE(tabla_1_x_snd_controls)); + else + snd_soc_add_codec_controls(codec, tabla_2_higher_snd_controls, + ARRAY_SIZE(tabla_2_higher_snd_controls)); + +// snd_soc_dapm_new_controls(dapm, tabla_dapm_widgets, +// ARRAY_SIZE(tabla_dapm_widgets)); + if (TABLA_IS_1_X(control->version)) + snd_soc_dapm_new_controls(dapm, tabla_1_x_dapm_widgets, + ARRAY_SIZE(tabla_1_x_dapm_widgets)); + else + snd_soc_dapm_new_controls(dapm, tabla_2_higher_dapm_widgets, + ARRAY_SIZE(tabla_2_higher_dapm_widgets)); + + if (tabla->intf_type == WCD9XXX_INTERFACE_TYPE_I2C) { + snd_soc_dapm_new_controls(dapm, tabla_dapm_i2s_widgets, + ARRAY_SIZE(tabla_dapm_i2s_widgets)); + snd_soc_dapm_add_routes(dapm, audio_i2s_map, + ARRAY_SIZE(audio_i2s_map)); + } +// snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + if (TABLA_IS_1_X(control->version)) { + snd_soc_dapm_add_routes(dapm, tabla_1_x_lineout_2_to_4_map, + ARRAY_SIZE(tabla_1_x_lineout_2_to_4_map)); + } else if (TABLA_IS_2_0(control->version)) { + snd_soc_dapm_add_routes(dapm, tabla_2_x_lineout_2_to_4_map, + ARRAY_SIZE(tabla_2_x_lineout_2_to_4_map)); + } else { + pr_err("%s : ERROR. Unsupported Tabla version 0x%2x\n", + __func__, control->version); + goto err_pdata; + } + + snd_soc_dapm_sync(dapm); + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, + tabla_hs_insert_irq, "Headset insert detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_INSERTION); + goto err_insert_irq; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, + tabla_hs_remove_irq, "Headset remove detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_REMOVAL); + goto err_remove_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, + tabla_dce_handler, "DC Estimation detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_POTENTIAL); + goto err_potential_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, + tabla_release_handler, "Button Release detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_RELEASE); + goto err_release_irq; + } + + ret = wcd9xxx_request_irq(codec->control_data, TABLA_IRQ_SLIMBUS, + tabla_slimbus_irq, "SLIMBUS Slave", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_SLIMBUS); + goto err_slimbus_irq; + } + + for (i = 0; i < WCD9XXX_SLIM_NUM_PORT_REG; i++) + wcd9xxx_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_EN0 + i, 0xFF); + + ret = wcd9xxx_request_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla_hphl_ocp_irq, + "HPH_L OCP detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_HPH_PA_OCPL_FAULT); + goto err_hphl_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPL_FAULT); + + ret = wcd9xxx_request_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPR_FAULT, tabla_hphr_ocp_irq, + "HPH_R OCP detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_HPH_PA_OCPR_FAULT); + goto err_hphr_ocp_irq; + } + wcd9xxx_disable_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPR_FAULT); + for (i = 0; i < ARRAY_SIZE(tabla_dai); i++) { + switch (tabla_dai[i].id) { + case AIF1_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF1_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + case AIF2_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF2_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + case AIF3_PB: + ch_cnt = tabla_dai[i].playback.channels_max; + break; + case AIF3_CAP: + ch_cnt = tabla_dai[i].capture.channels_max; + break; + default: + continue; + } + tabla->dai[i].ch_num = kzalloc((sizeof(unsigned int)* + ch_cnt), GFP_KERNEL); + } + +#ifdef CONFIG_DEBUG_FS + if (ret == 0) { + tabla->debugfs_poke = + debugfs_create_file("TRRS", S_IFREG | S_IRUGO, NULL, tabla, + &codec_debug_ops); + tabla->debugfs_mbhc = + debugfs_create_file("tabla_mbhc", S_IFREG | S_IRUGO, + NULL, tabla, &codec_mbhc_debug_ops); + } +#endif + codec->ignore_pmdown_time = 1; + return ret; + +err_hphr_ocp_irq: + wcd9xxx_free_irq(codec->control_data, + TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla); +err_hphl_ocp_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla); +err_slimbus_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla); +err_release_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); +err_potential_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); +err_remove_irq: + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); +err_insert_irq: +err_pdata: + mutex_destroy(&tabla->codec_resource_lock); + kfree(tabla); + return ret; +} +static int tabla_codec_remove(struct snd_soc_codec *codec) +{ + int i; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); + wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); + TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock); + tabla_codec_disable_clock_block(codec); + TABLA_RELEASE_LOCK(tabla->codec_resource_lock); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF); + if (tabla->mbhc_fw) + release_firmware(tabla->mbhc_fw); + for (i = 0; i < ARRAY_SIZE(tabla_dai); i++) + kfree(tabla->dai[i].ch_num); + mutex_destroy(&tabla->codec_resource_lock); +#ifdef CONFIG_DEBUG_FS + debugfs_remove(tabla->debugfs_poke); + debugfs_remove(tabla->debugfs_mbhc); +#endif + kfree(tabla); + return 0; +} +static struct snd_soc_codec_driver soc_codec_dev_tabla = { + .probe = tabla_codec_probe, + .remove = tabla_codec_remove, + .read = tabla_read, + .write = tabla_write, + .readable_register = tabla_readable, + .volatile_register = tabla_volatile, + + .reg_cache_size = TABLA_CACHE_SIZE, + .reg_cache_default = tabla_reg_defaults, + .reg_word_size = 1, + .controls = tabla_snd_controls, + .num_controls = ARRAY_SIZE(tabla_snd_controls), + .dapm_widgets = tabla_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tabla_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +#ifdef CONFIG_PM +static int tabla_suspend(struct device *dev) +{ + dev_dbg(dev, "%s: system suspend\n", __func__); + return 0; +} + +static int tabla_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tabla_priv *tabla = platform_get_drvdata(pdev); + dev_dbg(dev, "%s: system resume\n", __func__); + tabla->mbhc_last_resume = jiffies; + return 0; +} + +static const struct dev_pm_ops tabla_pm_ops = { + .suspend = tabla_suspend, + .resume = tabla_resume, +}; +#endif + +static int tabla_probe(struct platform_device *pdev) +{ + int ret = 0; + pr_err("tabla_probe\n"); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tabla, + tabla_dai, ARRAY_SIZE(tabla_dai)); + else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tabla, + tabla_i2s_dai, ARRAY_SIZE(tabla_i2s_dai)); + return ret; +} +static int tabla_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} +static struct platform_driver tabla_codec_driver = { + .probe = tabla_probe, + .remove = tabla_remove, + .driver = { + .name = "tabla_codec", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tabla_pm_ops, +#endif + }, +}; + +static struct platform_driver tabla1x_codec_driver = { + .probe = tabla_probe, + .remove = tabla_remove, + .driver = { + .name = "tabla1x_codec", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tabla_pm_ops, +#endif + }, +}; + +static int __init tabla_codec_init(void) +{ + int rtn = platform_driver_register(&tabla_codec_driver); + if (rtn == 0) { + rtn = platform_driver_register(&tabla1x_codec_driver); + if (rtn != 0) + platform_driver_unregister(&tabla_codec_driver); + } + return rtn; +} + +static void __exit tabla_codec_exit(void) +{ + platform_driver_unregister(&tabla1x_codec_driver); + platform_driver_unregister(&tabla_codec_driver); +} + +module_init(tabla_codec_init); +module_exit(tabla_codec_exit); + +MODULE_DESCRIPTION("Tabla codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wcd9310.h b/sound/soc/codecs/wcd9310.h new file mode 100644 index 000000000000..e6a156e267fe --- /dev/null +++ b/sound/soc/codecs/wcd9310.h @@ -0,0 +1,253 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#define TABLA_NUM_REGISTERS 0x400 +#define TABLA_MAX_REGISTER (TABLA_NUM_REGISTERS-1) +#define TABLA_CACHE_SIZE TABLA_NUM_REGISTERS +#define TABLA_1_X_ONLY_REGISTERS 3 +#define TABLA_2_HIGHER_ONLY_REGISTERS 3 + +#define TABLA_REG_VAL(reg, val) {reg, 0, val} + +#define DEFAULT_DCE_STA_WAIT 55 +#define DEFAULT_DCE_WAIT 60000 +#define DEFAULT_STA_WAIT 5000 +#define VDDIO_MICBIAS_MV 1800 + +#define STA 0 +#define DCE 1 + +#define TABLA_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ + SND_JACK_BTN_4 | SND_JACK_BTN_5 | \ + SND_JACK_BTN_6 | SND_JACK_BTN_7) + +extern const u8 tabla_reg_readable[TABLA_CACHE_SIZE]; +extern const u32 tabla_1_reg_readable[TABLA_1_X_ONLY_REGISTERS]; +extern const u32 tabla_2_reg_readable[TABLA_2_HIGHER_ONLY_REGISTERS]; +extern const u8 tabla_reg_defaults[TABLA_CACHE_SIZE]; + +enum tabla_micbias_num { + TABLA_MICBIAS1 = 0, + TABLA_MICBIAS2, + TABLA_MICBIAS3, + TABLA_MICBIAS4, +}; + +enum tabla_pid_current { + TABLA_PID_MIC_2P5_UA, + TABLA_PID_MIC_5_UA, + TABLA_PID_MIC_10_UA, + TABLA_PID_MIC_20_UA, +}; + +struct tabla_reg_mask_val { + u16 reg; + u8 mask; + u8 val; +}; + +enum tabla_mbhc_clk_freq { + TABLA_MCLK_12P2MHZ = 0, + TABLA_MCLK_9P6MHZ, + TABLA_NUM_CLK_FREQS, +}; + +enum tabla_mbhc_analog_pwr_cfg { + TABLA_ANALOG_PWR_COLLAPSED = 0, + TABLA_ANALOG_PWR_ON, + TABLA_NUM_ANALOG_PWR_CONFIGS, +}; + +enum tabla_mbhc_btn_det_mem { + TABLA_BTN_DET_V_BTN_LOW, + TABLA_BTN_DET_V_BTN_HIGH, + TABLA_BTN_DET_N_READY, + TABLA_BTN_DET_N_CIC, + TABLA_BTN_DET_GAIN +}; + +struct tabla_mbhc_general_cfg { + u8 t_ldoh; + u8 t_bg_fast_settle; + u8 t_shutdown_plug_rem; + u8 mbhc_nsa; + u8 mbhc_navg; + u8 v_micbias_l; + u8 v_micbias; + u8 mbhc_reserved; + u16 settle_wait; + u16 t_micbias_rampup; + u16 t_micbias_rampdown; + u16 t_supply_bringup; +} __packed; + +struct tabla_mbhc_plug_detect_cfg { + u32 mic_current; + u32 hph_current; + u16 t_mic_pid; + u16 t_ins_complete; + u16 t_ins_retry; + u16 v_removal_delta; + u8 micbias_slow_ramp; + u8 reserved0; + u8 reserved1; + u8 reserved2; +} __packed; + +struct tabla_mbhc_plug_type_cfg { + u8 av_detect; + u8 mono_detect; + u8 num_ins_tries; + u8 reserved0; + s16 v_no_mic; + s16 v_av_min; + s16 v_av_max; + s16 v_hs_min; + s16 v_hs_max; + u16 reserved1; +} __packed; + + +struct tabla_mbhc_btn_detect_cfg { + s8 c[8]; + u8 nc; + u8 n_meas; + u8 mbhc_nsc; + u8 n_btn_meas; + u8 n_btn_con; + u8 num_btn; + u8 reserved0; + u8 reserved1; + u16 t_poll; + u16 t_bounce_wait; + u16 t_rel_timeout; + s16 v_btn_press_delta_sta; + s16 v_btn_press_delta_cic; + u16 t_btn0_timeout; + s16 _v_btn_low[0]; /* v_btn_low[num_btn] */ + s16 _v_btn_high[0]; /* v_btn_high[num_btn] */ + u8 _n_ready[TABLA_NUM_CLK_FREQS]; + u8 _n_cic[TABLA_NUM_CLK_FREQS]; + u8 _gain[TABLA_NUM_CLK_FREQS]; +} __packed; + +struct tabla_mbhc_imped_detect_cfg { + u8 _hs_imped_detect; + u8 _n_rload; + u8 _hph_keep_on; + u8 _repeat_rload_calc; + u16 _t_dac_ramp_time; + u16 _rhph_high; + u16 _rhph_low; + u16 _rload[0]; /* rload[n_rload] */ + u16 _alpha[0]; /* alpha[n_rload] */ + u16 _beta[3]; +} __packed; + +struct tabla_mbhc_config { + struct snd_soc_jack *headset_jack; + struct snd_soc_jack *button_jack; + bool read_fw_bin; + /* void* calibration contains: + * struct tabla_mbhc_general_cfg generic; + * struct tabla_mbhc_plug_detect_cfg plug_det; + * struct tabla_mbhc_plug_type_cfg plug_type; + * struct tabla_mbhc_btn_detect_cfg btn_det; + * struct tabla_mbhc_imped_detect_cfg imped_det; + * Note: various size depends on btn_det->num_btn + */ + void *calibration; + enum tabla_micbias_num micbias; + int (*mclk_cb_fn) (struct snd_soc_codec*, int, bool); + unsigned int mclk_rate; + unsigned int gpio; + unsigned int gpio_irq; + int gpio_level_insert; + /* swap_gnd_mic returns true if extern GND/MIC swap switch toggled */ + bool (*swap_gnd_mic) (struct snd_soc_codec *); +}; + +extern int tabla_hs_detect(struct snd_soc_codec *codec, + const struct tabla_mbhc_config *cfg); + +struct anc_header { + u32 reserved[3]; + u32 num_anc_slots; +}; + +extern int tabla_mclk_enable(struct snd_soc_codec *codec, int mclk_enable, + bool dapm); + +extern void *tabla_mbhc_cal_btn_det_mp(const struct tabla_mbhc_btn_detect_cfg + *btn_det, + const enum tabla_mbhc_btn_det_mem mem); + +#define TABLA_MBHC_CAL_SIZE(buttons, rload) ( \ + sizeof(enum tabla_micbias_num) + \ + sizeof(struct tabla_mbhc_general_cfg) + \ + sizeof(struct tabla_mbhc_plug_detect_cfg) + \ + ((sizeof(s16) + sizeof(s16)) * buttons) + \ + sizeof(struct tabla_mbhc_plug_type_cfg) + \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + ((sizeof(u16) + sizeof(u16)) * rload) \ + ) + +#define TABLA_MBHC_CAL_GENERAL_PTR(cali) ( \ + (struct tabla_mbhc_general_cfg *) cali) +#define TABLA_MBHC_CAL_PLUG_DET_PTR(cali) ( \ + (struct tabla_mbhc_plug_detect_cfg *) \ + &(TABLA_MBHC_CAL_GENERAL_PTR(cali)[1])) +#define TABLA_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \ + (struct tabla_mbhc_plug_type_cfg *) \ + &(TABLA_MBHC_CAL_PLUG_DET_PTR(cali)[1])) +#define TABLA_MBHC_CAL_BTN_DET_PTR(cali) ( \ + (struct tabla_mbhc_btn_detect_cfg *) \ + &(TABLA_MBHC_CAL_PLUG_TYPE_PTR(cali)[1])) +#define TABLA_MBHC_CAL_IMPED_DET_PTR(cali) ( \ + (struct tabla_mbhc_imped_detect_cfg *) \ + (((void *)&TABLA_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \ + (TABLA_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \ + (sizeof(TABLA_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \ + sizeof(TABLA_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \ + ) + +/* minimum size of calibration data assuming there is only one button and + * one rload. + */ +#define TABLA_MBHC_CAL_MIN_SIZE ( \ + sizeof(struct tabla_mbhc_general_cfg) + \ + sizeof(struct tabla_mbhc_plug_detect_cfg) + \ + sizeof(struct tabla_mbhc_plug_type_cfg) + \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + (sizeof(u16) * 2)) + +#define TABLA_MBHC_CAL_BTN_SZ(cfg_ptr) ( \ + sizeof(struct tabla_mbhc_btn_detect_cfg) + \ + (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \ + sizeof(cfg_ptr->_v_btn_high[0])))) + +#define TABLA_MBHC_CAL_IMPED_MIN_SZ ( \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + sizeof(u16) * 2) + +#define TABLA_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \ + sizeof(struct tabla_mbhc_imped_detect_cfg) + \ + (cfg_ptr->_n_rload * (sizeof(cfg_ptr->_rload[0]) + \ + sizeof(cfg_ptr->_alpha[0])))) + + diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index f5d81b948759..66b329becbf1 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -869,6 +869,8 @@ SND_SOC_DAPM_SUPPLY("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0, SND_SOC_DAPM_SUPPLY("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0, micbias_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("LINEOUT_VMID_BUF", WM8993_ANTIPOP1, 7, 0, NULL, 0), + SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, in1l_pga, ARRAY_SIZE(in1l_pga)), SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, @@ -1093,9 +1095,11 @@ static const struct snd_soc_dapm_route lineout1_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout1_se_routes[] = { + { "LINEOUT1N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Mixer", "Right Output Switch", "Right Output PGA" }, + { "LINEOUT1P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1P Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, @@ -1112,9 +1116,11 @@ static const struct snd_soc_dapm_route lineout2_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout2_se_routes[] = { + { "LINEOUT2N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT2N Mixer", "Right Output Switch", "Right Output PGA" }, + { "LINEOUT2P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2P Mixer", "Right Output Switch", "Right Output PGA" }, { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig new file mode 100644 index 000000000000..36cbb23a27cc --- /dev/null +++ b/sound/soc/msm/Kconfig @@ -0,0 +1,158 @@ +menu "MSM SoC Audio support" + +#7201 7625 variants +config SND_MSM_DAI_SOC + tristate + +config SND_MSM_SOC_MSM7K + tristate + +config SND_MSM_SOC + tristate "SoC Audio for the MSM series chips" + depends on ARCH_MSM7X27 + select SND_MSM_DAI_SOC + select SND_MSM_SOC_MSM7K + default n + help + To add support for ALSA PCM driver for MSM board. + +#7630 Variants +config SND_MSM7KV2_DAI_SOC + tristate + +config SND_MSM_SOC_MSM7KV2 + tristate + +config SND_MSM7KV2_SOC + tristate "SoC Audio for the MSM7KV2 chip" + depends on ARCH_MSM7X30 && SND_SOC && MSM7KV2_AUDIO + select SND_MSM_SOC_MSM7KV2 + select SND_MSM7KV2_DAI_SOC + default n + help + To add support for ALSA PCM driver for QSD8k board. + +config SND_MSM_MVS7x30_SOC + tristate + +config SND_MSM_MVS_DAI_SOC + tristate + +config SND_MVS_SOC + tristate "SoC Mvs support for MSM7X30" + depends on SND_MSM7KV2_SOC + select SND_MSM_MVS7x30_SOC + select SND_MSM_MVS_DAI_SOC + default n + help + To support Mvs packet capture/playback + +#8660 Variants +config SND_SOC_MSM8X60_PCM + tristate + +config SND_SOC_MSM8X60_DAI + tristate + +config SND_SOC_MSM8X60 + tristate "SoC Audio over DSP support for MSM8660" + depends on ARCH_MSM8X60 && SND_SOC && MSM8X60_AUDIO + select SND_SOC_MSM8X60_PCM + select SND_SOC_MSM8X60_DAI + select SND_SOC_MSM_QDSP6_INTF + default y + help + To add support for SoC audio on MSM8X60. This driver + Adds support for audio over DSP. The driver adds Kcontrols + to do device switch/routing and volume control support for all + audio sessions. The kcontols also does sesion management for + voice calls + +config SND_SOC_MSM_HOSTLESS_PCM + tristate + +config SND_SOC_LPASS_PCM + tristate + +config SND_SOC_MSM8660_LPAIF + tristate + +config SND_VOIP_PCM + tristate + +config SND_SOC_MSM_QDSP6_HDMI_AUDIO + tristate "Soc QDSP6 HDMI Audio DAI driver" + depends on FB_MSM_HDMI_MSM_PANEL + default n + help + To support HDMI Audio on MSM8960 over QDSP6. + +config MSM_8x60_VOIP + tristate "SoC Machine driver for voip" + depends on SND_SOC_MSM8X60 + select SND_MSM_MVS_DAI_SOC + select SND_VOIP_PCM + default n + help + To support ALSA VOIP driver for MSM8x60 target. + This driver communicates with QDSP6, for getting + uplink and downlink voice packets. + +config SND_SOC_MSM_QDSP6_INTF + bool "SoC Q6 audio driver for MSM8960" + depends on MSM_QDSP6_APR + default n + help + To add support for SoC audio on MSM8960. + +config SND_SOC_VOICE + bool "SoC Q6 voice driver for MSM8960" + depends on SND_SOC_MSM_QDSP6_INTF + default n + help + To add support for SoC voice on MSM8960. + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + select SND_SOC_MSM_QDSP6_INTF + default n + help + To add support for MSM QDSP6 Soc Audio. + +config SND_SOC_MSM8960 + tristate "SoC Machine driver for MSM8960 and APQ8064 boards" + depends on ARCH_MSM8960 || ARCH_APQ8064 + select SND_SOC_VOICE + select SND_SOC_QDSP6 + select SND_SOC_MSM_STUB + select SND_SOC_WCD9310 + select SND_SOC_WCD9304 + select SND_SOC_MSM_HOSTLESS_PCM + select SND_SOC_MSM_QDSP6_HDMI_AUDIO + select SND_SOC_CS8427 + default n + help + To add support for SoC audio on MSM8960 and APQ8064 boards + +config SND_SOC_MDM9615 + tristate "SoC Machine driver for MDM9615 boards" + depends on ARCH_MSM9615 + select SND_SOC_VOICE + select SND_SOC_QDSP6 + select SND_SOC_MSM_STUB + select SND_SOC_WCD9310 + select SND_SOC_MSM_HOSTLESS_PCM + select SND_DYNAMIC_MINORS + help + To add support for SoC audio on MDM9615 boards + +config SND_SOC_MSM8660_APQ + tristate "Soc Machine driver for APQ8060 WM8903 codec" + depends on ARCH_MSM8X60 + select SND_SOC_QDSP6 + select SND_SOC_WM8903 + select SND_SOC_MSM_STUB + default n + help + To add support for SoC audio on APQ8060 board +endmenu diff --git a/sound/soc/msm/Makefile b/sound/soc/msm/Makefile new file mode 100644 index 000000000000..43c678d76f4d --- /dev/null +++ b/sound/soc/msm/Makefile @@ -0,0 +1,86 @@ +# MSM CPU/CODEC DAI Support +snd-soc-msm-dai-objs := msm-dai.o +obj-$(CONFIG_SND_MSM_DAI_SOC) += snd-soc-msm-dai.o + +snd-soc-msm7kv2-dai-objs := msm7kv2-dai.o +obj-$(CONFIG_SND_MSM7KV2_DAI_SOC) += snd-soc-msm7kv2-dai.o + +# MSM Platform Support +snd-soc-msm-objs := msm-pcm.o msm7k-pcm.o +obj-$(CONFIG_SND_MSM_SOC) += snd-soc-msm.o + +snd-soc-msmv2-objs := msm7kv2-dsp.o msm7kv2-pcm.o +obj-$(CONFIG_SND_MSM7KV2_SOC) += snd-soc-msmv2.o + +# MSM Machine Support +snd-soc-msm7k-objs := msm7201.o +obj-$(CONFIG_SND_MSM_SOC_MSM7K) += snd-soc-msm7k.o + +snd-soc-msm7kv2-objs := msm7x30.o +obj-$(CONFIG_SND_MSM_SOC_MSM7KV2) += snd-soc-msm7kv2.o + +# 8660 ALSA Support +snd-soc-msm8x60-dai-objs := msm8x60-dai.o +obj-$(CONFIG_SND_SOC_MSM8X60_DAI) += snd-soc-msm8x60-dai.o + +snd-soc-msm8x60-pcm-objs := msm8x60-pcm.o +obj-$(CONFIG_SND_SOC_MSM8X60_PCM) += snd-soc-msm8x60-pcm.o + +snd-soc-msm8x60-objs := msm8x60.o +obj-$(CONFIG_SND_SOC_MSM8X60) += snd-soc-msm8x60.o + + +#MVS Support +snd-soc-msm-mvs-dai-objs := mvs-dai.o +obj-$(CONFIG_SND_MSM_MVS_DAI_SOC) += snd-soc-msm-mvs-dai.o + +snd-soc-msm-mvs-objs := msm-mvs.o +obj-$(CONFIG_SND_MVS_SOC) += snd-soc-msm-mvs.o + +# 8660 ALSA Support +snd-soc-lpass-objs := lpass-i2s.o lpass-dma.o +obj-$(CONFIG_SND_SOC_MSM8660_LPAIF) += snd-soc-lpass.o + +snd-soc-lpass-pcm-objs := lpass-pcm.o +obj-$(CONFIG_SND_SOC_LPASS_PCM) += snd-soc-lpass-pcm.o + +#8660 VOIP Driver Support + +snd-soc-msm-voip-objs := msm-voip.o +obj-$(CONFIG_SND_VOIP_PCM) += snd-soc-msm-voip.o + +snd-soc-lpass-dma-objs := lpass-dma.o +obj-$(CONFIG_SND_SOC_MSM8X60) += snd-soc-lpass-dma.o + +# for MSM 8960 sound card driver + +obj-$(CONFIG_SND_SOC_MSM_QDSP6_INTF) += qdsp6/ + +snd-soc-qdsp6-objs := msm-dai-q6.o msm-pcm-q6.o msm-multi-ch-pcm-q6.o msm-pcm-routing.o msm-dai-fe.o msm-compr-q6.o msm-dai-stub.o +obj-$(CONFIG_SND_SOC_MSM_QDSP6_HDMI_AUDIO) += msm-dai-q6-hdmi.o +obj-$(CONFIG_SND_SOC_VOICE) += msm-pcm-voice.o msm-pcm-voip.o +snd-soc-qdsp6-objs += msm-pcm-lpa.o msm-pcm-afe.o +obj-$(CONFIG_SND_SOC_QDSP6) += snd-soc-qdsp6.o + +snd-soc-msm8960-objs := msm8960.o apq8064.o msm8930.o mpq8064.o +obj-$(CONFIG_SND_SOC_MSM8960) += snd-soc-msm8960.o + +# Generic MSM drivers +snd-soc-hostless-pcm-objs := msm-pcm-hostless.o +obj-$(CONFIG_SND_SOC_MSM_HOSTLESS_PCM) += snd-soc-hostless-pcm.o + +snd-soc-msm8660-apq-objs := msm8660-apq-wm8903.o +obj-$(CONFIG_SND_SOC_MSM8660_APQ) += snd-soc-msm8660-apq.o + +# for MDM 9615 sound card driver +snd-soc-mdm9615-objs := mdm9615.o +obj-$(CONFIG_SND_SOC_MDM9615) += snd-soc-mdm9615.o + +# for MSM 8974 sound card driver +obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += qdsp6v2/ +snd-soc-msm8974-objs := msm8974.o +obj-$(CONFIG_SND_SOC_MSM8974) += snd-soc-msm8974.o + +snd-soc-qdsp6v2-objs := msm-dai-fe.o msm-dai-stub.o +obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o + diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c new file mode 100644 index 000000000000..5f1fafa134f3 --- /dev/null +++ b/sound/soc/msm/apq8064.c @@ -0,0 +1,2031 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wcd9310.h" + +/* 8064 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM8064_SPK_ON 1 +#define MSM8064_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 +#define TOP_SPK_AMP 0x10 + + +#define GPIO_AUX_PCM_DOUT 43 +#define GPIO_AUX_PCM_DIN 44 +#define GPIO_AUX_PCM_SYNC 45 +#define GPIO_AUX_PCM_CLK 46 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define JACK_DETECT_GPIO 38 + +/* Shared channel numbers for Slimbus ports that connect APQ to MDM. */ +enum { + SLIM_1_RX_1 = 145, /* BT-SCO and USB TX */ + SLIM_1_TX_1 = 146, /* BT-SCO and USB RX */ + SLIM_3_RX_1 = 151, /* External echo-cancellation ref */ + SLIM_3_RX_2 = 152, /* External echo-cancellation ref */ + SLIM_3_TX_1 = 153, /* HDMI RX */ + SLIM_3_TX_2 = 154, /* HDMI RX */ + SLIM_4_TX_1 = 148, /* In-call recording RX */ + SLIM_4_TX_2 = 149, /* In-call recording RX */ + SLIM_4_RX_1 = 150, /* In-call music delivery TX */ +}; + +enum { + INCALL_REC_MONO, + INCALL_REC_STEREO, +}; + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; +static int msm_slim_3_rx_ch = 1; + +static int msm_btsco_rate = BTSCO_RATE_8KHZ; +static int msm_btsco_ch = 1; + +static int rec_mode = INCALL_REC_MONO; + +static struct clk *codec_clk; +static int clk_users; + +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int apq8064_hs_detect_use_gpio = -1; +module_param(apq8064_hs_detect_use_gpio, int, 0444); +MODULE_PARM_DESC(apq8064_hs_detect_use_gpio, "Use GPIO for headset detection"); + +static bool apq8064_hs_detect_use_firmware; +module_param(apq8064_hs_detect_use_firmware, bool, 0444); +MODULE_PARM_DESC(apq8064_hs_detect_use_firmware, "Use firmware for headset " + "detection"); + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + . + function = PM_GPIO_FUNC_NORMAL, + }; + + if (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG | TOP_SPK_AMP)) { + + pr_debug("%s():top_spk_amp_state = 0x%x spk_event = 0x%x\n", + __func__, msm_ext_top_spk_pamp, spk); + + if (((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) || + (msm_ext_top_spk_pamp & TOP_SPK_AMP)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if (((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) || + (msm_ext_top_spk_pamp & TOP_SPK_AMP)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG | TOP_SPK_AMP)) { + + pr_debug("%s: top_spk_amp_state = 0x%x spk_event = 0x%x\n", + __func__, msm_ext_top_spk_pamp, spk); + + if (!msm_ext_top_spk_pamp) + return; + + if ((spk & TOP_SPK_AMP_POS) || (spk & TOP_SPK_AMP_NEG)) { + + msm_ext_top_spk_pamp &= (~(TOP_SPK_AMP_POS | + TOP_SPK_AMP_NEG)); + } else if (spk & TOP_SPK_AMP) { + msm_ext_top_spk_pamp &= ~TOP_SPK_AMP; + } + + if (msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after ext Top Spek Ampl is off\n", + __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MSM8064_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top", 12)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top", 12)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, 12288000); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(w->codec, 1, true); + + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + break; + case SND_SOC_DAPM_POST_PMD: + + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users == 0) + return 0; + + clk_users--; + + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + + tabla_mclk_enable(w->codec, 0, true); + clk_disable_unprepare(codec_clk); + } + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget apq8064_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top", msm_spkramp_event), + + /************ Analog MICs ************/ + /** + * Analog mic7 (Front Top) on Liquid. + * Used as Handset mic on CDP. + */ + SND_SOC_DAPM_MIC("Analog mic7", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + /*********** Digital Mics ***************/ + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), +}; + +static const struct snd_soc_dapm_route apq8064_common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + {"Ext Spk Top", NULL, "LINEOUT5"}, + + /************ Analog MIC Paths ************/ + + /* Headset Mic */ + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /* Headset ANC microphones */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, +}; + +static const struct snd_soc_dapm_route apq8064_mtp_audio_map[] = { + + /************ Digital MIC Paths ************/ + + /* + * Digital Mic1 (Front bottom Left) on MTP. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2 (Front bottom right) on MTP. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3 (Back bottom) on MTP. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4 (Back top) on MTP. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5 (Top front Mic) on MTP. + * Conncted to DMIC6 Input on Tabla codec. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + +}; + +static const struct snd_soc_dapm_route apq8064_liquid_cdp_audio_map[] = { + + /************ Analog MIC Paths ************/ + /** + * Analog mic7 (Front Top Mic) on Liquid. + * Used as Handset mic on CDP. + * Not there on MTP. + */ + {"AMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Analog mic7"}, + + + /************ Digital MIC Paths ************/ + /** + * The digital Mic routes are setup considering + * Liquid as default device. + */ + + /** + * Digital Mic1 (Front bottom left corner) on Liquid. + * Digital Mic2 (Front bottom right) on MTP. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2 (Front left side) on Liquid. + * Digital Mic GM2 on CDP mainboard. + * Not there on MTP. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Front bottom left of middle on Liquid. + * Digital Mic5 (Top front Mic) on MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC6 Input on Tabla codec. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back bottom on Liquid. + * Digital Mic GM3 on CDP mainboard. + * Top Front Mic on MTP. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front bottom right of middle on Liquid. + * Digital Mic GM6 on CDP mainboard. + * Not there on MTP. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic5"}, + + /* Digital Mic6 (Front bottom right corner) on Liquid. + * Digital Mic1 (Front bottom Left) on MTP. + * Digital Mic GM4 on CDP. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_slim_3_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_3_rx_ch = %d\n", __func__, + msm_slim_3_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_3_rx_ch - 1; + return 0; +} + +static int msm_slim_3_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_3_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_3_rx_ch = %d\n", __func__, + msm_slim_3_rx_ch); + return 1; +} + +static int msm_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_btsco_rate = %d", __func__, + msm_btsco_rate); + ucontrol->value.integer.value[0] = msm_btsco_rate; + return 0; +} + +static int msm_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm_btsco_rate = %d\n", __func__, + msm_btsco_rate); + return 0; +} + +static int msm_incall_rec_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rec_mode; + return 0; +} + +static int msm_incall_rec_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + rec_mode = ucontrol->value.integer.value[0]; + pr_debug("%s: rec_mode:%d\n", __func__, rec_mode); + + return 0; +} + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm_btsco_enum[0], + msm_btsco_rate_get, msm_btsco_rate_put), + SOC_SINGLE_EXT("Incall Rec Mode", SND_SOC_NOPM, 0, 1, 0, + msm_incall_rec_mode_get, msm_incall_rec_mode_put), + SOC_ENUM_EXT("SLIM_3_RX Channels", msm_enum[1], + msm_slim_3_rx_ch_get, msm_slim_3_rx_ch_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + unsigned int num_tx_ch = 0; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + pr_debug("%s: rx_0_ch=%d\n", __func__, msm_slim_0_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + + if (codec_dai->id == 2) + num_tx_ch = msm_slim_0_tx_ch; + else if (codec_dai->id == 5) { + /* DAI 5 is used for external EC reference from codec. + * Since Rx is fed as reference for EC, the config of + * this DAI is based on that of the Rx path. + */ + num_tx_ch = msm_slim_0_rx_ch; + } + + pr_debug("%s: %s_tx_dai_id_%d_ch=%d\n", __func__, + codec_dai->name, codec_dai->id, num_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, + num_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + num_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + + } +end: + return ret; +} + +static int msm_stubrx_init(struct snd_soc_pcm_runtime *rtd) +{ + rtd->pmdown_time = 0; + + return 0; +} + +static int msm_slimbus_2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + unsigned int num_tx_ch = 0; + unsigned int num_rx_ch = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + num_rx_ch = params_channels(params); + + pr_debug("%s: %s rx_dai_id = %d num_ch = %d\n", __func__, + codec_dai->name, codec_dai->id, num_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + num_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + num_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + + num_tx_ch = params_channels(params); + + pr_debug("%s: %s tx_dai_id = %d num_ch = %d\n", __func__, + codec_dai->name, codec_dai->id, num_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, + num_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + num_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } +end: + return ret; +} + +static int msm_slimbus_1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch = SLIM_1_RX_1, tx_ch = SLIM_1_TX_1; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: APQ BT/USB TX -> SLIMBUS_1_RX -> MDM TX shared ch %d\n", + __func__, rx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, 1, &rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_1 RX channel map\n", + __func__, ret); + + goto end; + } + } else { + pr_debug("%s: MDM RX -> SLIMBUS_1_TX -> APQ BT/USB Rx shared ch %d\n", + __func__, tx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 1, &tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_1 TX channel map\n", + __func__, ret); + + goto end; + } + } + +end: + return ret; +} + +static int msm_slimbus_3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[2] = {SLIM_3_RX_1, SLIM_3_RX_2}; + unsigned int tx_ch[2] = {SLIM_3_TX_1, SLIM_3_TX_2}; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: slim_3_rx_ch %d, sch %d %d\n", + __func__, msm_slim_3_rx_ch, + rx_ch[0], rx_ch[1]); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_3_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_3 RX channel map\n", + __func__, ret); + + goto end; + } + } else { + pr_debug("%s: MDM RX -> SLIMBUS_3_TX -> APQ HDMI ch: %d, %d\n", + __func__, tx_ch[0], tx_ch[1]); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 2, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_3 TX channel map\n", + __func__, ret); + + goto end; + } + } + +end: + return ret; +} + +static int msm_slimbus_4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch = SLIM_4_RX_1, tx_ch[2]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: APQ Incall Playback SLIMBUS_4_RX -> MDM TX shared ch %d\n", + __func__, rx_ch); + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, 1, &rx_ch); + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_4 RX channel map\n", + __func__, ret); + + } + } else { + if (rec_mode == INCALL_REC_STEREO) { + tx_ch[0] = SLIM_4_TX_1; + tx_ch[1] = SLIM_4_TX_2; + ret = snd_soc_dai_set_channel_map(cpu_dai, 2, + tx_ch, 0, 0); + } else { + tx_ch[0] = SLIM_4_TX_1; + ret = snd_soc_dai_set_channel_map(cpu_dai, 1, + tx_ch, 0, 0); + } + pr_debug("%s: Incall Record shared tx_ch[0]:%d, tx_ch[1]:%d\n", + __func__, tx_ch[0], tx_ch[1]); + + if (ret < 0) { + pr_err("%s: Erorr %d setting SLIM_4 TX channel map\n", + __func__, ret); + + } + } + + return ret; +} + +static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + uint32_t revision; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + /*if (machine_is_msm_liquid()) { + top_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(19)); + bottom_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(18)); + }*/ + + snd_soc_dapm_new_controls(dapm, apq8064_dapm_widgets, + ARRAY_SIZE(apq8064_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, apq8064_common_audio_map, + ARRAY_SIZE(apq8064_common_audio_map)); + + if (machine_is_apq8064_mtp()) { + snd_soc_dapm_add_routes(dapm, apq8064_mtp_audio_map, + ARRAY_SIZE(apq8064_mtp_audio_map)); + } else { + snd_soc_dapm_add_routes(dapm, apq8064_liquid_cdp_audio_map, + ARRAY_SIZE(apq8064_liquid_cdp_audio_map)); + } + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + /* APQ8064 Rev 1.1 CDP and Liquid have mechanical switch */ + revision = socinfo_get_version(); + if (apq8064_hs_detect_use_gpio != -1) { + if (apq8064_hs_detect_use_gpio == 1) + pr_debug("%s: MBHC mechanical is enabled by request\n", + __func__); + else if (apq8064_hs_detect_use_gpio == 0) + pr_debug("%s: MBHC mechanical is disabled by request\n", + __func__); + else + pr_warn("%s: Invalid hs_detect_use_gpio %d\n", __func__, + apq8064_hs_detect_use_gpio); + } else if (SOCINFO_VERSION_MAJOR(revision) == 0) { + pr_warn("%s: Unknown HW revision detected %d.%d\n", __func__, + SOCINFO_VERSION_MAJOR(revision), + SOCINFO_VERSION_MINOR(revision)); + } else if ((SOCINFO_VERSION_MAJOR(revision) == 1 && + SOCINFO_VERSION_MINOR(revision) >= 1 && + (machine_is_apq8064_cdp() || + machine_is_apq8064_liquid())) || + SOCINFO_VERSION_MAJOR(revision) > 1) { + pr_debug("%s: MBHC mechanical switch available APQ8064 " + "detected\n", __func__); + apq8064_hs_detect_use_gpio = 1; + } + + if (apq8064_hs_detect_use_gpio == 1) { + pr_debug("%s: Using MBHC mechanical switch\n", __func__); + mbhc_cfg.gpio = JACK_DETECT_GPIO; + mbhc_cfg.gpio_irq = gpio_to_irq(JACK_DETECT_GPIO); + err = gpio_request(mbhc_cfg.gpio, "MBHC_HS_DETECT"); + if (err < 0) { + pr_err("%s: gpio_request %d failed %d\n", __func__, + mbhc_cfg.gpio, err); + return err; + } + gpio_direction_input(JACK_DETECT_GPIO); + } else + pr_debug("%s: Not using MBHC mechanical switch\n", __func__); + + mbhc_cfg.read_fw_bin = apq8064_hs_detect_use_firmware; + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int msm_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm_slim_0_rx_ch; + + return 0; +} + +static int msm_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm_slim_0_tx_ch; + + return 0; +} + +static int msm_slim_3_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm_slim_3_rx_ch; + + return 0; +} + +static int msm_slim_3_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int msm_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + + return 0; +} + +static int msm_hdmi_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + + return 0; +} + +static int msm_btsco_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = msm_btsco_rate; + channels->min = channels->max = msm_btsco_ch; + + return 0; +} +static int msm_auxpcm_be_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} + +static int msm_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int msm_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} +static int msm_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + pr_debug("%s(): dai_link_str_name = %s cpu_dai = %s codec_dai = %s\n", + __func__, rtd->dai_link->stream_name, + rtd->dai_link->cpu_dai_name, + rtd->dai_link->codec_dai_name); + return 0; +} + +static int msm_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; +} + +static void msm_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm_aux_pcm_free_gpios(); +} + +static void msm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + pr_debug("%s(): dai_link_str_name = %s cpu_dai = %s codec_dai = %s\n", + __func__, rtd->dai_link->stream_name, + rtd->dai_link->cpu_dai_name, rtd->dai_link->codec_dai_name); +} + +static struct snd_soc_ops msm_be_ops = { + .startup = msm_startup, + .hw_params = msm_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_auxpcm_be_ops = { + .startup = msm_auxpcm_startup, + .shutdown = msm_auxpcm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_1_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_1_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_3_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_3_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_4_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_4_hw_params, + .shutdown = msm_shutdown, +}; + +static struct snd_soc_ops msm_slimbus_2_be_ops = { + .startup = msm_startup, + .hw_params = msm_slimbus_2_hw_params, + .shutdown = msm_shutdown, +}; + + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + /* .be_id = do not care */ + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "Voice Stub", + .stream_name = "Voice Stub", + .cpu_dai_name = "VOICE_STUB", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm_audrx_init, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + /* Backend BT/FM DAI Links */ + { + .name = LPASS_BE_INT_BT_SCO_RX, + .stream_name = "Internal BT-SCO Playback", + .cpu_dai_name = "msm-dai-q6.12288", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_BT_SCO_TX, + .stream_name = "Internal BT-SCO Capture", + .cpu_dai_name = "msm-dai-q6.12289", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + }, + { + .name = LPASS_BE_INT_FM_RX, + .stream_name = "Internal FM Playback", + .cpu_dai_name = "msm-dai-q6.12292", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_FM_TX, + .stream_name = "Internal FM Capture", + .cpu_dai_name = "msm-dai-q6.12293", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_HDMI_RX, + .be_hw_params_fixup = msm_hdmi_be_hw_params_fixup, + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_RX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + .ops = &msm_auxpcm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_TX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + }, + { + .name = LPASS_BE_STUB_RX, + .stream_name = "Stub Playback", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx2", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_RX, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .init = &msm_stubrx_init, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_STUB_TX, + .stream_name = "Stub Capture", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_TX, + .be_hw_params_fixup = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + .name = LPASS_BE_SLIMBUS_1_RX, + .stream_name = "Slimbus1 Playback", + .cpu_dai_name = "msm-dai-q6.16386", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_1_RX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ops = &msm_slimbus_1_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + + }, + { + .name = LPASS_BE_SLIMBUS_1_TX, + .stream_name = "Slimbus1 Capture", + .cpu_dai_name = "msm-dai-q6.16387", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_1_TX, + .be_hw_params_fixup = msm_btsco_be_hw_params_fixup, + .ops = &msm_slimbus_1_be_ops, + }, + /* Ultrasound TX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Capture", + .stream_name = "SLIMBUS_2 Hostless Capture", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm_slimbus_2_be_ops, + }, + /* Ultrasound RX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Playback", + .stream_name = "SLIMBUS_2 Hostless Playback", + .cpu_dai_name = "msm-dai-q6.16388", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx3", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm_slimbus_2_be_ops, + }, + /* Incall Music Back End DAI Link */ + { + .name = LPASS_BE_SLIMBUS_4_RX, + .stream_name = "Slimbus4 Playback", + .cpu_dai_name = "msm-dai-q6.16392", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_4_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_slimbus_4_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Incall Record Back End DAI Link */ + { + .name = LPASS_BE_SLIMBUS_4_TX, + .stream_name = "Slimbus4 Capture", + .cpu_dai_name = "msm-dai-q6.16393", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_4_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_slimbus_4_be_ops, + }, + { + .name = LPASS_BE_STUB_1_TX, + .stream_name = "Stub1 Capture", + .cpu_dai_name = "msm-dai-stub", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx3", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_EXTPROC_EC_TX, + /* This BE is used for external EC reference from codec. Since + * Rx is fed as reference for EC, the config of this DAI is + * based on that of the Rx path. + */ + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + + .name = LPASS_BE_SLIMBUS_3_RX, + .stream_name = "Slimbus3 Playback", + .cpu_dai_name = "msm-dai-q6.16390", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_3_RX, + .be_hw_params_fixup = msm_slim_3_rx_be_hw_params_fixup, + .ops = &msm_slimbus_3_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_3_TX, + .stream_name = "Slimbus3 Capture", + .cpu_dai_name = "msm-dai-q6.16391", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_3_TX, + .be_hw_params_fixup = msm_slim_3_tx_be_hw_params_fixup, + .ops = &msm_slimbus_3_be_ops, + }, +}; + +struct snd_soc_card snd_soc_card_msm = { + .name = "apq8064-tabla-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), + .controls = tabla_msm_controls, + .num_controls = ARRAY_SIZE(tabla_msm_controls), +}; + +static struct platform_device *msm_snd_device; + +static int msm_configure_headset_mic_gpios(void) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(35), "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(35), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(35), 0); + + return 0; +} +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret; + + if (!cpu_is_apq8064() || (socinfo_get_id() == 130)) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm_headset_gpios_configured = 0; + } else + msm_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (!cpu_is_apq8064() || (socinfo_get_id() == 130)) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + if (mbhc_cfg.gpio) + gpio_free(mbhc_cfg.gpio); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC msm"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/lpass-dma.c b/sound/soc/msm/lpass-dma.c new file mode 100644 index 000000000000..c3b4fe39d852 --- /dev/null +++ b/sound/soc/msm/lpass-dma.c @@ -0,0 +1,488 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-pcm.h" + +struct dai_baseinfo { + void __iomem *base; +}; + +static struct dai_baseinfo dai_info; + +struct dai_drv { + u8 *buffer; + u32 buffer_phys; + int channels; + irqreturn_t (*callback) (int intrsrc, void *private_data); + void *private_data; + int in_use; + u32 buffer_len; + u32 period_len; + u32 master_mode; +}; + +static struct dai_drv *dai[MAX_CHANNELS]; +static spinlock_t dai_lock; + +static int dai_find_dma_channel(uint32_t intrsrc) +{ + int i, dma_channel = 0; + pr_debug("%s\n", __func__); + + for (i = 0; i <= 27; i += 3) { + if (intrsrc & (1 << i)) { + dma_channel = i / 3; + break; + } + } + return dma_channel; +} + +void register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback) (int intrsrc, void *private_data), + void *private_data) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->callback = callback; + dai[dma_ch]->private_data = private_data; +} + +void unregister_dma_irq_handler(int dma_ch) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->callback = NULL; + dai[dma_ch]->private_data = NULL; +} + +static irqreturn_t dai_irq_handler(int irq, void *data) +{ + unsigned long flag; + uint32_t intrsrc; + uint32_t dma_ch = 0; + irqreturn_t ret = IRQ_HANDLED; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&dai_lock, flag); + intrsrc = readl(dai_info.base + LPAIF_IRQ_STAT(0)); + writel(intrsrc, dai_info.base + LPAIF_IRQ_CLEAR(0)); + mb(); + while (intrsrc) { + dma_ch = dai_find_dma_channel(intrsrc); + + if (!dai[dma_ch]->callback) + goto handled; + if (!dai[dma_ch]->private_data) + goto handled; + ret = dai[dma_ch]->callback(intrsrc, + dai[dma_ch]->private_data); + intrsrc &= ~(0x7 << (dma_ch * 3)); + } +handled: + spin_unlock_irqrestore(&dai_lock, flag); + return ret; +} + +void dai_print_state(uint32_t dma_ch) +{ + int i = 0; + unsigned long *ptrmem = (unsigned long *)dai_info.base; + + for (i = 0; i < 4; i++, ++ptrmem) + pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem, + (unsigned int)*ptrmem); + + ptrmem = (unsigned long *)(dai_info.base + + DMA_CH_CTL_BASE + DMA_CH_INDEX(dma_ch)); + for (i = 0; i < 10; i++, ++ptrmem) + pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem, + (unsigned int) *ptrmem); +} + +static int dai_enable_irq(uint32_t dma_ch) +{ + int ret; + pr_debug("%s\n", __func__); + ret = request_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, dai_irq_handler, + IRQF_TRIGGER_RISING | IRQF_SHARED, "msm-i2s", + (void *) (dma_ch+1)); + if (ret < 0) { + pr_debug("Request Irq Failed err = %d\n", ret); + return ret; + } + return ret; +} + +static void dai_config_dma(uint32_t dma_ch) +{ + pr_debug("%s dma_ch = %u\n", __func__, dma_ch); + + writel(dai[dma_ch]->buffer_phys, + dai_info.base + LPAIF_DMA_BASE(dma_ch)); + writel(((dai[dma_ch]->buffer_len >> 2) - 1), + dai_info.base + LPAIF_DMA_BUFF_LEN(dma_ch)); + writel(((dai[dma_ch]->period_len >> 2) - 1), + dai_info.base + LPAIF_DMA_PER_LEN(dma_ch)); + mb(); +} + +static void dai_enable_codec(uint32_t dma_ch, int codec) +{ + uint32_t intrVal; + uint32_t i2sctl; + pr_debug("%s\n", __func__); + + intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0)); + intrVal = intrVal | (7 << (dma_ch * 3)); + writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0)); + if (codec == DAI_SPKR) { + writel(0x0813, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + i2sctl = 0x4400; + i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT); + writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_SPKR)); + } else if (codec == DAI_MIC) { + writel(0x81b, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + i2sctl = 0x0110; + i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT); + writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_MIC)); + } +} + +static void dai_disable_codec(uint32_t dma_ch, int codec) +{ + uint32_t intrVal = 0; + uint32_t intrVal1 = 0; + unsigned long flag = 0x0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&dai_lock, flag); + + intrVal1 = readl(dai_info.base + LPAIF_I2S_CTL_OFFSET(codec)); + + if (codec == DAI_SPKR) + intrVal1 = intrVal1 & ~(1 << 14); + else if (codec == DAI_MIC) + intrVal1 = intrVal1 & ~(1 << 8); + + writel(intrVal1, dai_info.base + LPAIF_I2S_CTL_OFFSET(codec)); + intrVal = 0x0; + writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + spin_unlock_irqrestore(&dai_lock, flag); +} + +int dai_open(uint32_t dma_ch) +{ + + pr_debug("%s\n", __func__); + if (!dai_info.base) { + pr_debug("%s failed as no msm-dai device\n", __func__); + return -ENODEV; + } + if (dma_ch >= MAX_CHANNELS) { + pr_debug("%s over max channesl %d\n", __func__, dma_ch); + return -ENODEV; + } + return 0; +} + +void dai_close(uint32_t dma_ch) +{ + pr_debug("%s\n", __func__); + if ((dma_ch >= 0) && (dma_ch < 5)) + dai_disable_codec(dma_ch, DAI_SPKR); + else + dai_disable_codec(dma_ch, DAI_MIC); + free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1)); +} + +void dai_set_master_mode(uint32_t dma_ch, int mode) +{ + if (dma_ch < MAX_CHANNELS) + dai[dma_ch]->master_mode = mode; + else + pr_err("%s: invalid dma channel\n", __func__); +} + +int dai_set_params(uint32_t dma_ch, struct dai_dma_params *params) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->buffer = params->buffer; + dai[dma_ch]->buffer_phys = params->src_start; + dai[dma_ch]->channels = params->channels; + dai[dma_ch]->buffer_len = params->buffer_size; + dai[dma_ch]->period_len = params->period_size; + mb(); + dai_config_dma(dma_ch); + return dma_ch; +} + +int dai_start(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + + spin_lock_irqsave(&dai_lock, flag); + dai_enable_irq(dma_ch); + if ((dma_ch >= 0) && (dma_ch < 5)) + dai_enable_codec(dma_ch, DAI_SPKR); + else + dai_enable_codec(dma_ch, DAI_MIC); + spin_unlock_irqrestore(&dai_lock, flag); + dai_print_state(dma_ch); + return 0; +} + +#define HDMI_BURST_INCR4 (1 << 11) +#define HDMI_WPSCNT (1 << 8) +#define HDMI_AUDIO_INTF (5 << 4) +#define HDMI_FIFO_WATER_MARK (7 << 1) +#define HDMI_ENABLE (1) + +int dai_start_hdmi(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + uint32_t val; + + pr_debug("%s dma_ch = %u\n", __func__, dma_ch); + + spin_lock_irqsave(&dai_lock, flag); + + dai_enable_irq(dma_ch); + + if ((dma_ch >= 0) && (dma_ch < 5)) { + + val = readl(dai_info.base + LPAIF_IRQ_EN(0)); + val = val | (7 << (dma_ch * 3)); + writel(val, dai_info.base + LPAIF_IRQ_EN(0)); + mb(); + + + val = (HDMI_BURST_INCR4 | HDMI_WPSCNT | HDMI_AUDIO_INTF | + HDMI_FIFO_WATER_MARK | HDMI_ENABLE); + + writel(val, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + } + spin_unlock_irqrestore(&dai_lock, flag); + + mb(); + dai_print_state(dma_ch); + return 0; +} + +int wait_for_dma_cnt_stop(uint32_t dma_ch) +{ + uint32_t dma_per_cnt_reg_val, dma_per_cnt, prev_dma_per_cnt; + uint32_t i; + + pr_info("%s dma_ch %u\n", __func__, dma_ch); + + dma_per_cnt_reg_val = readl_relaxed(dai_info.base + + LPAIF_DMA_PER_CNT(dma_ch)); + + dma_per_cnt = + ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) - + ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT); + + prev_dma_per_cnt = dma_per_cnt; + + i = 1; + pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n", + __func__, i, dma_per_cnt_reg_val, dma_per_cnt); + + while (i <= 50) { + msleep(50); + + dma_per_cnt_reg_val = readl_relaxed(dai_info.base + + LPAIF_DMA_PER_CNT(dma_ch)); + + dma_per_cnt = + ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) - + ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >> + LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT); + + i++; + + pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n", + __func__, i, dma_per_cnt_reg_val, dma_per_cnt); + + if (prev_dma_per_cnt == dma_per_cnt) + break; + + prev_dma_per_cnt = dma_per_cnt; + } + return 0; +} + +void dai_stop_hdmi(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + uint32_t intrVal; + uint32_t int_mask = 0x00000007; + + pr_debug("%s dma_ch %u\n", __func__, dma_ch); + + spin_lock_irqsave(&dai_lock, flag); + + free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1)); + + + intrVal = 0x0; + writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + mb(); + + intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0)); + + int_mask = ((int_mask) << (dma_ch * 3)); + int_mask = ~int_mask; + + intrVal = intrVal & int_mask; + writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0)); + + mb(); + + spin_unlock_irqrestore(&dai_lock, flag); +} + +int dai_stop(uint32_t dma_ch) +{ + pr_debug("%s\n", __func__); + return 0; +} + + +uint32_t dai_get_dma_pos(uint32_t dma_ch) +{ + + uint32_t addr; + + pr_debug("%s\n", __func__); + addr = readl(dai_info.base + LPAIF_DMA_CURR_ADDR(dma_ch)); + + return addr; +} + +static int dai_probe(struct platform_device *pdev) +{ + int rc = 0; + int i = 0; + struct resource *src; + src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "msm-dai"); + if (!src) { + rc = -ENODEV; + pr_debug("%s Error rc=%d\n", __func__, rc); + goto error; + } + for (i = 0; i <= MAX_CHANNELS; i++) { + dai[i] = kzalloc(sizeof(struct dai_drv), GFP_KERNEL); + if (!dai[0]) { + pr_debug("Allocation failed for dma_channel = 0\n"); + return -ENODEV; + } + } + dai_info.base = ioremap(src->start, (src->end - src->start) + 1); + pr_debug("%s: msm-dai: 0x%08x\n", __func__, + (unsigned int)dai_info.base); + spin_lock_init(&dai_lock); +error: + return rc; +} + +static int dai_remove(struct platform_device *pdev) +{ + iounmap(dai_info.base); + return 0; +} + +static struct platform_driver dai_driver = { + .probe = dai_probe, + .remove = dai_remove, + .driver = { + .name = "msm-dai", + .owner = THIS_MODULE + }, +}; + +static struct resource msm_lpa_resources[] = { + { + .start = MSM_LPA_PHYS, + .end = MSM_LPA_END, + .flags = IORESOURCE_MEM, + .name = "msm-dai", + }, +}; + +static struct platform_device *codec_device; + +static int msm_dai_dev_register(const char *name) +{ + int ret = 0; + + pr_debug("%s : called\n", __func__); + codec_device = platform_device_alloc(name, -1); + if (codec_device == NULL) { + pr_debug("Failed to allocate %s\n", name); + return -ENODEV; + } + + platform_set_drvdata(codec_device, (void *)&dai_info); + platform_device_add_resources(codec_device, &msm_lpa_resources[0], + ARRAY_SIZE(msm_lpa_resources)); + ret = platform_device_add(codec_device); + if (ret != 0) { + pr_debug("Failed to register %s: %d\n", name, ret); + platform_device_put(codec_device); + } + return ret; +} + +static int __init dai_init(void) +{ + if (msm_dai_dev_register("msm-dai")) { + pr_notice("dai_init: msm-dai Failed"); + return -ENODEV; + } + return platform_driver_register(&dai_driver); +} + +static void __exit dai_exit(void) +{ + platform_driver_unregister(&dai_driver); + platform_device_put(codec_device); +} + +module_init(dai_init); +module_exit(dai_exit); + +MODULE_DESCRIPTION("MSM I2S driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/lpass-i2s.c b/sound/soc/msm/lpass-i2s.c new file mode 100644 index 000000000000..795f4eee64f3 --- /dev/null +++ b/sound/soc/msm/lpass-i2s.c @@ -0,0 +1,139 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int msm_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + uint32_t dma_ch = dai->id; + int ret = 0; + + pr_debug("%s\n", __func__); + ret = dai_open(dma_ch); + return ret; + +} + +static void msm_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + uint32_t dma_ch = dai->id; + + pr_debug("%s\n", __func__); + dai_close(dma_ch); +} + +static int msm_cpu_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int msm_cpu_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + uint32_t dma_ch = dai->id; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_set_master_mode(dma_ch, 1); /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_set_master_mode(dma_ch, 0); /* CPU is slave */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops msm_cpu_dai_ops = { + .startup = msm_cpu_dai_startup, + .shutdown = msm_cpu_dai_shutdown, + .trigger = msm_cpu_dai_trigger, + .set_fmt = msm_cpu_dai_fmt, + +}; + + +#define MSM_DAI_SPEAKER_BUILDER(link_id) \ +{ \ + .name = "msm-speaker-dai-"#link_id, \ + .id = (link_id), \ + .playback = { \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .channels_min = 1, \ + .channels_max = 2, \ + .rate_max = 96000, \ + .rate_min = 8000, \ + }, \ + .ops = &msm_cpu_dai_ops, \ +} + + +#define MSM_DAI_MIC_BUILDER(link_id) \ +{ \ + .name = "msm-mic-dai-"#link_id, \ + .id = (link_id), \ + .capture = { \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .rate_min = 8000, \ + .rate_max = 96000, \ + .channels_min = 1, \ + .channels_max = 2, \ + }, \ + .ops = &msm_cpu_dai_ops, \ +} + + +struct snd_soc_dai msm_cpu_dai[] = { + MSM_DAI_SPEAKER_BUILDER(0), + MSM_DAI_SPEAKER_BUILDER(1), + MSM_DAI_SPEAKER_BUILDER(2), + MSM_DAI_SPEAKER_BUILDER(3), + MSM_DAI_SPEAKER_BUILDER(4), + MSM_DAI_MIC_BUILDER(5), + MSM_DAI_MIC_BUILDER(6), + MSM_DAI_MIC_BUILDER(7), +}; +EXPORT_SYMBOL_GPL(msm_cpu_dai); + +static int __init msm_cpu_dai_init(void) +{ + return snd_soc_register_dais(msm_cpu_dai, ARRAY_SIZE(msm_cpu_dai)); +} +module_init(msm_cpu_dai_init); + +static void __exit msm_cpu_dai_exit(void) +{ + snd_soc_unregister_dais(msm_cpu_dai, ARRAY_SIZE(msm_cpu_dai)); +} +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM CPU DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/lpass-pcm.c b/sound/soc/msm/lpass-pcm.c new file mode 100644 index 000000000000..33d5e64911d8 --- /dev/null +++ b/sound/soc/msm/lpass-pcm.c @@ -0,0 +1,369 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-pcm.h" + +static const struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = DMASZ/4, + .buffer_bytes_max = DMASZ, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .periods_min = 4, + .periods_max = 512, + .fifo_size = 0, +}; + +struct msm_pcm_data { + spinlock_t lock; + int ch; +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + pr_debug("%s\n", __func__); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static irqreturn_t msm_pcm_irq(int intrsrc, void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int dma_ch = 0; + unsigned int has_xrun, pending; + int ret = IRQ_NONE; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return ret; + + pr_debug("msm8660-pcm: msm_pcm_irq called\n"); + pending = (intrsrc + & (UNDER_CH(dma_ch) | PER_CH(dma_ch) | ERR_CH(dma_ch))); + has_xrun = (pending & UNDER_CH(dma_ch)); + + if (unlikely(has_xrun) && + substream->runtime && + snd_pcm_running(substream)) { + pr_err("xrun\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + pending &= ~UNDER_CH(dma_ch); + } + + + if (pending & PER_CH(dma_ch)) { + ret = IRQ_HANDLED; + if (likely(substream->runtime && + snd_pcm_running(substream))) { + /* end of buffer missed? loop back */ + if (++prtd->period_index >= runtime->periods) + prtd->period_index = 0; + snd_pcm_period_elapsed(substream); + pr_debug("period elapsed\n"); + } + pending &= ~PER_CH(dma_ch); + } + + if (unlikely(pending + & (UNDER_CH(dma_ch) & PER_CH(dma_ch) & ERR_CH(dma_ch)))) { + if (pending & UNDER_CH(dma_ch)) + pr_err("msm8660-pcm: DMA %x Underflow\n", + dma_ch); + if (pending & ERR_CH(dma_ch)) + pr_err("msm8660-pcm: DMA %x Master Error\n", + dma_ch); + + } + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + struct dai_dma_params dma_params; + int dma_ch = 0; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return 0; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + pr_debug("%s:prtd->pcm_size = %d\n", __func__, prtd->pcm_size); + pr_debug("%s:prtd->pcm_count = %d\n", __func__, prtd->pcm_count); + + if (prtd->enabled) + return 0; + + dma_params.src_start = runtime->dma_addr; + dma_params.buffer = (u8 *)runtime->dma_area; + dma_params.buffer_size = prtd->pcm_size; + dma_params.period_size = prtd->pcm_count; + dma_params.channels = runtime->channels; + + dai_set_params(dma_ch, &dma_params); + register_dma_irq_handler(dma_ch, msm_pcm_irq, (void *)substream); + + prtd->enabled = 1; + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int ret = 0; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dai_start(prtd->dma_ch); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dai_stop(prtd->dma_ch); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + snd_pcm_uframes_t offset = 0; + + pr_debug("%s: period_index =%d\n", __func__, prtd->period_index); + offset = prtd->period_index * runtime->period_size; + if (offset >= runtime->buffer_size) + offset = 0; + return offset; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct msm_audio *prtd = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + snd_soc_set_runtime_hwparams(substream, &msm_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + if (ret < 0) { + pr_err("Error setting hw_constraint\n"); + goto err; + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("Error snd_pcm_hw_constraint_list failed\n"); + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + + if (prtd == NULL) { + pr_err("Error allocating prtd\n"); + ret = -ENOMEM; + goto err; + } + prtd->dma_ch = cpu_dai->id; + prtd->enabled = 0; + runtime->dma_bytes = msm_pcm_hardware.buffer_bytes_max; + runtime->private_data = prtd; +err: + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int dma_ch = 0; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return 0; + + pr_debug("%s\n", __func__); + unregister_dma_irq_handler(dma_ch); + kfree(runtime->private_data); + return 0; +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vms) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s\n", __func__); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_addr 0x(%x)\n", + __func__, (unsigned int)runtime->dma_addr); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_area 0x(%x)\n", + __func__, (unsigned int)runtime->dma_area); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_bytes 0x(%x)\n", + __func__, (unsigned int)runtime->dma_bytes); + + return dma_mmap_coherent(substream->pcm->card->dev, vms, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = msm_pcm_hw_params, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int pcm_preallocate_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = msm_pcm_hardware.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!stream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} +static u64 msm_pcm_dmamask = DMA_BIT_MASK(32); + +static int msm_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &msm_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->playback.channels_min) { + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + if (dai->capture.channels_min) { + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + return ret; +} + +struct snd_soc_platform msm8660_soc_platform = { + .name = "msm8660-pcm-audio", + .pcm_ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL_GPL(msm8660_soc_platform); + +static int __init msm_soc_platform_init(void) +{ + return snd_soc_register_platform(&msm8660_soc_platform); +} +static void __exit msm_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&msm8660_soc_platform); +} +module_init(msm_soc_platform_init); +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("MSM PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/lpass-pcm.h b/sound/soc/msm/lpass-pcm.h new file mode 100644 index 000000000000..e7e559760c4f --- /dev/null +++ b/sound/soc/msm/lpass-pcm.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define NUM_DMAS 9 +#define DMASZ 16384 +#define MAX_CHANNELS 9 + +#define MSM_LPA_PHYS 0x28100000 +#define MSM_LPA_END 0x2810DFFF + + +struct msm_audio { + struct snd_pcm_substream *substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + int enabled; + int period; + int dma_ch; + int period_index; + int start; +}; + +extern struct snd_soc_dai msm_cpu_dai[NUM_DMAS]; +extern struct snd_soc_platform msm8660_soc_platform; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/mdm9615.c b/sound/soc/msm/mdm9615.c new file mode 100644 index 000000000000..0e3d136b6e18 --- /dev/null +++ b/sound/soc/msm/mdm9615.c @@ -0,0 +1,2203 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wcd9310.h" +#include + +/* 9615 machine driver */ + +#define PM8018_GPIO_BASE NR_GPIO_IRQS +#define PM8018_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8018_GPIO_BASE) + +#define MDM9615_SPK_ON 1 +#define MDM9615_SPK_OFF 0 + +#define MDM9615_SLIM_0_RX_MAX_CHANNELS 2 +#define MDM9615_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 23 +#define GPIO_AUX_PCM_DIN 22 +#define GPIO_AUX_PCM_SYNC 21 +#define GPIO_AUX_PCM_CLK 20 + +#define GPIO_SEC_AUX_PCM_DOUT 28 +#define GPIO_SEC_AUX_PCM_DIN 27 +#define GPIO_SEC_AUX_PCM_SYNC 26 +#define GPIO_SEC_AUX_PCM_CLK 25 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +/* + * Added for I2S + */ +#define GPIO_SPKR_I2S_MCLK 24 +#define GPIO_PRIM_I2S_SCK 20 +#define GPIO_PRIM_I2S_DOUT 23 +#define GPIO_PRIM_I2S_WS 21 +#define GPIO_PRIM_I2S_DIN 22 +#define GPIO_SEC_I2S_SCK 25 +#define GPIO_SEC_I2S_WS 26 +#define GPIO_SEC_I2S_DOUT 28 +#define GPIO_SEC_I2S_DIN 27 + +static struct gpiomux_setting cdc_i2s_mclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_sclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_dout = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_ws = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_i2s_din = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + + +static struct msm_gpiomux_config msm9615_audio_prim_i2s_codec_configs[] = { + { + .gpio = GPIO_SPKR_I2S_MCLK, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_mclk, + }, + }, + { + .gpio = GPIO_PRIM_I2S_SCK, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_sclk, + }, + }, + { + .gpio = GPIO_PRIM_I2S_DOUT, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_dout, + }, + }, + { + .gpio = GPIO_PRIM_I2S_WS, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_ws, + }, + }, + { + .gpio = GPIO_PRIM_I2S_DIN, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_i2s_din, + }, + }, +}; + +/* Physical address for LPA CSR + * LPA SIF mux registers. These are + * ioremap( ) for Virtual address. + */ +#define LPASS_CSR_BASE 0x28000000 +#define LPA_IF_BASE 0x28100000 +#define SIF_MUX_REG_BASE (LPASS_CSR_BASE + 0x00000000) +#define LPA_IF_REG_BASE (LPA_IF_BASE + 0x00000000) +#define LPASS_SIF_MUX_ADDR (SIF_MUX_REG_BASE + 0x00004000) +#define LPAIF_SPARE_ADDR (LPA_IF_REG_BASE + 0x00000070) +#define SEC_PCM_PORT_SLC_ADDR 0x00802074 +/* bits 2:0 should be updated with 100 to select SDC2 */ +#define SEC_PCM_PORT_SLC_VALUE 0x4 +/* SIF & SPARE MUX Values */ +#define MSM_SIF_FUNC_PCM 0 +#define MSM_SIF_FUNC_I2S_MIC 1 +#define MSM_SIF_FUNC_I2S_SPKR 2 +#define MSM_LPAIF_SPARE_DISABLE 0x0 +#define MSM_LPAIF_SPARE_BOTH_ENABLE 0x3 + +/* I2S INTF CTL */ +#define MSM_INTF_PRIM 0 +#define MSM_INTF_SECN 1 +#define MSM_INTF_BOTH 2 + +/* I2S Dir CTL */ +#define MSM_DIR_RX 0 +#define MSM_DIR_TX 1 +#define MSM_DIR_BOTH 2 +#define MSM_DIR_MAX 3 + +/* I2S HW Params */ +#define NO_OF_BITS_PER_SAMPLE 16 +#define I2S_MIC_SCLK_RATE 1536000 +static int msm9615_i2s_rx_ch = 1; +static int msm9615_i2s_tx_ch = 1; +static int msm9615_i2s_spk_control; + +/* SIF mux bit mask & shift */ +#define LPASS_SIF_MUX_CTL_PRI_MUX_SEL_BMSK 0x30000 +#define LPASS_SIF_MUX_CTL_PRI_MUX_SEL_SHFT 0x10 +#define LPASS_SIF_MUX_CTL_SEC_MUX_SEL_BMSK 0x3 +#define LPASS_SIF_MUX_CTL_SEC_MUX_SEL_SHFT 0x0 + +#define LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_BMSK 0x3 +#define LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_SHFT 0x2 +#define LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_BMSK 0x3 +#define LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_SHFT 0x0 + +static u32 spare_shadow; +static u32 sif_shadow; + +static atomic_t msm9615_auxpcm_ref; +static atomic_t msm9615_sec_auxpcm_ref; + +struct msm_i2s_mux_ctl { + const u8 sifconfig; + const u8 spareconfig; +}; +struct msm_clk { + struct clk *osr_clk; + struct clk *bit_clk; + int clk_enable; +}; +struct msm_i2s_clk { + struct msm_clk rx_clk; + struct msm_clk tx_clk; +}; +struct msm_i2s_ctl { + struct msm_i2s_clk prim_clk; + struct msm_i2s_clk sec_clk; + struct msm_i2s_mux_ctl mux_ctl[MSM_DIR_MAX]; + u8 intf_status[MSM_INTF_BOTH][MSM_DIR_BOTH]; + void *sif_virt_addr; + void *spare_virt_addr; +}; +static struct msm_i2s_ctl msm9x15_i2s_ctl = { + {{NULL, NULL, 0}, {NULL, NULL, 0} }, /* prim_clk */ + {{NULL, NULL, 0}, {NULL, NULL, 0} }, /* sec_clk */ + /* mux_ctl */ + { + /* Rx path only */ + { MSM_SIF_FUNC_I2S_SPKR, MSM_LPAIF_SPARE_DISABLE }, + /* Tx path only */ + { MSM_SIF_FUNC_I2S_MIC, MSM_LPAIF_SPARE_DISABLE }, + /* Rx + Tx path only */ + { MSM_SIF_FUNC_I2S_SPKR, MSM_LPAIF_SPARE_BOTH_ENABLE }, + }, + /* intf_status */ + { + /* Prim I2S */ + {0, 0}, + /* Sec I2S */ + {0, 0} + }, + /* sif_virt_addr */ + NULL, + /* spare_virt_addr */ + NULL, +}; + +enum msm9x15_set_i2s_clk { + MSM_I2S_CLK_SET_FALSE, + MSM_I2S_CLK_SET_TRUE, + MSM_I2S_CLK_SET_RATE0, +}; +/* + * Added for I2S + */ + +static u32 top_spk_pamp_gpio = PM8018_GPIO_PM_TO_SYS(3); +static u32 bottom_spk_pamp_gpio = PM8018_GPIO_PM_TO_SYS(5); + +void *sif_virt_addr; +void *secpcm_portslc_virt_addr; + +static int mdm9615_spk_control; +static int mdm9615_ext_bottom_spk_pamp; +static int mdm9615_ext_top_spk_pamp; +static int mdm9615_slim_0_rx_ch = 1; +static int mdm9615_slim_0_tx_ch = 1; + +static int mdm9615_btsco_rate = BTSCO_RATE_8KHZ; +static int mdm9615_btsco_ch = 1; + +static struct clk *codec_clk; +static int clk_users; + +static int mdm9615_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int mdm9615_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = mdm9615_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void mdm9615_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + if (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void mdm9615_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + mdm9615_ext_bottom_spk_pamp |= spk; + + if ((mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (mdm9615_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + mdm9615_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + mdm9615_ext_top_spk_pamp |= spk; + + if ((mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (mdm9615_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + mdm9615_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void mdm9615_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!mdm9615_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + mdm9615_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!mdm9615_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + mdm9615_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void mdm9615_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: mdm9615_spk_control = %d", __func__, mdm9615_spk_control); + if (mdm9615_spk_control == MDM9615_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int mdm9615_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_spk_control = %d", __func__, mdm9615_spk_control); + ucontrol->value.integer.value[0] = mdm9615_spk_control; + return 0; +} +static int mdm9615_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (mdm9615_spk_control == ucontrol->value.integer.value[0]) + return 0; + + mdm9615_spk_control = ucontrol->value.integer.value[0]; + mdm9615_ext_control(codec); + return 1; +} +static int mdm9615_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + mdm9615_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + mdm9615_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + mdm9615_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + mdm9615_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + mdm9615_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + mdm9615_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + mdm9615_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + mdm9615_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} +static int mdm9615_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + if (IS_ERR(codec_clk)) { + + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int mdm9615_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return mdm9615_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return mdm9615_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget mdm9615_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + mdm9615_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", mdm9615_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", mdm9615_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", mdm9615_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", mdm9615_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum mdm9615_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum mdm9615_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int mdm9615_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_slim_0_rx_ch = %d\n", __func__, + mdm9615_slim_0_rx_ch); + ucontrol->value.integer.value[0] = mdm9615_slim_0_rx_ch - 1; + return 0; +} + +static int mdm9615_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mdm9615_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: mdm9615_slim_0_rx_ch = %d\n", __func__, + mdm9615_slim_0_rx_ch); + return 1; +} + +static int mdm9615_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_slim_0_tx_ch = %d\n", __func__, + mdm9615_slim_0_tx_ch); + ucontrol->value.integer.value[0] = mdm9615_slim_0_tx_ch - 1; + return 0; +} + +static int mdm9615_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mdm9615_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: mdm9615_slim_0_tx_ch = %d\n", __func__, + mdm9615_slim_0_tx_ch); + return 1; +} + +static int mdm9615_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: mdm9615_btsco_rate = %d", __func__, mdm9615_btsco_rate); + ucontrol->value.integer.value[0] = mdm9615_btsco_rate; + return 0; +} + +static int mdm9615_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + mdm9615_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + mdm9615_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + mdm9615_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: mdm9615_btsco_rate = %d\n", __func__, mdm9615_btsco_rate); + return 0; +} + +static const struct snd_kcontrol_new tabla_mdm9615_controls[] = { + SOC_ENUM_EXT("Speaker Function", mdm9615_enum[0], mdm9615_get_spk, + mdm9615_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", mdm9615_enum[1], + mdm9615_slim_0_rx_ch_get, mdm9615_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", mdm9615_enum[2], + mdm9615_slim_0_tx_ch_get, mdm9615_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", mdm9615_btsco_enum[0], + mdm9615_btsco_rate_get, mdm9615_btsco_rate_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm9615_i2s_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm9615_i2s_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm9615_i2s_spk_control = ucontrol->value.integer.value[0]; + mdm9615_ext_control(codec); + return 1; +} + +static int mdm9615_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + mdm9615_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + mdm9615_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + mdm9615_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + mdm9615_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + mdm9615_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } +end: + return ret; +} + +static int msm9615_i2s_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_i2s_rx_ch = %d\n", __func__, + msm9615_i2s_rx_ch); + ucontrol->value.integer.value[0] = msm9615_i2s_rx_ch - 1; + return 0; +} + +static int msm9615_i2s_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm9615_i2s_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm9615_i2s_rx_ch = %d\n", __func__, + msm9615_i2s_rx_ch); + return 1; +} + +static int msm9615_i2s_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_i2s_tx_ch = %d\n", __func__, + msm9615_i2s_tx_ch); + ucontrol->value.integer.value[0] = msm9615_i2s_tx_ch - 1; + return 0; +} + +static int msm9615_i2s_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm9615_i2s_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm9615_i2s_tx_ch = %d\n", __func__, + msm9615_i2s_tx_ch); + return 1; +} + +static int msm9615_i2s_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm9615_spk_control = %d", __func__, mdm9615_spk_control); + ucontrol->value.integer.value[0] = msm9615_i2s_spk_control; + return 0; +} + +static const struct snd_kcontrol_new tabla_msm9615_i2s_controls[] = { + SOC_ENUM_EXT("Speaker Function", mdm9615_enum[0], msm9615_i2s_get_spk, + msm9615_i2s_set_spk), + SOC_ENUM_EXT("PRI_RX Channels", mdm9615_enum[1], + msm9615_i2s_rx_ch_get, msm9615_i2s_rx_ch_put), + SOC_ENUM_EXT("PRI_TX Channels", mdm9615_enum[2], + msm9615_i2s_tx_ch_get, msm9615_i2s_tx_ch_put), +}; + +static int msm9615_i2s_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + snd_soc_dapm_new_controls(dapm, mdm9615_dapm_widgets, + ARRAY_SIZE(mdm9615_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL| + SND_JACK_OC_HPHR), &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + err = tabla_hs_detect(codec, &mbhc_cfg); + return err; +} + +static int msm9615_i2s_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + rate->min = rate->max = 48000; + channels->min = channels->max = msm9615_i2s_rx_ch; + + return 0; +} + +static int msm9615_i2s_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + rate->min = rate->max = 48000; + + channels->min = channels->max = msm9615_i2s_tx_ch; + + return 0; +} + +static int mdm9615_i2s_free_gpios(u8 i2s_intf, u8 i2s_dir) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + if (i2s_dir == MSM_DIR_RX) + gpio_free(GPIO_PRIM_I2S_DOUT); + if (i2s_dir == MSM_DIR_TX) + gpio_free(GPIO_PRIM_I2S_DIN); + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + gpio_free(GPIO_PRIM_I2S_SCK); + gpio_free(GPIO_PRIM_I2S_WS); + } + } else if (i2s_intf == MSM_INTF_SECN) { + if (i2s_dir == MSM_DIR_RX) + gpio_free(GPIO_SEC_I2S_DOUT); + if (i2s_dir == MSM_DIR_TX) + gpio_free(GPIO_SEC_I2S_DIN); + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + gpio_free(GPIO_SEC_I2S_WS); + gpio_free(GPIO_SEC_I2S_SCK); + } + } + return 0; +} + +int msm9615_i2s_intf_dir_sel(const char *cpu_dai_name, + u8 *i2s_intf, u8 *i2s_dir) +{ + int ret = 0; + if (i2s_intf == NULL || i2s_dir == NULL || cpu_dai_name == NULL) { + ret = 1; + goto err; + } + if (!strncmp(cpu_dai_name, "msm-dai-q6.0", 12)) { + *i2s_intf = MSM_INTF_PRIM; + *i2s_dir = MSM_DIR_RX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.1", 12)) { + *i2s_intf = MSM_INTF_PRIM; + *i2s_dir = MSM_DIR_TX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.4", 12)) { + *i2s_intf = MSM_INTF_SECN; + *i2s_dir = MSM_DIR_RX; + } else if (!strncmp(cpu_dai_name, "msm-dai-q6.5", 12)) { + *i2s_intf = MSM_INTF_SECN; + *i2s_dir = MSM_DIR_TX; + } else { + pr_err("Error in I2S cpu dai name\n"); + ret = 1; + } +err: + return ret; +} + +int msm9615_enable_i2s_gpio(u8 i2s_intf, u8 i2s_dir) +{ + u8 ret = 0; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + if (i2s_dir == MSM_DIR_TX) { + ret = gpio_request(GPIO_PRIM_I2S_DIN, "I2S_PRIM_DIN"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_DIN); + goto err; + } + } else if (i2s_dir == MSM_DIR_RX) { + ret = gpio_request(GPIO_PRIM_I2S_DOUT, + "I2S_PRIM_DOUT"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_DOUT); + goto err; + } + } else if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + ret = gpio_request(GPIO_PRIM_I2S_SCK, "I2S_PRIM_SCK"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_SCK); + goto err; + } + ret = gpio_request(GPIO_PRIM_I2S_WS, "I2S_PRIM_WS"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_PRIM_I2S_WS); + goto err; + } + } + } else if (i2s_intf == MSM_INTF_SECN) { + if (i2s_dir == MSM_DIR_RX) { + ret = gpio_request(GPIO_SEC_I2S_DOUT, "I2S_SEC_DOUT"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_DOUT); + goto err; + } + } else if (i2s_dir == MSM_DIR_TX) { + ret = gpio_request(GPIO_SEC_I2S_DIN, "I2S_SEC_DIN"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_DIN); + goto err; + } + } else if (pintf->intf_status[i2s_intf][MSM_DIR_TX] == 0 && + pintf->intf_status[i2s_intf][MSM_DIR_RX] == 0) { + ret = gpio_request(GPIO_SEC_I2S_SCK, "I2S_SEC_SCK"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_SCK); + goto err; + } + ret = gpio_request(GPIO_SEC_I2S_WS, "I2S_SEC_WS"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", + __func__, GPIO_SEC_I2S_WS); + goto err; + } + } + } +err: + return ret; +} + +static int msm9615_set_i2s_osr_bit_clk(struct snd_soc_dai *cpu_dai, + u8 i2s_intf, u8 i2s_dir, + enum msm9x15_set_i2s_clk enable) +{ + + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + struct msm_i2s_clk *pclk = &pintf->prim_clk; + struct msm_clk *clk_ctl = &pclk->rx_clk; + u8 ret = 0; + pr_debug("Dev name %s Intf =%d, Dir = %d, Enable=%d\n", + cpu_dai->name, i2s_intf, i2s_dir, enable); + if (i2s_intf == MSM_INTF_PRIM) + pclk = &pintf->prim_clk; + else if (i2s_intf == MSM_INTF_SECN) + pclk = &pintf->sec_clk; + + if (i2s_dir == MSM_DIR_TX) + clk_ctl = &pclk->tx_clk; + else if (i2s_dir == MSM_DIR_RX) + clk_ctl = &pclk->rx_clk; + + if (enable == MSM_I2S_CLK_SET_TRUE || + enable == MSM_I2S_CLK_SET_RATE0) { + if (clk_ctl->clk_enable != 0) { + pr_info("%s: I2S Clk is already enabled" + "clk users %d\n", __func__, + clk_ctl->clk_enable); + ret = 0; + goto err; + } + clk_ctl->osr_clk = clk_get(cpu_dai->dev, "osr_clk"); + if (IS_ERR(clk_ctl->osr_clk)) { + pr_err("%s: Fail to get OSR CLK\n", __func__); + ret = -EINVAL; + goto err; + } + ret = clk_prepare(clk_ctl->osr_clk); + if (ret != 0) { + pr_err("Unable to prepare i2s_spkr_osr_clk\n"); + goto err; + } + clk_set_rate(clk_ctl->osr_clk, TABLA_EXT_CLK_RATE); + ret = clk_enable(clk_ctl->osr_clk); + if (ret != 0) { + pr_err("Fail to enable i2s_spkr_osr_clk\n"); + clk_unprepare(clk_ctl->osr_clk); + goto err; + } + clk_ctl->bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(clk_ctl->bit_clk)) { + pr_err("Fail to get i2s_spkr_bit_clk\n"); + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + ret = -EINVAL; + goto err; + } + ret = clk_prepare(clk_ctl->bit_clk); + if (ret != 0) { + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + pr_err("Fail to prepare i2s_spkr_osr_clk\n"); + goto err; + } + if (enable == MSM_I2S_CLK_SET_RATE0) + clk_set_rate(clk_ctl->bit_clk, 0); + else + clk_set_rate(clk_ctl->bit_clk, 8); + ret = clk_enable(clk_ctl->bit_clk); + if (ret != 0) { + clk_disable(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_put(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->bit_clk); + pr_err("Unable to enable i2s_spkr_osr_clk\n"); + goto err; + } + clk_ctl->clk_enable++; + } else if (enable == MSM_I2S_CLK_SET_FALSE && + clk_ctl->clk_enable != 0) { + clk_disable(clk_ctl->osr_clk); + clk_disable(clk_ctl->bit_clk); + clk_unprepare(clk_ctl->osr_clk); + clk_unprepare(clk_ctl->bit_clk); + clk_put(clk_ctl->bit_clk); + clk_put(clk_ctl->osr_clk); + clk_ctl->bit_clk = NULL; + clk_ctl->osr_clk = NULL; + clk_ctl->clk_enable--; + ret = 0; + } +err: + return ret; +} + +void msm9615_config_i2s_sif_mux(u8 value) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + sif_shadow = 0x00000; + sif_shadow = (sif_shadow & LPASS_SIF_MUX_CTL_PRI_MUX_SEL_BMSK) | + (value << LPASS_SIF_MUX_CTL_PRI_MUX_SEL_SHFT); + iowrite32(sif_shadow, pintf->sif_virt_addr); + /* Dont read SIF register. Device crashes. */ + pr_debug("%s() SIF Reg = 0x%x\n", __func__, sif_shadow); +} + +void msm9615_config_i2s_spare_mux(u8 value, u8 i2s_intf) +{ + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + if (i2s_intf == MSM_INTF_PRIM) { + /* Configure Primary SIF */ + spare_shadow = (spare_shadow & LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_BMSK + ) | (value << LPAIF_SPARE_MUX_CTL_PRI_MUX_SEL_SHFT); + } + if (i2s_intf == MSM_INTF_SECN) { + /*Secondary interface configuration*/ + spare_shadow = (spare_shadow & LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_BMSK + ) | (value << LPAIF_SPARE_MUX_CTL_SEC_MUX_SEL_SHFT); + } + iowrite32(spare_shadow, pintf->spare_virt_addr); + /* Dont read SPARE register. Device crashes. */ + pr_debug("%s( ): SPARE Reg =0x%x\n", __func__, spare_shadow); +} + +static int msm9615_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + struct msm_i2s_clk *pclk = &pintf->prim_clk; + struct msm_clk *clk_ctl = &pclk->rx_clk; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int bit_clk_set = 0; + u8 i2s_intf, i2s_dir; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, + &i2s_intf, &i2s_dir)) { + bit_clk_set = TABLA_EXT_CLK_RATE / + (rate * 2 * NO_OF_BITS_PER_SAMPLE); + if (bit_clk_set != 8) { + if (i2s_intf == MSM_INTF_PRIM) + pclk = &pintf->prim_clk; + else if (i2s_intf == MSM_INTF_SECN) + pclk = &pintf->sec_clk; + clk_ctl = &pclk->rx_clk; + pr_debug("%s( ): New rate = %d", + __func__, bit_clk_set); + clk_set_rate(clk_ctl->bit_clk, bit_clk_set); + } + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + bit_clk_set = I2S_MIC_SCLK_RATE / (rate * 2 * + NO_OF_BITS_PER_SAMPLE); + /* Not required to modify TX rate. + * Speaker clock are looped back + * to Mic. + */ + } + return 1; +} + +static int msm9615_i2s_startup(struct snd_pcm_substream *substream) +{ + u8 ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + u8 i2s_intf, i2s_dir; + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, &i2s_intf, &i2s_dir)) { + pr_debug("%s( ): cpu name = %s intf =%d dir = %d\n", + __func__, cpu_dai->name, i2s_intf, i2s_dir); + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + msm9615_enable_i2s_gpio(i2s_intf, i2s_dir); + if (i2s_dir == MSM_DIR_TX) { + if (pintf->intf_status[i2s_intf][MSM_DIR_RX] > 0) { + /* This means that Rx is enabled before */ + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_RATE0); + if (ret != 0) { + pr_err("%s: Fail enable I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_BOTH].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_BOTH].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } else if (pintf->intf_status[i2s_intf][i2s_dir] == 0) { + /* This means that Rx is + * not enabled before. + * only Tx will be used. + */ + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_TRUE); + if (ret != 0) { + pr_err("%s: Fail Tx I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_TX].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_TX].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } + } else if (i2s_dir == MSM_DIR_RX) { + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] > 0) { + pr_err("%s: Error shutdown Tx first\n", + __func__); + return -EINVAL; + } else if (pintf->intf_status[i2s_intf][i2s_dir] + == 0) { + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, + i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_TRUE); + if (ret != 0) { + pr_err("%s: Fail Rx I2S clock\n", + __func__); + return -EINVAL; + } + msm9615_config_i2s_sif_mux( + pintf->mux_ctl[MSM_DIR_RX].sifconfig); + msm9615_config_i2s_spare_mux( + pintf->mux_ctl[MSM_DIR_RX].spareconfig, + i2s_intf); + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt cpu dai failed\n"); + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set fmt codec dai failed\n"); + } + } + pintf->intf_status[i2s_intf][i2s_dir]++; + } else { + pr_err("%s: Err in i2s_intf_dir_sel\n", __func__); + return -EINVAL; + } + pr_debug("Exit %s() Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + return ret; +} + +static void msm9615_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct msm_i2s_ctl *pintf = &msm9x15_i2s_ctl; + u8 i2s_intf = 0, i2s_dir = 0, ret = 0; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", + __func__, pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); + if (!msm9615_i2s_intf_dir_sel(cpu_dai->name, &i2s_intf, &i2s_dir)) { + pr_debug("%s( ): intf =%d dir = %d\n", __func__, + i2s_intf, i2s_dir); + if (i2s_dir == MSM_DIR_RX) + if (pintf->intf_status[i2s_intf][MSM_DIR_TX] > 0) + pr_err("%s: Shutdown Tx First then by RX\n", + __func__); + ret = msm9615_set_i2s_osr_bit_clk(cpu_dai, i2s_intf, i2s_dir, + MSM_I2S_CLK_SET_FALSE); + if (ret != 0) + pr_err("%s: Cannot disable I2S clock\n", + __func__); + pintf->intf_status[i2s_intf][i2s_dir]--; + mdm9615_i2s_free_gpios(i2s_intf, i2s_dir); + } + pr_debug("%s( ): Enable status Rx =%d Tx = %d\n", __func__, + pintf->intf_status[i2s_intf][MSM_DIR_RX], + pintf->intf_status[i2s_intf][MSM_DIR_TX]); +} + +static struct snd_soc_ops msm9615_i2s_be_ops = { + .startup = msm9615_i2s_startup, + .shutdown = msm9615_i2s_shutdown, + .hw_params = msm9615_i2s_hw_params, +}; + +static int mdm9615_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + rtd->pmdown_time = 0; + + snd_soc_dapm_new_controls(dapm, mdm9615_dapm_widgets, + ARRAY_SIZE(mdm9615_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | + SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int mdm9615_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = mdm9615_slim_0_rx_ch; + + return 0; +} + +static int mdm9615_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = mdm9615_slim_0_tx_ch; + + return 0; +} + +static int mdm9615_btsco_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = mdm9615_btsco_rate; + channels->min = channels->max = mdm9615_btsco_ch; + + return 0; +} +static int mdm9615_auxpcm_be_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} +static int mdm9615_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int mdm9615_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} + +static int mdm9615_sec_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_SEC_AUX_PCM_DOUT, "SEC_AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): SEC_AUX PCM DOUT", + __func__, GPIO_SEC_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_SEC_AUX_PCM_DIN, "SEC_AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): SEC_AUX PCM DIN", + __func__, GPIO_SEC_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_SEC_AUX_PCM_SYNC, "SEC_AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): SEC_AUX PCM SYNC", + __func__, GPIO_SEC_AUX_PCM_SYNC); + goto fail_sync; + } + + ret = gpio_request(GPIO_SEC_AUX_PCM_CLK, "SEC_AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): SEC_AUX PCM CLK", + __func__, GPIO_SEC_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_SEC_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_SEC_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_SEC_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int mdm9615_sec_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_SEC_AUX_PCM_DIN); + gpio_free(GPIO_SEC_AUX_PCM_DOUT); + gpio_free(GPIO_SEC_AUX_PCM_SYNC); + gpio_free(GPIO_SEC_AUX_PCM_CLK); + + return 0; +} + +static int mdm9615_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +void msm9615_config_sif_mux(u8 value) +{ + u32 sif_shadow = 0x00000; + + sif_shadow = (sif_shadow & LPASS_SIF_MUX_CTL_SEC_MUX_SEL_BMSK) | + (value << LPASS_SIF_MUX_CTL_SEC_MUX_SEL_SHFT); + iowrite32(sif_shadow, sif_virt_addr); + /* Dont read SIF register. Device crashes. */ + pr_debug("%s() SIF Reg = 0x%x\n", __func__, sif_shadow); +} + +void msm9615_config_port_select(void) +{ + pr_debug("%s() port select defualt = 0x%x\n", + __func__, ioread32(secpcm_portslc_virt_addr)); + iowrite32(SEC_PCM_PORT_SLC_VALUE, secpcm_portslc_virt_addr); + pr_debug("%s() port select after updating = 0x%x\n", + __func__, ioread32(secpcm_portslc_virt_addr)); +} + +static int mdm9615_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + if (atomic_inc_return(&msm9615_auxpcm_ref) == 1) { + ret = mdm9615_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + } + return 0; +} + +static void mdm9615_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + if (atomic_dec_return(&msm9615_auxpcm_ref) == 0) + mdm9615_aux_pcm_free_gpios(); +} + +static int mdm9615_sec_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + if (atomic_inc_return(&msm9615_sec_auxpcm_ref) == 1) { + ret = mdm9615_sec_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: SEC Aux PCM GPIO request failed\n", + __func__); + return -EINVAL; + } + msm9615_config_sif_mux(MSM_SIF_FUNC_PCM); + msm9615_config_port_select(); + } + return 0; +} + +static void mdm9615_sec_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s\n", __func__, substream->name); + if (atomic_dec_return(&msm9615_sec_auxpcm_ref) == 0) + mdm9615_sec_aux_pcm_free_gpios(); +} + +static void mdm9615_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops mdm9615_be_ops = { + .startup = mdm9615_startup, + .hw_params = mdm9615_hw_params, + .shutdown = mdm9615_shutdown, +}; + +static struct snd_soc_ops mdm9615_auxpcm_be_ops = { + .startup = mdm9615_auxpcm_startup, + .shutdown = mdm9615_auxpcm_shutdown, +}; + +static struct snd_soc_ops mdm9615_sec_auxpcm_be_ops = { + .startup = mdm9615_sec_auxpcm_startup, + .shutdown = mdm9615_sec_auxpcm_shutdown, +}; + + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mdm9615_dai_common[] = { + /* FrontEnd DAI Links */ + { + .name = "MDM9615 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MDM9615 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + { + .name = "VoLTE", + .stream_name = "VoLTE", + .cpu_dai_name = "VoLTE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .be_id = MSM_FRONTEND_DAI_VOLTE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + }, + /* Backend BT DAI Links */ + { + .name = LPASS_BE_INT_BT_SCO_RX, + .stream_name = "Internal BT-SCO Playback", + .cpu_dai_name = "msm-dai-q6.12288", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX, + .be_hw_params_fixup = mdm9615_btsco_be_hw_params_fixup, + }, + { + .name = LPASS_BE_INT_BT_SCO_TX, + .stream_name = "Internal BT-SCO Capture", + .cpu_dai_name = "msm-dai-q6.12289", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX, + .be_hw_params_fixup = mdm9615_btsco_be_hw_params_fixup, + }, + + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_RX, + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_RX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + .ops = &mdm9615_auxpcm_be_ops, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_TX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + .ops = &mdm9615_auxpcm_be_ops, + }, + + /* SECONDARY AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_SEC_AUXPCM_RX, + .stream_name = "SEC AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.12", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SEC_AUXPCM_RX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + .ops = &mdm9615_sec_auxpcm_be_ops, + }, + { + .name = LPASS_BE_SEC_AUXPCM_TX, + .stream_name = "SEC AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.13", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SEC_AUXPCM_TX, + .be_hw_params_fixup = mdm9615_auxpcm_be_params_fixup, + .ops = &mdm9615_sec_auxpcm_be_ops, + }, +}; + +static struct snd_soc_dai_link mdm9615_dai_i2s_tabla[] = { + /* Backend I2S DAI Links */ + { + .name = LPASS_BE_PRI_I2S_RX, + .stream_name = "Primary I2S Playback", + .cpu_dai_name = "msm-dai-q6.0", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_i2s_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_PRI_I2S_RX, + .init = &msm9615_i2s_audrx_init, + .be_hw_params_fixup = msm9615_i2s_rx_be_hw_params_fixup, + .ops = &msm9615_i2s_be_ops, + }, + { + .name = LPASS_BE_PRI_I2S_TX, + .stream_name = "Primary I2S Capture", + .cpu_dai_name = "msm-dai-q6.1", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_i2s_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_PRI_I2S_TX, + .be_hw_params_fixup = msm9615_i2s_tx_be_hw_params_fixup, + .ops = &msm9615_i2s_be_ops, + }, +}; + +static struct snd_soc_dai_link mdm9615_dai_slimbus_tabla[] = { + /* Backend SlimBus DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &mdm9615_audrx_init, + .be_hw_params_fixup = mdm9615_slim_0_rx_be_hw_params_fixup, + .ops = &mdm9615_be_ops, + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = mdm9615_slim_0_tx_be_hw_params_fixup, + .ops = &mdm9615_be_ops, + }, +}; + +static struct snd_soc_dai_link mdm9615_i2s_dai[ + ARRAY_SIZE(mdm9615_dai_common) + + ARRAY_SIZE(mdm9615_dai_i2s_tabla)]; + +static struct snd_soc_dai_link mdm9615_slimbus_dai[ + ARRAY_SIZE(mdm9615_dai_common) + + ARRAY_SIZE(mdm9615_dai_slimbus_tabla)]; + + +static struct snd_soc_card snd_soc_card_mdm9615 = { + .name = "mdm9615-tabla-snd-card", + .controls = tabla_mdm9615_controls, + .num_controls = ARRAY_SIZE(tabla_mdm9615_controls), +}; + +static struct platform_device *mdm9615_snd_device; + +static int mdm9615_configure_headset_mic_gpios(void) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + ret = gpio_request(PM8018_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8018_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8018_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(PM8018_GPIO_PM_TO_SYS(35), "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(35)); + gpio_free(PM8018_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(PM8018_GPIO_PM_TO_SYS(35), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8018_GPIO_PM_TO_SYS(35)); + else + gpio_direction_output(PM8018_GPIO_PM_TO_SYS(35), 0); + + return 0; +} +static void mdm9615_free_headset_mic_gpios(void) +{ + if (mdm9615_headset_gpios_configured) { + gpio_free(PM8018_GPIO_PM_TO_SYS(23)); + gpio_free(PM8018_GPIO_PM_TO_SYS(35)); + } +} + +void __init install_codec_i2s_gpio(void) +{ + msm_gpiomux_install(msm9615_audio_prim_i2s_codec_configs, + ARRAY_SIZE(msm9615_audio_prim_i2s_codec_configs)); +} +static int __init mdm9615_audio_init(void) +{ + int ret; + + if (!cpu_is_msm9615()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + mdm9615_snd_device = platform_device_alloc("soc-audio", 0); + if (!mdm9615_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + pr_err("%s: Interface Type = %d\n", __func__, + wcd9xxx_get_intf_type()); + if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS) { + memcpy(mdm9615_slimbus_dai, mdm9615_dai_common, + sizeof(mdm9615_dai_common)); + memcpy(mdm9615_slimbus_dai + ARRAY_SIZE(mdm9615_dai_common), + mdm9615_dai_slimbus_tabla, + sizeof(mdm9615_dai_slimbus_tabla)); + snd_soc_card_mdm9615.dai_link = mdm9615_slimbus_dai; + snd_soc_card_mdm9615.num_links = + ARRAY_SIZE(mdm9615_slimbus_dai); + } else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C) { + install_codec_i2s_gpio(); + memcpy(mdm9615_i2s_dai, mdm9615_dai_common, + sizeof(mdm9615_dai_common)); + memcpy(mdm9615_i2s_dai + ARRAY_SIZE(mdm9615_dai_common), + mdm9615_dai_i2s_tabla, + sizeof(mdm9615_dai_i2s_tabla)); + snd_soc_card_mdm9615.dai_link = mdm9615_i2s_dai; + snd_soc_card_mdm9615.num_links = + ARRAY_SIZE(mdm9615_i2s_dai); + } else{ + snd_soc_card_mdm9615.dai_link = mdm9615_dai_common; + snd_soc_card_mdm9615.num_links = + ARRAY_SIZE(mdm9615_dai_common); + } + + platform_set_drvdata(mdm9615_snd_device, &snd_soc_card_mdm9615); + ret = platform_device_add(mdm9615_snd_device); + if (ret) { + platform_device_put(mdm9615_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + if (mdm9615_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + mdm9615_headset_gpios_configured = 0; + } else + mdm9615_headset_gpios_configured = 1; + + atomic_set(&msm9615_auxpcm_ref, 0); + atomic_set(&msm9615_sec_auxpcm_ref, 0); + msm9x15_i2s_ctl.sif_virt_addr = ioremap(LPASS_SIF_MUX_ADDR, 4); + msm9x15_i2s_ctl.spare_virt_addr = ioremap(LPAIF_SPARE_ADDR, 4); + sif_virt_addr = ioremap(LPASS_SIF_MUX_ADDR, 4); + secpcm_portslc_virt_addr = ioremap(SEC_PCM_PORT_SLC_ADDR, 4); + + return ret; +} +module_init(mdm9615_audio_init); + +static void __exit mdm9615_audio_exit(void) +{ + if (!cpu_is_msm9615()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + mdm9615_free_headset_mic_gpios(); + platform_device_unregister(mdm9615_snd_device); + kfree(mbhc_cfg.calibration); + iounmap(msm9x15_i2s_ctl.sif_virt_addr); + iounmap(msm9x15_i2s_ctl.spare_virt_addr); + iounmap(sif_virt_addr); + iounmap(secpcm_portslc_virt_addr); + +} +module_exit(mdm9615_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MDM9615"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/mpq8064.c b/sound/soc/msm/mpq8064.c new file mode 100644 index 000000000000..52ebbdce4055 --- /dev/null +++ b/sound/soc/msm/mpq8064.c @@ -0,0 +1,1470 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wcd9310.h" + +/* 8064 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MPQ8064_SPK_ON 1 +#define MPQ8064_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define GPIO_SEC_I2S_RX_SCK 47 +#define GPIO_SEC_I2S_RX_WS 48 +#define GPIO_SEC_I2S_RX_DOUT 49 +#define GPIO_SEC_I2S_RX_MCLK 50 +#define I2S_MCLK_RATE 1536000 + +#define GPIO_MI2S_WS 27 +#define GPIO_MI2S_SCLK 28 +#define GPIO_MI2S_DOUT3 29 +#define GPIO_MI2S_DOUT2 30 +#define GPIO_MI2S_DOUT1 31 +#define GPIO_MI2S_DOUT0 32 +#define GPIO_MI2S_MCLK 33 + +static struct clk *sec_i2s_rx_osr_clk; +static struct clk *sec_i2s_rx_bit_clk; + +struct request_gpio { + unsigned gpio_no; + char *gpio_name; +}; + +static struct request_gpio sec_i2s_rx_gpio[] = { + { + .gpio_no = GPIO_SEC_I2S_RX_MCLK, + .gpio_name = "SEC_I2S_RX_MCLK", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_SCK, + .gpio_name = "SEC_I2S_RX_SCK", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_WS, + .gpio_name = "SEC_I2S_RX_WS", + }, + { + .gpio_no = GPIO_SEC_I2S_RX_DOUT, + .gpio_name = "SEC_I2S_RX_DOUT", + }, +}; + +static struct request_gpio mi2s_gpio[] = { + { + .gpio_no = GPIO_MI2S_WS, + .gpio_name = "MI2S_WS", + }, + { + .gpio_no = GPIO_MI2S_SCLK, + .gpio_name = "MI2S_SCLK", + }, + { + .gpio_no = GPIO_MI2S_DOUT3, + .gpio_name = "MI2S_DOUT3", + }, + { + .gpio_no = GPIO_MI2S_DOUT2, + .gpio_name = "MI2S_DOUT2", + }, + { + .gpio_no = GPIO_MI2S_DOUT1, + .gpio_name = "MI2S_DOUT1", + }, + { + .gpio_no = GPIO_MI2S_DOUT0, + .gpio_name = "MI2S_DOUT0", + }, + { + .gpio_no = GPIO_MI2S_MCLK, + .gpio_name = "MI2S_MCLK", + }, +}; + +static struct clk *mi2s_bit_clk; + + + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; +static int msm_hdmi_rx_ch = 2; + +static struct clk *codec_clk; +static int clk_users; + +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, /* MBHC GPIO is not configured */ + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + . + function = PM_GPIO_FUNC_NORMAL, + }; + + if (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MPQ8064_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + clk_disable_unprepare(codec_clk); + tabla_mclk_enable(codec, 0, dapm); + } + } + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, 12288000); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(w->codec, 1, true); + + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + break; + case SND_SOC_DAPM_POST_PMD: + + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + + if (clk_users == 0) + return 0; + + clk_users--; + + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + + clk_disable_unprepare(codec_clk); + tabla_mclk_enable(w->codec, 0, true); + } + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget msm_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; +static const char *hdmi_rx_ch_text[] = {"Two", "Three", "Four", "Five", "Six"}; + + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), + SOC_ENUM_SINGLE_EXT(5, hdmi_rx_ch_text), + +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_hdmi_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_hdmi_rx_ch = %d\n", __func__, + msm_hdmi_rx_ch); + ucontrol->value.integer.value[0] = msm_hdmi_rx_ch - 2; + return 0; +} + +static int msm_hdmi_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_hdmi_rx_ch = ucontrol->value.integer.value[0] + 2; + + pr_debug("%s: msm_hdmi_rx_ch = %d\n", __func__, + msm_hdmi_rx_ch); + return 1; +} + + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), + SOC_ENUM_EXT("HDMI_RX Channels", msm_enum[3], + msm_hdmi_rx_ch_get, msm_hdmi_rx_ch_put), + +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1550); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + msm_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + msm_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + msm_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + + } +end: + return ret; +} + +static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + snd_soc_dapm_new_controls(dapm, msm_dapm_widgets, + ARRAY_SIZE(msm_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int msm_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm_slim_0_rx_ch; + + return 0; +} + +static int msm_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm_slim_0_tx_ch; + + return 0; +} + +static int msm_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + + return 0; +} + +static int msm_hdmi_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + channels->min = channels->max = msm_hdmi_rx_ch; + + return 0; +} + +static int msm_mi2s_free_gpios(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mi2s_gpio); i++) + gpio_free(mi2s_gpio[i].gpio_no); + return 0; +} + +static void msm_mi2s_shutdown(struct snd_pcm_substream *substream) +{ + if (mi2s_bit_clk) { + clk_disable(mi2s_bit_clk); + clk_put(mi2s_bit_clk); + mi2s_bit_clk = NULL; + } + msm_mi2s_free_gpios(); +} + +static int configure_mi2s_gpio(void) +{ + int rtn; + int i; + int j; + for (i = 0; i < ARRAY_SIZE(mi2s_gpio); i++) { + rtn = gpio_request(mi2s_gpio[i].gpio_no, + mi2s_gpio[i].gpio_name); + pr_debug("%s: gpio = %d, gpio name = %s, rtn = %d\n", + __func__, + mi2s_gpio[i].gpio_no, + mi2s_gpio[i].gpio_name, + rtn); + if (rtn) { + pr_err("%s: Failed to request gpio %d\n", + __func__, + mi2s_gpio[i].gpio_no); + for (j = i; j >= 0; j--) + gpio_free(mi2s_gpio[j].gpio_no); + goto err; + } + } +err: + return rtn; +} +static int msm_mi2s_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + configure_mi2s_gpio(); + mi2s_bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(mi2s_bit_clk)) + return PTR_ERR(mi2s_bit_clk); + clk_set_rate(mi2s_bit_clk, 0); + ret = clk_enable(mi2s_bit_clk); + if (IS_ERR_VALUE(ret)) { + pr_err("Unable to enable mi2s_bit_clk\n"); + clk_put(mi2s_bit_clk); + return ret; + } + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (IS_ERR_VALUE(ret)) + pr_err("set format for CPU dai failed\n"); + return ret; +} + +static int msm_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static void msm_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm_be_ops = { + .startup = msm_startup, + .hw_params = msm_hw_params, + .shutdown = msm_shutdown, +}; + +static int mpq8064_sec_i2s_rx_free_gpios(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(sec_i2s_rx_gpio); i++) + gpio_free(sec_i2s_rx_gpio[i].gpio_no); + return 0; +} + +static int mpq8064_sec_i2s_rx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + int rate = params_rate(params); + int bit_clk_set = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bit_clk_set = I2S_MCLK_RATE/(rate * 2 * 16); + clk_set_rate(sec_i2s_rx_bit_clk, bit_clk_set); + break; + case SNDRV_PCM_FORMAT_S24_LE: + bit_clk_set = I2S_MCLK_RATE/(rate * 2 * 24); + clk_set_rate(sec_i2s_rx_bit_clk, bit_clk_set); + break; + default: + pr_err("wrong format\n"); + break; + } + } + return 0; +} + +static void mpq8064_sec_i2s_rx_shutdown(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sec_i2s_rx_bit_clk) { + clk_disable_unprepare(sec_i2s_rx_bit_clk); + clk_put(sec_i2s_rx_bit_clk); + sec_i2s_rx_bit_clk = NULL; + } + if (sec_i2s_rx_osr_clk) { + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + sec_i2s_rx_osr_clk = NULL; + } + mpq8064_sec_i2s_rx_free_gpios(); + } + pr_info("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static int configure_sec_i2s_rx_gpio(void) +{ + int rtn; + int i; + int j; + for (i = 0; i < ARRAY_SIZE(sec_i2s_rx_gpio); i++) { + rtn = gpio_request(sec_i2s_rx_gpio[i].gpio_no, + sec_i2s_rx_gpio[i].gpio_name); + pr_debug("%s: gpio = %d, gpio name = %s, rtn = %d\n", + __func__, + sec_i2s_rx_gpio[i].gpio_no, + sec_i2s_rx_gpio[i].gpio_name, + rtn); + if (rtn) { + pr_err("%s: Failed to request gpio %d\n", + __func__, + sec_i2s_rx_gpio[i].gpio_no); + for (j = i; j >= 0; j--) + gpio_free(sec_i2s_rx_gpio[j].gpio_no); + + goto err; + } + } +err: + return rtn; +} + +static int mpq8064_sec_i2s_rx_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + configure_sec_i2s_rx_gpio(); + sec_i2s_rx_osr_clk = clk_get(cpu_dai->dev, "osr_clk"); + if (IS_ERR(sec_i2s_rx_osr_clk)) { + pr_err("Failed to get sec_i2s_rx_osr_clk\n"); + return PTR_ERR(sec_i2s_rx_osr_clk); + } + clk_set_rate(sec_i2s_rx_osr_clk, I2S_MCLK_RATE); + clk_prepare_enable(sec_i2s_rx_osr_clk); + sec_i2s_rx_bit_clk = clk_get(cpu_dai->dev, "bit_clk"); + if (IS_ERR(sec_i2s_rx_bit_clk)) { + pr_err("Failed to get sec i2s osr_clk\n"); + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + return PTR_ERR(sec_i2s_rx_bit_clk); + } + clk_set_rate(sec_i2s_rx_bit_clk, 1); + ret = clk_prepare_enable(sec_i2s_rx_bit_clk); + if (ret != 0) { + pr_err("Unable to enable sec i2s rx_bit_clk\n"); + clk_put(sec_i2s_rx_bit_clk); + clk_disable_unprepare(sec_i2s_rx_osr_clk); + clk_put(sec_i2s_rx_osr_clk); + return ret; + } + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + pr_err("set format for codec dai failed\n"); + } + pr_debug("%s: ret = %d\n", __func__, ret); + pr_info("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return ret; +} + +static struct snd_soc_ops mpq8064_sec_i2s_rx_be_ops = { + .startup = mpq8064_sec_i2s_rx_startup, + .shutdown = mpq8064_sec_i2s_rx_shutdown, + .hw_params = mpq8064_sec_i2s_rx_hw_params, +}; + +static struct snd_soc_ops msm_mi2s_tx_be_ops = { + .startup = msm_mi2s_startup, + .shutdown = msm_mi2s_shutdown, +}; + + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "Voice Stub", + .stream_name = "Voice Stub", + .cpu_dai_name = "VOICE_STUB", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* MI2S TX Hostless */ + { + .name = "MI2S_TX Hostless", + .stream_name = "MI2S_TX Hostless", + .cpu_dai_name = "MI2S_TX_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Secondary I2S RX Hostless */ + { + .name = "SEC_I2S_RX Hostless", + .stream_name = "SEC_I2S_RX Hostless", + .cpu_dai_name = "SEC_I2S_RX_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_BESPOKE, SND_SOC_DPCM_TRIGGER_BESPOKE}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm_audrx_init, + .be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup, + .ops = &msm_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm_slim_0_tx_be_hw_params_fixup, + .ops = &msm_be_ops, + }, + { + .name = LPASS_BE_SEC_I2S_RX, + .stream_name = "Secondary I2S Playback", + .cpu_dai_name = "msm-dai-q6.4", + .platform_name = "msm-pcm-routing", + .codec_name = "cs8427-spdif.5-0014", + .codec_dai_name = "spdif_rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SEC_I2S_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &mpq8064_sec_i2s_rx_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_FM_RX, + .stream_name = "Internal FM Playback", + .cpu_dai_name = "msm-dai-q6.12292", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_RX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_FM_TX, + .stream_name = "Internal FM Capture", + .cpu_dai_name = "msm-dai-q6.12293", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_HDMI_RX, + .be_hw_params_fixup = msm_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_MI2S_TX, + .stream_name = "MI2S Capture", + .cpu_dai_name = "msm-dai-q6-mi2s", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_MI2S_TX, + .be_hw_params_fixup = msm_be_hw_params_fixup, + .ops = &msm_mi2s_tx_be_ops, + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_TX, + }, +}; + + +static struct snd_soc_card snd_soc_card_msm = { + .name = "mpq8064-tabla-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), + .controls = tabla_msm_controls, + .num_controls = ARRAY_SIZE(tabla_msm_controls), +}; + +static struct platform_device *msm_snd_device; + +static int msm_configure_headset_mic_gpios(void) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(35), "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(35), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(35), 0); + + return 0; +} +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret; + + if (socinfo_get_id() != 130) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm_headset_gpios_configured = 0; + } else + msm_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (socinfo_get_id() != 130) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC mpq8064"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-compr-q6.c b/sound/soc/msm/msm-compr-q6.c new file mode 100644 index 000000000000..e96a7223b254 --- /dev/null +++ b/sound/soc/msm/msm-compr-q6.c @@ -0,0 +1,753 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-compr-q6.h" +#include "msm-pcm-routing.h" + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm compressed_audio = {NULL, 0x2000} ; + +static struct audio_locks the_locks; + +static struct snd_pcm_hardware msm_compr_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 1200 * 1024 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 1200 * 1024, + .periods_min = 2, + .periods_max = 512, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void compr_event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct compr_audio *compr = priv; + struct msm_audio *prtd = &compr->prtd; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + int i = 0; + + pr_debug("%s opcode =%08x\n", __func__, opcode); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing %d bytes of buffer[%d] to dsp 2\n", + __func__, prtd->pcm_count, prtd->out_head); + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + + param.paddr = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + for (i = 0; i < sizeof(struct audio_aio_write_param)/4; + i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) & (runtime->periods - 1); + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: { + if (!atomic_read(&prtd->pending_buffer)) + break; + pr_debug("%s:writing %d bytes" + " of buffer[%d] to dsp\n", + __func__, prtd->pcm_count, prtd->out_head); + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + param.paddr = (unsigned long)buf[prtd->out_head].phys; + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[prtd->out_head].phys; + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) + & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_compr_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct asm_aac_cfg aac_cfg; + struct asm_wma_cfg wma_cfg; + struct asm_wmapro_cfg wma_pro_cfg; + int ret; + + pr_debug("compressed stream prepare\n"); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + prtd->out_head = 0; + atomic_set(&prtd->out_count, runtime->periods); + + if (prtd->enabled) + return 0; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + ret = q6asm_media_format_block(prtd->audio_client, + compr->codec); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + memset(&aac_cfg, 0x0, sizeof(struct asm_aac_cfg)); + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + aac_cfg.format = 0x03; + aac_cfg.ch_cfg = runtime->channels; + aac_cfg.sample_rate = runtime->rate; + ret = q6asm_media_format_block_aac(prtd->audio_client, + &aac_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + pr_debug("compressd playback, no need to send" + " the decoder params\n"); + break; + case SND_AUDIOCODEC_WMA: + pr_debug("SND_AUDIOCODEC_WMA\n"); + memset(&wma_cfg, 0x0, sizeof(struct asm_wma_cfg)); + wma_cfg.format_tag = compr->info.codec_param.codec.format; + wma_cfg.ch_cfg = runtime->channels; + wma_cfg.sample_rate = runtime->rate; + wma_cfg.avg_bytes_per_sec = + compr->info.codec_param.codec.bit_rate/8; + wma_cfg.block_align = compr->info.codec_param.codec.align; + wma_cfg.valid_bits_per_sample = + compr->info.codec_param.codec.options.wma.bits_per_sample; + wma_cfg.ch_mask = + compr->info.codec_param.codec.options.wma.channelmask; + wma_cfg.encode_opt = + compr->info.codec_param.codec.options.wma.encodeopt; + ret = q6asm_media_format_block_wma(prtd->audio_client, + &wma_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + case SND_AUDIOCODEC_WMA_PRO: + pr_debug("SND_AUDIOCODEC_WMA_PRO\n"); + memset(&wma_pro_cfg, 0x0, sizeof(struct asm_wmapro_cfg)); + wma_pro_cfg.format_tag = compr->info.codec_param.codec.format; + wma_pro_cfg.ch_cfg = compr->info.codec_param.codec.ch_in; + wma_pro_cfg.sample_rate = runtime->rate; + wma_pro_cfg.avg_bytes_per_sec = + compr->info.codec_param.codec.bit_rate/8; + wma_pro_cfg.block_align = compr->info.codec_param.codec.align; + wma_pro_cfg.valid_bits_per_sample = + compr->info.codec_param.codec.options.wma.bits_per_sample; + wma_pro_cfg.ch_mask = + compr->info.codec_param.codec.options.wma.channelmask; + wma_pro_cfg.encode_opt = + compr->info.codec_param.codec.options.wma.encodeopt; + ret = q6asm_media_format_block_wmapro(prtd->audio_client, + &wma_pro_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + default: + return -EINVAL; + } + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_compr_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + if (compr->info.codec_param.codec.id == + SND_AUDIOCODEC_AC3_PASS_THROUGH) { + msm_pcm_routing_reg_psthr_stream( + soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + } + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void populate_codec_list(struct compr_audio *compr, + struct snd_pcm_runtime *runtime) +{ + pr_debug("%s\n", __func__); + /* MP3 Block */ + compr->info.compr_cap.num_codecs = 1; + compr->info.compr_cap.min_fragment_size = runtime->hw.period_bytes_min; + compr->info.compr_cap.max_fragment_size = runtime->hw.period_bytes_max; + compr->info.compr_cap.min_fragments = runtime->hw.periods_min; + compr->info.compr_cap.max_fragments = runtime->hw.periods_max; + compr->info.compr_cap.codecs[0] = SND_AUDIOCODEC_MP3; + compr->info.compr_cap.codecs[1] = SND_AUDIOCODEC_AAC; + compr->info.compr_cap.codecs[2] = SND_AUDIOCODEC_AC3_PASS_THROUGH; + compr->info.compr_cap.codecs[3] = SND_AUDIOCODEC_WMA; + compr->info.compr_cap.codecs[4] = SND_AUDIOCODEC_WMA_PRO; + /* Add new codecs here */ +} + +static int msm_compr_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + pr_debug("%s\n", __func__); + compr = kzalloc(sizeof(struct compr_audio), GFP_KERNEL); + if (compr == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd = &compr->prtd; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)compr_event_handler, compr); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + runtime->hw = msm_compr_hardware_playback; + + pr_info("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + atomic_set(&prtd->pending_buffer, 1); + compr->codec = FORMAT_MP3; + populate_codec_list(compr, runtime); + runtime->private_data = compr; + compressed_audio.prtd = &compr->prtd; + ret = compressed_set_volume(compressed_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(compressed_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(compressed_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int compressed_set_volume(unsigned volume) +{ + int rc = 0; + if (compressed_audio.prtd && compressed_audio.prtd->audio_client) { + rc = q6asm_set_volume(compressed_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + compressed_audio.volume = volume; + return rc; +} + +static int msm_compr_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + int dir = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + compressed_audio.prtd = NULL; + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_compr_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} +static int msm_compr_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} + +static snd_pcm_uframes_t msm_compr_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_compr_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_compr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EINVAL; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + ret = q6asm_open_write_compressed(prtd->audio_client, + compr->codec); + if (ret < 0) { + pr_err("%s: compressed Session out open failed\n", + __func__); + return -ENOMEM; + } + break; + default: + ret = q6asm_open_write(prtd->audio_client, compr->codec); + if (ret < 0) { + pr_err("%s: Session out open failed\n", __func__); + return -ENOMEM; + } + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + break; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + return -ENOMEM; + } + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed " + "rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int msm_compr_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:," + "timestamp = %lld,\n", __func__, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_COMPRESS_GET_CAPS: + pr_debug("SNDRV_COMPRESS_GET_CAPS\n"); + if (copy_to_user((void *) arg, &compr->info.compr_cap, + sizeof(struct snd_compr_caps))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy to user\n", __func__); + return rc; + } + return 0; + case SNDRV_COMPRESS_SET_PARAMS: + pr_debug("SNDRV_COMPRESS_SET_PARAMS: "); + if (copy_from_user(&compr->info.codec_param, (void *) arg, + sizeof(struct snd_compr_params))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy from user\n", __func__); + return rc; + } + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* For MP3 we dont need any other parameter */ + pr_debug("SND_AUDIOCODEC_MP3\n"); + compr->codec = FORMAT_MP3; + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + compr->codec = FORMAT_MPEG4_AAC; + break; + case SND_AUDIOCODEC_AC3_PASS_THROUGH: + pr_debug("SND_AUDIOCODEC_AC3_PASS_THROUGH\n"); + compr->codec = FORMAT_AC3; + break; + case SND_AUDIOCODEC_WMA: + pr_debug("SND_AUDIOCODEC_WMA\n"); + compr->codec = FORMAT_WMA_V9; + break; + case SND_AUDIOCODEC_WMA_PRO: + pr_debug("SND_AUDIOCODEC_WMA_PRO\n"); + compr->codec = FORMAT_WMA_V10PRO; + break; + default: + pr_debug("FORMAT_LINEAR_PCM\n"); + compr->codec = FORMAT_LINEAR_PCM; + break; + } + return 0; + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_compr_ops = { + .open = msm_compr_open, + .hw_params = msm_compr_hw_params, + .close = msm_compr_close, + .ioctl = msm_compr_ioctl, + .prepare = msm_compr_prepare, + .trigger = msm_compr_trigger, + .pointer = msm_compr_pointer, + .mmap = msm_compr_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_compr_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_compr_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_compr_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_compr_driver = { + .driver = { + .name = "msm-compr-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_compr_probe, + .remove = msm_compr_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_compr_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_compr_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-compr-q6.h b/sound/soc/msm/msm-compr-q6.h new file mode 100644 index 000000000000..d91854e491de --- /dev/null +++ b/sound/soc/msm/msm-compr-q6.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MSM_COMPR_H +#define _MSM_COMPR_H +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" + +struct compr_info { + struct snd_compr_caps compr_cap; + struct snd_compr_codec_caps codec_caps; + struct snd_compr_params codec_param; +}; + +struct compr_audio { + struct msm_audio prtd; + struct compr_info info; + uint32_t codec; +}; + +#endif /*_MSM_COMPR_H*/ diff --git a/sound/soc/msm/msm-dai-fe.c b/sound/soc/msm/msm-dai-fe.c new file mode 100644 index 000000000000..9cc18325bd13 --- /dev/null +++ b/sound/soc/msm/msm-dai-fe.c @@ -0,0 +1,412 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include + +static struct snd_soc_dai_ops msm_fe_dai_ops = {}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int multimedia_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + + return 0; +} + +static struct snd_soc_dai_ops msm_fe_Multimedia_dai_ops = { + .startup = multimedia_startup, +}; + +static struct snd_soc_dai_driver msm_fe_dais[] = { + { + .playback = { + .stream_name = "Multimedia1 Playback", + .aif_name = "MM_DL1", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia1 Capture", + .aif_name = "MM_UL1", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia1", + }, + { + .playback = { + .stream_name = "Multimedia2 Playback", + .aif_name = "MM_DL2", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 6, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia2 Capture", + .aif_name = "MM_UL2", + .rates = (SNDRV_PCM_RATE_8000_48000| + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia2", + }, + { + .playback = { + .stream_name = "Voice Playback", + .aif_name = "CS-VOICE_DL1", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Voice Capture", + .aif_name = "CS-VOICE_UL1", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "CS-VOICE", + }, + { + .playback = { + .stream_name = "VoIP Playback", + .aif_name = "VOIP_DL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_SPECIAL, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "VoIP Capture", + .aif_name = "VOIP_UL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_SPECIAL, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "VoIP", + }, + { + .playback = { + .stream_name = "MultiMedia3 Playback", + .aif_name = "MM_DL3", + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia3", + }, + { + .playback = { + .stream_name = "MultiMedia4 Playback", + .aif_name = "MM_DL4", + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_Multimedia_dai_ops, + .name = "MultiMedia4", + }, + /* FE DAIs created for hostless operation purpose */ + { + .playback = { + .stream_name = "SLIMBUS0 Hostless Playback", + .aif_name = "SLIM0_DL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "SLIMBUS0 Hostless Capture", + .aif_name = "SLIM0_UL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "SLIMBUS0_HOSTLESS", + }, + { + .playback = { + .stream_name = "INT_FM Hostless Playback", + .aif_name = "INTFM_DL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "INT_FM Hostless Capture", + .aif_name = "INTFM_UL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "INT_FM_HOSTLESS", + }, + { + .playback = { + .stream_name = "AFE-PROXY Playback", + .aif_name = "PCM_RX", + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "AFE-PROXY Capture", + .aif_name = "PCM_TX", + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "AFE-PROXY", + }, + { + .playback = { + .stream_name = "HDMI_Rx Hostless Playback", + .aif_name = "HDMI_DL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "HDMI_HOSTLESS" + }, + { + .playback = { + .stream_name = "AUXPCM Hostless Playback", + .aif_name = "AUXPCM_DL_HL", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .capture = { + .stream_name = "AUXPCM Hostless Capture", + .aif_name = "AUXPCM_UL_HL", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_fe_dai_ops, + .name = "AUXPCM_HOSTLESS", + }, + { + .playback = { + .stream_name = "Voice Stub Playback", + .aif_name = "VOICE_STUB_DL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Voice Stub Capture", + .aif_name = "VOICE_STUB_UL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "VOICE_STUB", + }, + { + .playback = { + .stream_name = "VoLTE Playback", + .aif_name = "VoLTE_DL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "VoLTE Capture", + .aif_name = "VoLTE_UL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "VoLTE", + }, + { + .capture = { + .stream_name = "MI2S_TX Hostless Capture", + .aif_name = "MI2S_UL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "MI2S_TX_HOSTLESS", + }, + { + .playback = { + .stream_name = "SEC_I2S_RX Hostless Playback", + .aif_name = "SEC_I2S_DL_HL", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "SEC_I2S_RX_HOSTLESS", + }, +}; + +static int msm_fe_dai_dev_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s: dev name %s\n", __func__, + dev_name(&pdev->dev)); + return snd_soc_register_dais(&pdev->dev, msm_fe_dais, + ARRAY_SIZE(msm_fe_dais)); +} + +static int msm_fe_dai_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_fe_dai_driver = { + .probe = msm_fe_dai_dev_probe, + .remove = msm_fe_dai_dev_remove, + .driver = { + .name = "msm-dai-fe", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_fe_dai_init(void) +{ + return platform_driver_register(&msm_fe_dai_driver); +} +module_init(msm_fe_dai_init); + +static void __exit msm_fe_dai_exit(void) +{ + platform_driver_unregister(&msm_fe_dai_driver); +} +module_exit(msm_fe_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Frontend DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai-q6-hdmi.c b/sound/soc/msm/msm-dai-q6-hdmi.c new file mode 100644 index 000000000000..ac7d94cd94d5 --- /dev/null +++ b/sound/soc/msm/msm-dai-q6-hdmi.c @@ -0,0 +1,326 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +enum { + STATUS_PORT_STARTED, /* track if AFE port has started */ + STATUS_MAX +}; + +struct msm_dai_q6_hdmi_dai_data { + DECLARE_BITMAP(status_mask, STATUS_MAX); + u32 rate; + u32 channels; + union afe_port_config port_config; +}; + +static int msm_dai_q6_hdmi_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_hdmi_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + dai_data->port_config.hdmi_multi_ch.data_type = value; + pr_debug("%s: value = %d\n", __func__, value); + return 0; +} + +static int msm_dai_q6_hdmi_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_hdmi_dai_data *dai_data = kcontrol->private_data; + ucontrol->value.integer.value[0] = + dai_data->port_config.hdmi_multi_ch.data_type; + return 0; +} + + +/* HDMI format field for AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG command + * 0: linear PCM + * 1: non-linear PCM + */ +static const char *hdmi_format[] = { + "LPCM", + "Compr" +}; + +static const struct soc_enum hdmi_config_enum[] = { + SOC_ENUM_SINGLE_EXT(2, hdmi_format), +}; + +static const struct snd_kcontrol_new hdmi_config_controls[] = { + SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0], + msm_dai_q6_hdmi_format_get, + msm_dai_q6_hdmi_format_put), +}; + +/* Current implementation assumes hw_param is called once + * This may not be the case but what to do when ADM and AFE + * port are already opened and parameter changes + */ +static int msm_dai_q6_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + u32 channel_allocation = 0; + u32 level_shift = 0; /* 0dB */ + bool down_mix = FALSE; + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + dai_data->port_config.hdmi_multi_ch.reserved = 0; + + switch (dai_data->channels) { + case 2: + channel_allocation = 0; + hdmi_msm_audio_info_setup(1, MSM_HDMI_AUDIO_CHANNEL_2, + channel_allocation, level_shift, down_mix); + dai_data->port_config.hdmi_multi_ch.channel_allocation = + channel_allocation; + break; + case 6: + channel_allocation = 0x0B; + hdmi_msm_audio_info_setup(1, MSM_HDMI_AUDIO_CHANNEL_6, + channel_allocation, level_shift, down_mix); + dai_data->port_config.hdmi_multi_ch.channel_allocation = + channel_allocation; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", + dai_data->channels); + return -EINVAL; + } + dev_dbg(dai->dev, "%s() num_ch = %u rate =%u" + " channel_allocation = %u data type = %d\n", __func__, + dai_data->channels, + dai_data->rate, + dai_data->port_config.hdmi_multi_ch.channel_allocation, + dai_data->port_config.hdmi_multi_ch.data_type); + + return 0; +} + + +static void msm_dai_q6_hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + pr_info("%s: afe port not started. dai_data->status_mask" + " = %ld\n", __func__, *dai_data->status_mask); + return; + } + + rc = afe_close(dai->id); /* can block */ + + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); +} + + +static int msm_dai_q6_hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + + /* Start/stop port without waiting for Q6 AFE response. Need to have + * native q6 AFE driver propagates AFE response in order to handle + * port start/stop command error properly if error does arise. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_start_nowait(dai->id, &dai_data->port_config, + dai_data->rate); + + set_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_stop_nowait(dai->id); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + break; + + default: + dev_err(dai->dev, "invalid Trigger command = %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_hdmi_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data; + const struct snd_kcontrol_new *kcontrol; + int rc = 0; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_hdmi_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + kcontrol = &hdmi_config_controls[0]; + + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(kcontrol, dai_data)); + return rc; +} + +static int msm_dai_q6_hdmi_dai_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_hdmi_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + rc = afe_close(dai->id); /* can block */ + + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static struct snd_soc_dai_ops msm_dai_q6_hdmi_ops = { + .prepare = msm_dai_q6_hdmi_prepare, + .trigger = msm_dai_q6_hdmi_trigger, + .hw_params = msm_dai_q6_hdmi_hw_params, + .shutdown = msm_dai_q6_hdmi_shutdown, +}; + +static struct snd_soc_dai_driver msm_dai_q6_hdmi_hdmi_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 6, + .rate_max = 48000, + .rate_min = 48000, + }, + .ops = &msm_dai_q6_hdmi_ops, + .probe = msm_dai_q6_hdmi_dai_probe, + .remove = msm_dai_q6_hdmi_dai_remove, +}; + + +/* To do: change to register DAIs as batch */ +static int msm_dai_q6_hdmi_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s dev-id %d\n", + dev_name(&pdev->dev), pdev->id); + + switch (pdev->id) { + case HDMI_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_hdmi_hdmi_rx_dai); + break; + default: + dev_err(&pdev->dev, "invalid device ID %d\n", pdev->id); + rc = -ENODEV; + break; + } + return rc; +} + +static int msm_dai_q6_hdmi_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_dai_q6_hdmi_driver = { + .probe = msm_dai_q6_hdmi_dev_probe, + .remove = msm_dai_q6_hdmi_dev_remove, + .driver = { + .name = "msm-dai-q6-hdmi", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_hdmi_init(void) +{ + return platform_driver_register(&msm_dai_q6_hdmi_driver); +} +module_init(msm_dai_q6_hdmi_init); + +static void __exit msm_dai_q6_hdmi_exit(void) +{ + platform_driver_unregister(&msm_dai_q6_hdmi_driver); +} +module_exit(msm_dai_q6_hdmi_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM DSP HDMI DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai-q6.c b/sound/soc/msm/msm-dai-q6.c new file mode 100644 index 000000000000..65fdac1e300e --- /dev/null +++ b/sound/soc/msm/msm-dai-q6.c @@ -0,0 +1,2024 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + STATUS_PORT_STARTED, /* track if AFE port has started */ + STATUS_MAX +}; + +struct msm_dai_q6_dai_data { + DECLARE_BITMAP(status_mask, STATUS_MAX); + u32 rate; + u32 channels; + u32 bitwidth; + union afe_port_config port_config; +}; + +struct msm_dai_q6_mi2s_dai_data { + struct msm_dai_q6_dai_data tx_dai; + struct msm_dai_q6_dai_data rx_dai; + struct snd_pcm_hw_constraint_list rate_constraint; + struct snd_pcm_hw_constraint_list bitwidth_constraint; +}; + +static struct clk *pcm_clk; +static struct clk *sec_pcm_clk; +static DEFINE_MUTEX(aux_pcm_mutex); +static int aux_pcm_count; +static struct msm_dai_auxpcm_pdata *auxpcm_plat_data; +static struct msm_dai_auxpcm_pdata *sec_auxpcm_plat_data; + +static int msm_dai_q6_mi2s_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + dai_data->port_config.mi2s.format = value; + pr_debug("%s: value = %d, channel = %d, line = %d\n", + __func__, value, dai_data->port_config.mi2s.channel, + dai_data->port_config.mi2s.line); + return 0; +} + +static int msm_dai_q6_mi2s_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct msm_dai_q6_dai_data *dai_data = kcontrol->private_data; + ucontrol->value.integer.value[0] = dai_data->port_config.mi2s.format ; + return 0; +} + + +/* MI2S format field for AFE_PORT_CMD_I2S_CONFIG command + * 0: linear PCM + * 1: non-linear PCM + * 2: PCM data in IEC 60968 container + * 3: compressed data in IEC 60958 container + */ +static const char *mi2s_format[] = { + "LPCM", + "Compr", + "60958-LPCM", + "60958-Compr"}; + +static const struct soc_enum mi2s_config_enum[] = { + SOC_ENUM_SINGLE_EXT(4, mi2s_format), +}; + +static const struct snd_kcontrol_new mi2s_config_controls[] = { + SOC_ENUM_EXT("MI2S RX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), + SOC_ENUM_EXT("SEC RX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), + SOC_ENUM_EXT("MI2S TX Format", mi2s_config_enum[0], + msm_dai_q6_mi2s_format_get, + msm_dai_q6_mi2s_format_put), +}; + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + num_bits_set++; + sd_line_mask = sd_line_mask & (sd_line_mask - 1); + } + return num_bits_set; +} + +static int msm_dai_q6_mi2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: cnst list %p\n", __func__, + mi2s_dai_data->rate_constraint.list); + + if (mi2s_dai_data->rate_constraint.list) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &mi2s_dai_data->rate_constraint); + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &mi2s_dai_data->bitwidth_constraint); + } + + return 0; +} + +static int msm_dai_q6_mi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + dai_data->port_config.mi2s.channel = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.mi2s.channel = MSM_AFE_MONO; + break; + default: + pr_warn("greater than stereo has not been validated"); + break; + } + dai_data->rate = params_rate(params); + dai_data->port_config.mi2s.bitwidth = 16; + dai_data->bitwidth = 16; + if (!mi2s_dai_data->rate_constraint.list) { + mi2s_dai_data->rate_constraint.list = &dai_data->rate; + mi2s_dai_data->bitwidth_constraint.list = &dai_data->bitwidth; + } + return 0; +} + +static int msm_dai_q6_mi2s_get_lineconfig(u16 sd_lines, u16 *config_ptr, + unsigned int *ch_cnt) +{ + u8 num_of_sd_lines; + + num_of_sd_lines = num_of_bits_set(sd_lines); + + switch (num_of_sd_lines) { + case 0: + pr_debug("%s: no line is assigned\n", __func__); + break; + case 1: + switch (sd_lines) { + case MSM_MI2S_SD0: + *config_ptr = AFE_I2S_SD0; + break; + case MSM_MI2S_SD1: + *config_ptr = AFE_I2S_SD1; + break; + case MSM_MI2S_SD2: + *config_ptr = AFE_I2S_SD2; + break; + case MSM_MI2S_SD3: + *config_ptr = AFE_I2S_SD3; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 2: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1: + *config_ptr = AFE_I2S_QUAD01; + break; + case MSM_MI2S_SD2 | MSM_MI2S_SD3: + *config_ptr = AFE_I2S_QUAD23; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 3: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2: + *config_ptr = AFE_I2S_6CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + case 4: + switch (sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | MSM_MI2S_SD3: + *config_ptr = AFE_I2S_8CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + default: + pr_err("%s: invalid SD lines\n", __func__); + goto error_invalid_data; + } + + *ch_cnt = num_of_sd_lines; + + return 0; + +error_invalid_data: + return -EINVAL; +} + +static int msm_dai_q6_mi2s_platform_data_validation( + struct platform_device *pdev, struct snd_soc_dai_driver *dai_driver) +{ + struct msm_dai_q6_mi2s_dai_data *dai_data = dev_get_drvdata(&pdev->dev); + struct msm_mi2s_pdata *mi2s_pdata = + (struct msm_mi2s_pdata *) pdev->dev.platform_data; + u16 sdline_config; + unsigned int ch_cnt; + int rc = 0; + + if ((mi2s_pdata->rx_sd_lines & mi2s_pdata->tx_sd_lines) || + (!mi2s_pdata->rx_sd_lines && !mi2s_pdata->tx_sd_lines)) { + dev_err(&pdev->dev, + "error sd line conflict or no line assigned\n"); + rc = -EINVAL; + goto rtn; + } + + rc = msm_dai_q6_mi2s_get_lineconfig(mi2s_pdata->rx_sd_lines, + &sdline_config, &ch_cnt); + + if (IS_ERR_VALUE(rc)) { + dev_err(&pdev->dev, "invalid MI2S RX sd line config\n"); + goto rtn; + } + + if (ch_cnt) { + dai_data->rx_dai.port_config.mi2s.line = sdline_config; + dai_driver->playback.channels_min = 1; + dai_driver->playback.channels_max = ch_cnt << 1; + } else { + dai_driver->playback.channels_min = 0; + dai_driver->playback.channels_max = 0; + } + rc = msm_dai_q6_mi2s_get_lineconfig(mi2s_pdata->tx_sd_lines, + &sdline_config, &ch_cnt); + + if (IS_ERR_VALUE(rc)) { + dev_err(&pdev->dev, "invalid MI2S TX sd line config\n"); + goto rtn; + } + + if (ch_cnt) { + dai_data->tx_dai.port_config.mi2s.line = sdline_config; + dai_driver->capture.channels_min = 1; + dai_driver->capture.channels_max = ch_cnt << 1; + } else { + dai_driver->capture.channels_min = 0; + dai_driver->capture.channels_max = 0; + } + + dev_info(&pdev->dev, "%s: playback sdline %x capture sdline %x\n", + __func__, dai_data->rx_dai.port_config.mi2s.line, + dai_data->tx_dai.port_config.mi2s.line); + dev_info(&pdev->dev, "%s: playback ch_max %d capture ch_mx %d\n", + __func__, dai_driver->playback.channels_max, + dai_driver->capture.channels_max); +rtn: + return rc; +} + +static int msm_dai_q6_mi2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask) || + test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + dev_err(dai->dev, "%s: err chg i2s mode while dai running", + __func__); + return -EPERM; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + mi2s_dai_data->rx_dai.port_config.mi2s.ws = 1; + mi2s_dai_data->tx_dai.port_config.mi2s.ws = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + mi2s_dai_data->rx_dai.port_config.mi2s.ws = 0; + mi2s_dai_data->tx_dai.port_config.mi2s.ws = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_mi2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_mi2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + u16 port_id = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MI2S_RX : MI2S_TX); + int rc = 0; + + dev_dbg(dai->dev, "%s: cmd:%d dai_data->status_mask = %ld", + __func__, cmd, *dai_data->status_mask); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_start_nowait(port_id, + &dai_data->port_config, dai_data->rate); + set_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_stop_nowait(port_id); + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} + +static void msm_dai_q6_mi2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct msm_dai_q6_dai_data *dai_data = + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + &mi2s_dai_data->rx_dai : &mi2s_dai_data->tx_dai); + u16 port_id = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MI2S_RX : MI2S_TX); + int rc = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + rc = afe_close(port_id); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + + if (!test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask) && + !test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + mi2s_dai_data->rate_constraint.list = NULL; + mi2s_dai_data->bitwidth_constraint.list = NULL; + } + +} + +static int msm_dai_q6_cdc_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + dai_data->port_config.mi2s.channel = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.mi2s.channel = MSM_AFE_MONO; + break; + default: + return -EINVAL; + break; + } + dai_data->rate = params_rate(params); + + dev_dbg(dai->dev, " channel %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + /* Q6 only supports 16 as now */ + dai_data->port_config.mi2s.bitwidth = 16; + dai_data->port_config.mi2s.line = 1; + return 0; +} + +static int msm_dai_q6_cdc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_data->port_config.mi2s.ws = 1; /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_data->port_config.mi2s.ws = 0; /* CPU is slave */ + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int msm_dai_q6_slim_bus_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + /* Q6 only supports 16 as now */ + dai_data->port_config.slim_sch.bit_width = 16; + dai_data->port_config.slim_sch.data_format = 0; + dai_data->port_config.slim_sch.num_channels = dai_data->channels; + dai_data->port_config.slim_sch.reserved = 0; + + dev_dbg(dai->dev, "%s:slimbus_dev_id[%hu] bit_wd[%hu] format[%hu]\n" + "num_channel %hu slave_ch_mapping[0] %hu\n" + "slave_port_mapping[1] %hu slave_port_mapping[2] %hu\n" + "slave_port_mapping[3] %hu\n sample_rate %d\n", __func__, + dai_data->port_config.slim_sch.slimbus_dev_id, + dai_data->port_config.slim_sch.bit_width, + dai_data->port_config.slim_sch.data_format, + dai_data->port_config.slim_sch.num_channels, + dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1], + dai_data->port_config.slim_sch.slave_ch_mapping[2], + dai_data->port_config.slim_sch.slave_ch_mapping[3], + dai_data->rate); + + return 0; +} + +static int msm_dai_q6_bt_fm_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + dev_dbg(dai->dev, "channels %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + memset(&dai_data->port_config, 0, sizeof(dai_data->port_config)); + + return 0; +} +static int msm_dai_q6_auxpcm_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + if (params_channels(params) != 1) { + dev_err(dai->dev, "AUX PCM supports only mono stream\n"); + return -EINVAL; + } + dai_data->channels = params_channels(params); + + dai_data->rate = params_rate(params); + switch (dai_data->rate) { + case 8000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_8k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_8k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_8k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_8k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_8k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_8k.data; + break; + case 16000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_16k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_16k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_16k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_16k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_16k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_16k.data; + break; + default: + dev_err(dai->dev, "AUX PCM supports only 8kHz and 16kHz sampling rate\n"); + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_sec_auxpcm_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + pr_debug("%s\n", __func__); + if (params_channels(params) != 1) { + dev_err(dai->dev, "SEC AUX PCM supports only mono stream\n"); + return -EINVAL; + } + dai_data->channels = params_channels(params); + + dai_data->rate = params_rate(params); + switch (dai_data->rate) { + case 8000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_8k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_8k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_8k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_8k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_8k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_8k.data; + break; + case 16000: + dai_data->port_config.pcm.mode = auxpcm_pdata->mode_16k.mode; + dai_data->port_config.pcm.sync = auxpcm_pdata->mode_16k.sync; + dai_data->port_config.pcm.frame = auxpcm_pdata->mode_16k.frame; + dai_data->port_config.pcm.quant = auxpcm_pdata->mode_16k.quant; + dai_data->port_config.pcm.slot = auxpcm_pdata->mode_16k.slot; + dai_data->port_config.pcm.data = auxpcm_pdata->mode_16k.data; + break; + default: + dev_err(dai->dev, "AUX PCM supports only 8kHz and 16kHz sampling rate\n"); + return -EINVAL; + } + + return 0; +} + +static int msm_dai_q6_afe_rtproxy_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->rate = params_rate(params); + dai_data->port_config.rtproxy.num_ch = + params_channels(params); + + pr_debug("channel %d entered,dai_id: %d,rate: %d\n", + dai_data->port_config.rtproxy.num_ch, dai->id, dai_data->rate); + + dai_data->port_config.rtproxy.bitwidth = 16; /* Q6 only supports 16 */ + dai_data->port_config.rtproxy.interleaved = 1; + dai_data->port_config.rtproxy.frame_sz = params_period_bytes(params); + dai_data->port_config.rtproxy.jitter = + dai_data->port_config.rtproxy.frame_sz/2; + dai_data->port_config.rtproxy.lw_mark = 0; + dai_data->port_config.rtproxy.hw_mark = 0; + dai_data->port_config.rtproxy.rsvd = 0; + + return 0; +} + +/* Current implementation assumes hw_param is called once + * This may not be the case but what to do when ADM and AFE + * port are already opened and parameter changes + */ +static int msm_dai_q6_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int rc = 0; + + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream); + break; + + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_3_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + rc = msm_dai_q6_slim_bus_hw_params(params, dai, + substream->stream); + break; + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_FM_RX: + case INT_FM_TX: + rc = msm_dai_q6_bt_fm_hw_params(params, dai, substream->stream); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_TX: + case RT_PROXY_DAI_002_RX: + rc = msm_dai_q6_afe_rtproxy_hw_params(params, dai); + break; + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = 0; + break; + default: + dev_err(dai->dev, "invalid AFE port ID\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static void msm_dai_q6_auxpcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int rc = 0; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. Just" + " return\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + aux_pcm_count = 0; + mutex_unlock(&aux_pcm_mutex); + return; + } + + pr_debug("%s: dai->id = %d aux_pcm_count = %d\n", __func__, + dai->id, aux_pcm_count); + + clk_disable_unprepare(pcm_clk); + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close PCM_RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX port\n"); + + mutex_unlock(&aux_pcm_mutex); +} + +static void msm_dai_q6_sec_auxpcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. Just" + " return\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + aux_pcm_count = 0; + mutex_unlock(&aux_pcm_mutex); + return; + } + + pr_debug("%s: dai->id = %d aux_pcm_count = %d\n", __func__, + dai->id, aux_pcm_count); + + clk_disable_unprepare(sec_pcm_clk); + rc = afe_close(SECONDARY_PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close PCM_RX AFE port\n"); + + rc = afe_close(SECONDARY_PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX port\n"); + + mutex_unlock(&aux_pcm_mutex); +} + +static void msm_dai_q6_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + break; + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } +} + +static int msm_dai_q6_auxpcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + unsigned long pcm_clk_rate; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 2. Just" + " return.\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return 0; + } else if (aux_pcm_count > 2) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d > 2\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + aux_pcm_count++; + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d after " + " increment\n", __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + pr_debug("%s:dai->id:%d aux_pcm_count = %d. opening afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + + /* + * For AUX PCM Interface the below sequence of clk + * settings and afe_open is a strict requirement. + * + * Also using afe_open instead of afe_port_start_nowait + * to make sure the port is open before deasserting the + * clock line. This is required because pcm register is + * not written before clock deassert. Hence the hw does + * not get updated with new setting if the below clock + * assert/deasset and afe_open sequence is not followed. + */ + + clk_reset(pcm_clk, CLK_RESET_ASSERT); + + afe_open(PCM_RX, &dai_data->port_config, dai_data->rate); + + afe_open(PCM_TX, &dai_data->port_config, dai_data->rate); + if (dai_data->rate == 8000) { + pcm_clk_rate = auxpcm_pdata->mode_8k.pcm_clk_rate; + } else if (dai_data->rate == 16000) { + pcm_clk_rate = auxpcm_pdata->mode_16k.pcm_clk_rate; + } else { + dev_err(dai->dev, "%s: Invalid AUX PCM rate %d\n", __func__, + dai_data->rate); + return -EINVAL; + } + + rc = clk_set_rate(pcm_clk, pcm_clk_rate); + if (rc < 0) { + pr_err("%s: clk_set_rate failed\n", __func__); + return rc; + } + + clk_prepare_enable(pcm_clk); + clk_reset(pcm_clk, CLK_RESET_DEASSERT); + + mutex_unlock(&aux_pcm_mutex); + + return rc; +} + +static int msm_dai_q6_sec_auxpcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + unsigned long pcm_clk_rate; + + pr_info("%s\n", __func__); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 2. Just" + " return.\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return 0; + } else if (aux_pcm_count > 2) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d > 2\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + aux_pcm_count++; + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d after " + " increment\n", __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + pr_debug("%s:dai->id:%d aux_pcm_count = %d. opening afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + + /* + * For AUX PCM Interface the below sequence of clk + * settings and afe_open is a strict requirement. + * + * Also using afe_open instead of afe_port_start_nowait + * to make sure the port is open before deasserting the + * clock line. This is required because pcm register is + * not written before clock deassert. Hence the hw does + * not get updated with new setting if the below clock + * assert/deasset and afe_open sequence is not followed. + */ + + clk_reset(sec_pcm_clk, CLK_RESET_ASSERT); + + afe_open(SECONDARY_PCM_RX, &dai_data->port_config, dai_data->rate); + + afe_open(SECONDARY_PCM_TX, &dai_data->port_config, dai_data->rate); + if (dai_data->rate == 8000) { + pcm_clk_rate = auxpcm_pdata->mode_8k.pcm_clk_rate; + } else if (dai_data->rate == 16000) { + pcm_clk_rate = auxpcm_pdata->mode_16k.pcm_clk_rate; + } else { + dev_err(dai->dev, "%s: Invalid AUX PCM rate %d\n", __func__, + dai_data->rate); + return -EINVAL; + } + + rc = clk_set_rate(sec_pcm_clk, pcm_clk_rate); + if (rc < 0) { + pr_err("%s: clk_set_rate failed\n", __func__); + return rc; + } + + clk_prepare_enable(sec_pcm_clk); + clk_reset(sec_pcm_clk, CLK_RESET_DEASSERT); + + mutex_unlock(&aux_pcm_mutex); + + return rc; +} + +static int msm_dai_q6_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_auxpcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int rc = 0; + + pr_debug("%s:port:%d cmd:%d aux_pcm_count= %d", + __func__, dai->id, cmd, aux_pcm_count); + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* afe_open will be called from prepare */ + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return 0; + + default: + rc = -EINVAL; + } + + return rc; + +} + +static int msm_dai_q6_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + /* Start/stop port without waiting for Q6 AFE response. Need to have + * native q6 AFE driver propagates AFE response in order to handle + * port start/stop command error properly if error does arise. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_start_nowait(dai->id); + break; + default: + afe_port_start_nowait(dai->id, + &dai_data->port_config, dai_data->rate); + break; + } + set_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_stop_nowait(dai->id); + break; + default: + afe_port_stop_nowait(dai->id); + break; + } + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} +static int msm_dai_q6_dai_auxpcm_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + mutex_lock(&aux_pcm_mutex); + + if (!auxpcm_plat_data) + auxpcm_plat_data = auxpcm_pdata; + else if (auxpcm_plat_data != auxpcm_pdata) { + + dev_err(dai->dev, "AUX PCM RX and TX devices does not have" + " same platform data\n"); + return -EINVAL; + } + + /* + * The clk name for AUX PCM operation is passed as platform + * data to the cpu driver, since cpu drive is unaware of any + * boarc specific configuration. + */ + if (!pcm_clk) { + + pcm_clk = clk_get(dai->dev, auxpcm_pdata->clk); + + if (IS_ERR(pcm_clk)) { + pr_err("%s: could not get pcm_clk\n", __func__); + pcm_clk = NULL; + return -ENODEV; + } + } + + mutex_unlock(&aux_pcm_mutex); + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + pr_debug("%s : probe done for dai->id %d\n", __func__, dai->id); + return rc; +} + +static int msm_dai_q6_dai_sec_auxpcm_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + pr_info("%s\n", __func__); + + mutex_lock(&aux_pcm_mutex); + + if (!sec_auxpcm_plat_data) + sec_auxpcm_plat_data = auxpcm_pdata; + else if (sec_auxpcm_plat_data != auxpcm_pdata) { + dev_err(dai->dev, "AUX PCM RX and TX devices does not have" + " same platform data sec_auxpcm_plat_data\n"); + return -EINVAL; + } + + /* + * The clk name for AUX PCM operation is passed as platform + * data to the cpu driver, since cpu drive is unaware of any + * boarc specific configuration. + */ + if (!sec_pcm_clk) { + + sec_pcm_clk = clk_get(dai->dev, auxpcm_pdata->clk); + if (IS_ERR(sec_pcm_clk)) { + pr_err("%s: could not get sec_pcm_clk\n", __func__); + sec_pcm_clk = NULL; + return -ENODEV; + } + } + + mutex_unlock(&aux_pcm_mutex); + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + pr_debug("%s : probe done for dai->id %d\n", __func__, dai->id); + return rc; +} + +static int msm_dai_q6_dai_auxpcm_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. clean" + " up and return\n", __func__, dai->id); + goto done; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + goto done; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + goto done; + } + + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d." + "closing afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX AFE port\n"); + +done: + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + mutex_unlock(&aux_pcm_mutex); + + return 0; +} + +static int msm_dai_q6_dai_sec_auxpcm_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + pr_debug("%s\n", __func__); + dai_data = dev_get_drvdata(dai->dev); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. clean" + " up and return\n", __func__, dai->id); + goto done; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + goto done; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + goto done; + } + + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d." + "closing afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_close(SECONDARY_PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM RX AFE port\n"); + + rc = afe_close(SECONDARY_PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX AFE port\n"); + +done: + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + mutex_unlock(&aux_pcm_mutex); + + return 0; +} + +static int msm_dai_q6_dai_mi2s_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + struct snd_kcontrol *kcontrol = NULL; + int rc = 0; + + if (mi2s_dai_data->rx_dai.port_config.mi2s.line) { + kcontrol = snd_ctl_new1(&mi2s_config_controls[0], + &mi2s_dai_data->rx_dai); + rc = snd_ctl_add(dai->card->snd_card, kcontrol); + + if (IS_ERR_VALUE(rc)) { + dev_err(dai->dev, "%s: err add RX fmt ctl\n", __func__); + goto rtn; + } + } + + if (mi2s_dai_data->tx_dai.port_config.mi2s.line) { + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(&mi2s_config_controls[2], + &mi2s_dai_data->tx_dai)); + + if (IS_ERR_VALUE(rc)) { + if (kcontrol) + snd_ctl_remove(dai->card->snd_card, kcontrol); + dev_err(dai->dev, "%s: err add TX fmt ctl\n", __func__); + } + } + +rtn: + return rc; +} + +static int msm_dai_q6_dai_mi2s_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data = + dev_get_drvdata(dai->dev); + int rc; + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->rx_dai.status_mask)) { + rc = afe_close(MI2S_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close MI2S_RX port\n"); + clear_bit(STATUS_PORT_STARTED, + mi2s_dai_data->rx_dai.status_mask); + } + if (test_bit(STATUS_PORT_STARTED, mi2s_dai_data->tx_dai.status_mask)) { + rc = afe_close(MI2S_TX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close MI2S_TX port\n"); + clear_bit(STATUS_PORT_STARTED, + mi2s_dai_data->tx_dai.status_mask); + } + kfree(mi2s_dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + const struct snd_kcontrol_new *kcontrol; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + if (dai->id == SECONDARY_I2S_RX) { + kcontrol = &mi2s_config_controls[1]; + rc = snd_ctl_add(dai->card->snd_card, + snd_ctl_new1(kcontrol, dai_data)); + } + + return rc; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + int rc = 0; + + dev_dbg(dai->dev, "enter %s, id = %d fmt[%d]\n", __func__, + dai->id, fmt); + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_set_fmt(dai, fmt); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_dai_q6_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + int rc = 0; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + unsigned int i = 0; + + dev_dbg(dai->dev, "%s: dai_id = %d\n", __func__, dai->id); + switch (dai->id) { + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_2_RX: + case SLIMBUS_3_RX: + case SLIMBUS_4_RX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!rx_slot) + return -EINVAL; + for (i = 0; i < rx_num; i++) { + dai_data->port_config.slim_sch.slave_ch_mapping[i] = + rx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, + rx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = rx_num; + pr_debug("%s:SLIMBUS_%d_RX cnt[%d] ch[%d %d]\n", __func__, + (dai->id - SLIMBUS_0_RX) / 2, + rx_num, dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1]); + + break; + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_TX: + case SLIMBUS_3_TX: + case SLIMBUS_4_TX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!tx_slot) + return -EINVAL; + for (i = 0; i < tx_num; i++) { + dai_data->port_config.slim_sch.slave_ch_mapping[i] = + tx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, tx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = tx_num; + pr_debug("%s:SLIMBUS_%d_TX cnt[%d] ch[%d %d]\n", __func__, + (dai->id - SLIMBUS_0_TX) / 2, + tx_num, dai_data->port_config.slim_sch.slave_ch_mapping[0], + dai_data->port_config.slim_sch.slave_ch_mapping[1]); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + return rc; +} + +static struct snd_soc_dai_ops msm_dai_q6_mi2s_ops = { + .startup = msm_dai_q6_mi2s_startup, + .prepare = msm_dai_q6_mi2s_prepare, + .trigger = msm_dai_q6_mi2s_trigger, + .hw_params = msm_dai_q6_mi2s_hw_params, + .shutdown = msm_dai_q6_mi2s_shutdown, + .set_fmt = msm_dai_q6_mi2s_set_fmt, +}; + +static struct snd_soc_dai_ops msm_dai_q6_ops = { + .prepare = msm_dai_q6_prepare, + .trigger = msm_dai_q6_trigger, + .hw_params = msm_dai_q6_hw_params, + .shutdown = msm_dai_q6_shutdown, + .set_fmt = msm_dai_q6_set_fmt, + .set_channel_map = msm_dai_q6_set_channel_map, +}; + +static struct snd_soc_dai_ops msm_dai_q6_auxpcm_ops = { + .prepare = msm_dai_q6_auxpcm_prepare, + .trigger = msm_dai_q6_auxpcm_trigger, + .hw_params = msm_dai_q6_auxpcm_hw_params, + .shutdown = msm_dai_q6_auxpcm_shutdown, +}; + +static struct snd_soc_dai_ops msm_dai_q6_sec_auxpcm_ops = { + .prepare = msm_dai_q6_sec_auxpcm_prepare, + .trigger = msm_dai_q6_auxpcm_trigger, + .hw_params = msm_dai_q6_sec_auxpcm_hw_params, + .shutdown = msm_dai_q6_sec_auxpcm_shutdown, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_afe_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_afe_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_voice_playback_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_incall_record_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_sec_aux_pcm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_sec_auxpcm_ops, + .probe = msm_dai_q6_dai_sec_auxpcm_probe, + .remove = msm_dai_q6_dai_sec_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_sec_aux_pcm_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_sec_auxpcm_ops, + .probe = msm_dai_q6_dai_sec_auxpcm_probe, + .remove = msm_dai_q6_dai_sec_auxpcm_remove, +}; + +/* Channel min and max are initialized base on platform data */ +static struct snd_soc_dai_driver msm_dai_q6_mi2s_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_mi2s_ops, + .probe = msm_dai_q6_dai_mi2s_probe, + .remove = msm_dai_q6_dai_mi2s_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_2_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_2_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_3_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +/* To do: change to register DAIs as batch */ +static int msm_dai_q6_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + + switch (pdev->id) { + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_rx_dai); + break; + case PRIMARY_I2S_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_tx_dai); + break; + case PCM_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_rx_dai); + break; + case PCM_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_tx_dai); + break; + + case SECONDARY_PCM_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_sec_aux_pcm_rx_dai); + break; + case SECONDARY_PCM_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_sec_aux_pcm_tx_dai); + break; + case SLIMBUS_0_RX: + case SLIMBUS_4_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_rx_dai); + break; + case SLIMBUS_0_TX: + case SLIMBUS_4_TX: + case SLIMBUS_3_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_tx_dai); + break; + case SLIMBUS_1_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_rx_dai); + break; + case SLIMBUS_1_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_tx_dai); + break; + case SLIMBUS_2_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_2_rx_dai); + break; + case SLIMBUS_2_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_2_tx_dai); + break; + case SLIMBUS_3_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_3_rx_dai); + break; + case INT_BT_SCO_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_rx_dai); + break; + case INT_BT_SCO_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_tx_dai); + break; + case INT_FM_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_rx_dai); + break; + case INT_FM_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_tx_dai); + break; + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_rx_dai); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_002_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_tx_dai); + break; + case VOICE_PLAYBACK_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_voice_playback_tx_dai); + break; + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_incall_record_dai); + break; + default: + rc = -ENODEV; + break; + } + return rc; +} + +static int msm_dai_q6_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static int msm_dai_q6_mi2s_dev_probe(struct platform_device *pdev) +{ + struct msm_dai_q6_mi2s_dai_data *dai_data; + int rc = 0; + + dev_dbg(&pdev->dev, "%s: pdev %p dev %p\n", __func__, pdev, &pdev->dev); + + dai_data = kzalloc(sizeof(struct msm_dai_q6_mi2s_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(&pdev->dev, "fail to allocate dai data\n"); + rc = -ENOMEM; + goto rtn; + } else + dev_set_drvdata(&pdev->dev, dai_data); + + rc = msm_dai_q6_mi2s_platform_data_validation(pdev, + &msm_dai_q6_mi2s_dai); + if (IS_ERR_VALUE(rc)) + goto err_pdata; + + dai_data->rate_constraint.count = 1; + dai_data->bitwidth_constraint.count = 1; + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_mi2s_dai); + + if (IS_ERR_VALUE(rc)) + goto err_pdata; + + return 0; + +err_pdata: + kfree(dai_data); +rtn: + return rc; +} + +static int msm_dai_q6_mi2s_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_dai_q6_driver = { + .probe = msm_dai_q6_dev_probe, + .remove = msm_dai_q6_dev_remove, + .driver = { + .name = "msm-dai-q6", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver msm_dai_q6_mi2s_driver = { + .probe = msm_dai_q6_mi2s_dev_probe, + .remove = msm_dai_q6_mi2s_dev_remove, + .driver = { + .name = "msm-dai-q6-mi2s", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_init(void) +{ + int rc1, rc2; + + rc1 = platform_driver_register(&msm_dai_q6_mi2s_driver); + + if (IS_ERR_VALUE(rc1)) + pr_err("%s: fail to register mi2s dai driver\n", __func__); + + rc2 = platform_driver_register(&msm_dai_q6_driver); + + if (IS_ERR_VALUE(rc2)) + pr_err("%s: fail to register mi2s dai driver\n", __func__); + + return (IS_ERR_VALUE(rc1) && IS_ERR_VALUE(rc2)) ? -1 : 0; +} +module_init(msm_dai_q6_init); + +static void __exit msm_dai_q6_exit(void) +{ + platform_driver_unregister(&msm_dai_q6_driver); +} +module_exit(msm_dai_q6_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM DSP DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai-stub.c b/sound/soc/msm/msm-dai-stub.c new file mode 100644 index 000000000000..ae6f5db8aaec --- /dev/null +++ b/sound/soc/msm/msm-dai-stub.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +static int msm_dai_stub_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + pr_debug("%s:\n", __func__); + + return 0; +} + +static struct snd_soc_dai_ops msm_dai_stub_ops = { + .set_channel_map = msm_dai_stub_set_channel_map, +}; + +static struct snd_soc_dai_driver msm_dai_stub_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_stub_ops, +}; + +static int msm_dai_stub_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_stub_dai); + + return rc; +} + +static int msm_dai_stub_dev_remove(struct platform_device *pdev) +{ + pr_debug("%s:\n", __func__); + + snd_soc_unregister_dai(&pdev->dev); + + return 0; +} + +static struct platform_driver msm_dai_stub_driver = { + .probe = msm_dai_stub_dev_probe, + .remove = msm_dai_stub_dev_remove, + .driver = { + .name = "msm-dai-stub", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_stub_init(void) +{ + pr_debug("%s:\n", __func__); + + return platform_driver_register(&msm_dai_stub_driver); +} +module_init(msm_dai_stub_init); + +static void __exit msm_dai_stub_exit(void) +{ + pr_debug("%s:\n", __func__); + + platform_driver_unregister(&msm_dai_stub_driver); +} +module_exit(msm_dai_stub_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Stub DSP DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai.c b/sound/soc/msm/msm-dai.c new file mode 100644 index 000000000000..cb74b8a8b495 --- /dev/null +++ b/sound/soc/msm/msm-dai.c @@ -0,0 +1,150 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2011, The Linux Foundation. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.c. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm.h" + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +}; + +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static int asoc_pcm_cpu_probe(struct platform_device *pdev) +{ + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int asoc_pcm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_codec_dai_driver = { + .probe = asoc_msm_codec_probe, + .remove = asoc_msm_codec_remove, + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_cpu_dai_driver = { + .probe = asoc_pcm_cpu_probe, + .remove = asoc_pcm_cpu_remove, + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_codec_dai_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_codec_dai_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_cpu_dai_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_cpu_dai_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-multi-ch-pcm-q6.c b/sound/soc/msm/msm-multi-ch-pcm-q6.c new file mode 100644 index 000000000000..ecf62788867e --- /dev/null +++ b/sound/soc/msm/msm-multi-ch-pcm-q6.c @@ -0,0 +1,804 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +struct snd_msm_volume { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm_volume multi_ch_pcm_audio = {NULL, 0x2000}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 4032 +#define PLAYBACK_MIN_PERIOD_SIZE 256 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 6, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static uint32_t in_frame_info[CAPTURE_NUM_PERIODS][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + uint32_t idx = 0; + uint32_t size = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + if (q6asm_is_cpu_buf_avail_nolock(IN, + prtd->audio_client, + &size, &idx)) { + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + } + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag + && q6asm_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + break; + } + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + atomic_set(&prtd->start, 1); + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_multi_ch_pcm(prtd->audio_client, + runtime->rate, runtime->channels); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + prtd->periods = runtime->periods; + + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_err("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, + FORMAT_MULTI_CHANNEL_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_hardware_capture; + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PLAYBACK_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, + PLAYBACK_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); + if (ret < 0) { + pr_err("constraint for buffer bytes min max ret = %d\n", + ret); + } + } + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + pr_debug("substream->pcm->device = %d\n", substream->pcm->device); + pr_debug("soc_prtd->dai_link->be_id = %d\n", soc_prtd->dai_link->be_id); + multi_ch_pcm_audio.prtd = prtd; + ret = multi_ch_pcm_set_volume(multi_ch_pcm_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(multi_ch_pcm_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(multi_ch_pcm_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int multi_ch_pcm_set_volume(unsigned volume) +{ + int rc = 0; + pr_err("multi_ch_pcm_set_volume\n"); + + if (multi_ch_pcm_audio.prtd && multi_ch_pcm_audio.prtd->audio_client) { + pr_err("%s q6asm_set_volume\n", __func__); + rc = q6asm_set_volume(multi_ch_pcm_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + multi_ch_pcm_audio.volume = volume; + return rc; +} + + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_err("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + multi_ch_pcm_audio.prtd = NULL; + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_CAPTURE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + if (dir == OUT) { + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + } else { + /* + *TODO : Need to Add Async IO changes. All period + * size might not be supported. + */ + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + (params_buffer_bytes(params) / params_periods(params)), + params_periods(params)); + } + + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + if (dir == OUT) + dma_buf->bytes = runtime->hw.buffer_bytes_max; + else + dma_buf->bytes = params_buffer_bytes(params); + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-multi-ch-pcm-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Multi channel PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-mvs.c b/sound/soc/msm/msm-mvs.c new file mode 100644 index 000000000000..87dd68cb51cc --- /dev/null +++ b/sound/soc/msm/msm-mvs.c @@ -0,0 +1,936 @@ +/* Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + + +static struct audio_mvs_info_type audio_mvs_info; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN, + .period_bytes_min = MVS_MAX_VOC_PKT_SIZE, + .period_bytes_max = MVS_MAX_VOC_PKT_SIZE, + .periods_min = MVS_MAX_Q_LEN, + .periods_max = MVS_MAX_Q_LEN, + .fifo_size = 0, +}; + +static void snd_pcm_mvs_timer(unsigned long data) +{ + struct audio_mvs_info_type *audio = &audio_mvs_info; + MM_DBG("%s\n", __func__); + if (audio->playback_start) { + if (audio->ack_dl_count) { + audio->pcm_playback_irq_pos += audio->pcm_count; + audio->ack_dl_count--; + snd_pcm_period_elapsed(audio->playback_substream); + } + } + + if (audio->capture_start) { + if (audio->ack_ul_count) { + audio->pcm_capture_irq_pos += audio->pcm_capture_count; + audio->ack_ul_count--; + snd_pcm_period_elapsed(audio->capture_substream); + } + } + audio->timer.expires += audio->expiry_delta; + add_timer(&audio->timer); +} + +static int audio_mvs_setup_mvs(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_enable_msg enable_msg; + MM_DBG("%s\n", __func__); + + /* Enable MVS. */ + + memset(&enable_msg, 0, sizeof(enable_msg)); + audio->rpc_status = RPC_STATUS_FAILURE; + enable_msg.enable_args.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + enable_msg.enable_args.mode = cpu_to_be32(MVS_MODE_LINEAR_PCM); + enable_msg.enable_args.ul_cb_func_id = (int) NULL; + enable_msg.enable_args.dl_cb_func_id = (int) NULL; + enable_msg.enable_args.context = cpu_to_be32(MVS_PKT_CONTEXT_ISR); + + msm_rpc_setup_req(&enable_msg.rpc_hdr, MVS_PROG, + MVS_VERS, MVS_ENABLE_PROC); + + rc = msm_rpc_write(audio->rpc_endpt, + &enable_msg, sizeof(enable_msg)); + + if (rc >= 0) { + MM_DBG("RPC write for enable done\n"); + + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), 1 * HZ); + + if (rc > 0) { + MM_DBG("Wait event for enable succeeded\n"); + + mutex_lock(&audio->lock); + audio->mvs_mode = MVS_MODE_LINEAR_PCM; + audio->frame_mode = MVS_FRAME_MODE_PCM_DL; + audio->pcm_frame = 0; + mutex_unlock(&audio->lock); + rc = 0; + + } else + MM_ERR("Wait event for enable failed %d\n", rc); + } else + MM_ERR("RPC write for enable failed %d\n", rc); + return rc; +} + +static void audio_mvs_rpc_reply(struct msm_rpc_endpoint *endpoint, + uint32_t xid) +{ + int rc = 0; + struct rpc_reply_hdr reply_hdr; + MM_DBG("%s\n", __func__); + + memset(&reply_hdr, 0, sizeof(reply_hdr)); + reply_hdr.xid = cpu_to_be32(xid); + reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + reply_hdr.reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + reply_hdr.data.acc_hdr.verf_flavor = 0; + reply_hdr.data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(endpoint, &reply_hdr, sizeof(reply_hdr)); + + if (rc < 0) + MM_ERR("RPC write for response failed %d\n", rc); +} + +static void audio_mvs_process_rpc_request(uint32_t procedure, uint32_t xid, + void *data, uint32_t length, + struct audio_mvs_info_type *audio) +{ + + int rc = 0; + uint32_t index; + MM_DBG("%s\n", __func__); + switch (procedure) { + case MVS_EVENT_CB_TYPE_PROC:{ + struct audio_mvs_cb_func_args *args = data; + uint32_t event_type = be32_to_cpu(args->event); + uint32_t cmd_status = + be32_to_cpu(args-> + event_data.mvs_ev_command_type.cmd_status); + uint32_t mode_status = + be32_to_cpu(args-> + event_data.mvs_ev_mode_type.mode_status); + audio_mvs_rpc_reply(audio->rpc_endpt, xid); + if (be32_to_cpu(args->valid_ptr)) { + if (event_type == AUDIO_MVS_COMMAND) { + if (cmd_status == AUDIO_MVS_CMD_SUCCESS) + audio->rpc_status = RPC_STATUS_SUCCESS; + wake_up(&audio->wait); + } else if (event_type == AUDIO_MVS_MODE) { + if (mode_status != AUDIO_MVS_MODE_NOT_AVAIL) { + audio->rpc_status = + RPC_STATUS_SUCCESS; + } + audio->prepare_ack++; + wake_up(&audio->wait); + wake_up(&audio->prepare_wait); + } else { + /*nothing to do */ + } + } else + MM_ERR("ALSA: CB event pointer not valid\n"); + break; + } + case MVS_PACKET_UL_FN_TYPE_PROC:{ + uint32_t *cb_data = data; + uint32_t pkt_len ; + struct audio_mvs_ul_reply ul_reply; + MM_DBG("MVS_PACKET_UL_FN_TYPE_PROC\n"); + + memset(&ul_reply, 0, sizeof(ul_reply)); + cb_data++; + pkt_len = be32_to_cpu(*cb_data); + cb_data++; + if (audio->capture_enable) { + audio_mvs_info.ack_ul_count++; + mutex_lock(&audio->out_lock); + index = audio->out_write % MVS_MAX_Q_LEN; + memcpy(audio->out[index].voc_pkt, cb_data, + pkt_len); + audio->out[index].len = pkt_len; + audio->out_write++; + mutex_unlock(&audio->out_lock); + } + MM_DBG(" audio->out_read = %d audio->out write = %d\n", + audio->out_read, audio->out_write); + ul_reply.reply_hdr.xid = cpu_to_be32(xid); + ul_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + ul_reply.reply_hdr.reply_stat = + cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + ul_reply.reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + ul_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + ul_reply.reply_hdr.data.acc_hdr.verf_length = 0; + ul_reply.valid_pkt_status_ptr = cpu_to_be32(0x00000001); + ul_reply.pkt_status = cpu_to_be32(0x00000000); + rc = msm_rpc_write(audio->rpc_endpt, &ul_reply, + sizeof(ul_reply)); + wake_up(&audio->out_wait); + if (rc < 0) + MM_ERR("RPC write for UL response failed %d\n", + rc); + break; + } + case MVS_PACKET_DL_FN_TYPE_PROC:{ + struct audio_mvs_dl_reply dl_reply; + MM_DBG("MVS_PACKET_DL_FN_TYPE_PROC\n"); + memset(&dl_reply, 0, sizeof(dl_reply)); + dl_reply.reply_hdr.xid = cpu_to_be32(xid); + dl_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + dl_reply.reply_hdr.reply_stat = + cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + dl_reply.reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + dl_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + dl_reply.reply_hdr.data.acc_hdr.verf_length = 0; + mutex_lock(&audio->in_lock); + if (audio->in_read < audio->in_write + && audio->dl_play) { + index = audio->in_read % MVS_MAX_Q_LEN; + memcpy(&dl_reply.voc_pkt, + audio->in[index].voc_pkt, + audio->in[index].len); + audio->in_read++; + audio_mvs_info.ack_dl_count++; + dl_reply.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + wake_up(&audio->in_wait); + } else { + dl_reply.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_SLOW); + } + mutex_unlock(&audio->in_lock); + MM_DBG(" audio->in_read = %d audio->in write = %d\n", + audio->in_read, audio->in_write); + dl_reply.valid_frame_info_ptr = cpu_to_be32(0x00000001); + dl_reply.frame_mode = cpu_to_be32(audio->frame_mode); + dl_reply.frame_mode_again = + cpu_to_be32(audio->frame_mode); + dl_reply.frame_info_hdr.frame_mode = + cpu_to_be32(audio->frame_mode); + dl_reply.frame_info_hdr.mvs_mode = + cpu_to_be32(audio->mvs_mode); + dl_reply.frame_info_hdr.buf_free_cnt = 0; + dl_reply.pcm_frame = cpu_to_be32(audio->pcm_frame); + dl_reply.pcm_mode = cpu_to_be32(audio->pcm_mode); + dl_reply.valid_pkt_status_ptr = cpu_to_be32(0x00000001); + rc = msm_rpc_write(audio->rpc_endpt, &dl_reply, + sizeof(dl_reply)); + if (rc < 0) + MM_ERR("RPC write for DL response failed %d\n", + rc); + break; + } + default: + MM_ERR("Unknown CB type %d\n", procedure); + } +} + +static int audio_mvs_thread(void *data) +{ + struct audio_mvs_info_type *audio = &audio_mvs_info; + struct rpc_request_hdr *rpc_hdr = NULL; + struct rpc_reply_hdr *rpc_reply = NULL; + uint32_t reply_status = 0; + uint32_t rpc_type; + int rpc_hdr_len; + MM_DBG("%s\n", __func__); + + while (!kthread_should_stop()) { + rpc_hdr_len = + msm_rpc_read(audio->rpc_endpt, (void **)&rpc_hdr, -1, -1); + if (rpc_hdr_len < 0) { + MM_ERR("RPC read failed %d\n", rpc_hdr_len); + break; + } else if (rpc_hdr_len < RPC_COMMON_HDR_SZ) + continue; + else { + rpc_type = be32_to_cpu(rpc_hdr->type); + if (rpc_type == RPC_TYPE_REPLY) { + if (rpc_hdr_len < RPC_REPLY_HDR_SZ) + continue; + rpc_reply = (void *)rpc_hdr; + reply_status = be32_to_cpu(rpc_reply-> + reply_stat); + if (reply_status != RPCMSG_REPLYSTAT_ACCEPTED) { + /* If the command is not accepted, + * there will be no response callback. + * Wake the caller and report error. */ + audio->rpc_status = RPC_STATUS_REJECT; + wake_up(&audio->wait); + MM_ERR("RPC reply status denied\n"); + } + } else if (rpc_type == RPC_TYPE_REQUEST) { + if (rpc_hdr_len < RPC_REQUEST_HDR_SZ) + continue; + MM_DBG("ALSA: kthread call procedure\n"); + audio_mvs_process_rpc_request( + be32_to_cpu(rpc_hdr->procedure), + be32_to_cpu(rpc_hdr->xid), + (void *)(rpc_hdr + 1), + (rpc_hdr_len - sizeof(*rpc_hdr)), + audio); + } else + MM_ERR("Unexpected RPC type %d\n", rpc_type); + } + kfree(rpc_hdr); + rpc_hdr = NULL; + } + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + + struct audio_mvs_info_type *audio = &audio_mvs_info; + MM_DBG("%s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 1; + else + audio->capture_start = 1; + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 0; + else + audio->capture_start = 0; + break; + default: + break; + } + return 0; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + MM_DBG("%s\n", __func__); + mutex_lock(&audio->lock); + if (audio->state < AUDIO_MVS_OPENED) { + audio->rpc_endpt = + msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS, + MSM_RPC_UNINTERRUPTIBLE); + audio->state = AUDIO_MVS_OPENED; + } + + if (IS_ERR(audio->rpc_endpt)) { + MM_ERR("ALSA MVS RPC connect failed with version 0x%x\n", + MVS_VERS); + ret = PTR_ERR(audio->rpc_endpt); + audio->rpc_endpt = NULL; + goto err; + } else { + MM_DBG("ALSA MVS RPC connect succeeded\n"); + if (audio->playback_substream == NULL || + audio->capture_substream == NULL) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + audio->playback_substream = + substream; + runtime->hw = msm_pcm_hardware; + } else if (substream->stream == + SNDRV_PCM_STREAM_CAPTURE) { + audio->capture_substream = + substream; + runtime->hw = msm_pcm_hardware; + } + } else { + ret = -EPERM; + goto err; + } + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + MM_ERR("snd_pcm_hw_constraint_integer failed\n"); + if (!audio->instance) { + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + goto err; + } + audio->instance++; + } +err: + mutex_unlock(&audio->lock); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + uint32_t index; + MM_DBG("%s\n", __func__); + if (audio->dl_play == 1) { + rc = wait_event_interruptible_timeout(audio->in_wait, + (audio->in_write - audio->in_read <= 3), + 100 * HZ); + if (!rc) { + MM_ERR("MVS: write time out\n"); + return -ETIMEDOUT; + } else if (rc < 0) { + MM_ERR("MVS: write was interrupted\n"); + return -ERESTARTSYS; + } + } + mutex_lock(&audio->in_lock); + if (audio->state == AUDIO_MVS_ENABLED) { + index = audio->in_write % MVS_MAX_Q_LEN; + count = frames_to_bytes(runtime, frames); + if (count <= MVS_MAX_VOC_PKT_SIZE) { + rc = copy_from_user(audio->in[index].voc_pkt, buf, + count); + } else + rc = -ENOMEM; + if (!rc) { + audio->in[index].len = count; + audio->in_write++; + rc = count; + if (audio->in_write >= 3) + audio->dl_play = 1; + } else { + MM_ERR("Copy from user returned %d\n", rc); + rc = -EFAULT; + } + + } else { + MM_ERR("Write performed in invalid state %d\n", + audio->state); + rc = -EINVAL; + } + mutex_unlock(&audio->in_lock); + return rc; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, + void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + uint32_t index = 0; + + MM_DBG("%s\n", __func__); + + /* Ensure the driver has been enabled. */ + if (audio->state != AUDIO_MVS_ENABLED) { + MM_ERR("Read performed in invalid state %d\n", audio->state); + return -EPERM; + } + rc = wait_event_interruptible_timeout(audio->out_wait, + (audio->out_read < audio->out_write || + audio->state == AUDIO_MVS_CLOSING || + audio->state == AUDIO_MVS_CLOSED), + 100 * HZ); + if (!rc) { + MM_ERR("MVS: No UL data available\n"); + return -ETIMEDOUT; + } else if (rc < 0) { + MM_ERR("MVS: Read was interrupted\n"); + return -ERESTARTSYS; + } + + mutex_lock(&audio->out_lock); + if (audio->state == AUDIO_MVS_CLOSING + || audio->state == AUDIO_MVS_CLOSED) { + rc = -EBUSY; + } else { + count = frames_to_bytes(runtime, frames); + index = audio->out_read % MVS_MAX_Q_LEN; + if (audio->out[index].len <= count) { + rc = copy_to_user(buf, + audio->out[index].voc_pkt, + audio->out[index].len); + if (rc == 0) { + rc = audio->out[index].len; + audio->out_read++; + } else { + MM_ERR("Copy to user %d\n", rc); + rc = -EFAULT; + } + } else + rc = -ENOMEM; + } + mutex_unlock(&audio->out_lock); + return rc; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + MM_DBG("%s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_mvs_info_type *audio = &audio_mvs_info; + struct audio_mvs_release_msg release_msg; + MM_DBG("%s\n", __func__); + memset(&release_msg, 0, sizeof(release_msg)); + mutex_lock(&audio->lock); + + audio->instance--; + wake_up(&audio->out_wait); + + if (!audio->instance) { + if (audio->state == AUDIO_MVS_ENABLED) { + audio->state = AUDIO_MVS_CLOSING; + /* Release MVS. */ + release_msg.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + msm_rpc_setup_req(&release_msg.rpc_hdr, audio->rpc_prog, + audio->rpc_ver, + MVS_RELEASE_PROC); + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, &release_msg, + sizeof(release_msg)); + if (rc >= 0) { + MM_DBG("RPC write for release done\n"); + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), 1 * HZ); + if (rc != 0) { + MM_DBG + ("Wait event for release succeeded\n"); + rc = 0; + kthread_stop(audio->task); + audio->prepare_ack = 0; + audio->task = NULL; + del_timer_sync(&audio->timer); + } else { + MM_ERR + ("Wait event for release failed %d\n", + rc); + } + } else { + MM_ERR("RPC write for release failed %d\n", rc); + } + } + audio->state = AUDIO_MVS_CLOSED; + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + + mutex_unlock(&audio->lock); + + wake_unlock(&audio->suspend_lock); + pm_qos_update_request(&audio->pm_qos_req, PM_QOS_DEFAULT_VALUE); + /* Release the IO buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mutex_lock(&audio->in_lock); + audio->in_write = 0; + audio->in_read = 0; + audio->playback_enable = 0; + audio->dl_play = 0; + audio->ack_dl_count = 0; + memset(audio->in[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN); + audio->in->len = 0; + audio->playback_substream = NULL; + mutex_unlock(&audio->in_lock); + } else { + mutex_lock(&audio->out_lock); + audio->out_write = 0; + audio->out_read = 0; + audio->capture_enable = 0; + audio->ack_ul_count = 0; + memset(audio->out[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN); + audio->out->len = 0; + audio->capture_substream = NULL; + mutex_unlock(&audio->out_lock); + } + return rc; +} + +static int msm_mvs_pcm_setup(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_mvs_acquire_msg acquire_msg; + struct audio_mvs_info_type *audio = &audio_mvs_info; + memset(&acquire_msg, 0, sizeof(acquire_msg)); + + /*Create an Kthread */ + MM_DBG("ALSA MVS thread creating\n"); + if (!IS_ERR(audio->rpc_endpt)) { + audio->task = + kthread_run(audio_mvs_thread, audio, + "audio_alsa_mvs_thread"); + if (!IS_ERR(audio->task)) { + MM_DBG("ALSA MVS thread create succeeded\n"); + audio->rpc_prog = MVS_PROG; + audio->rpc_ver = MVS_VERS; + /* Acquire MVS. */ + acquire_msg.acquire_args.client_id = + cpu_to_be32(MVS_CLIENT_ID_VOIP); + acquire_msg.acquire_args.cb_func_id = + cpu_to_be32(MVS_CB_FUNC_ID); + msm_rpc_setup_req(&acquire_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_ACQUIRE_PROC); + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &acquire_msg, sizeof(acquire_msg)); + if (rc >= 0) { + MM_DBG("RPC write for acquire done\n"); + + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), + 1 * HZ); + if (rc != 0) { + audio->state = + AUDIO_MVS_ACQUIRE; + rc = 0; + MM_DBG + ("MVS driver in acquire state\n"); + } else { + MM_ERR + ("acquire Wait event failed %d\n", + rc); + rc = -EBUSY; + } + } else { + MM_ERR("RPC write for acquire failed %d\n", + rc); + rc = -EBUSY; + } + } else { + MM_ERR("ALSA MVS thread create failed\n"); + rc = PTR_ERR(audio->task); + audio->task = NULL; + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + } else { + MM_ERR("RPC connect is not setup with version 0x%x\n", + MVS_VERS); + rc = PTR_ERR(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + /*mvs mode setup */ + if (audio->state == AUDIO_MVS_ACQUIRE) + rc = audio_mvs_setup_mvs(audio); + else + rc = -EBUSY; + return rc; +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct audio_mvs_info_type *prtd = &audio_mvs_info; + MM_DBG("%s\n", __func__); + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->playback_enable = 1; + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct audio_mvs_info_type *prtd = &audio_mvs_info; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + prtd->capture_enable = 1; + return 0; +} + + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *prtd = &audio_mvs_info; + unsigned long expiry = 0; + MM_DBG("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + + mutex_lock(&prtd->prepare_lock); + if (prtd->state == AUDIO_MVS_ENABLED) + goto enabled; + else if (prtd->state == AUDIO_MVS_PREPARING) + goto prepairing; + else if (prtd->state == AUDIO_MVS_OPENED) { + prtd->state = AUDIO_MVS_PREPARING; + rc = msm_mvs_pcm_setup(substream); + } + if (!rc) { + expiry = ((unsigned long)((prtd->pcm_count * 1000) + /(runtime->rate * runtime->channels * 2))); + expiry -= (expiry % 10); + prtd->timer.expires = jiffies + (msecs_to_jiffies(expiry)); + prtd->expiry_delta = (msecs_to_jiffies(expiry)); + if (prtd->expiry_delta <= 2) + prtd->expiry_delta = 1; + setup_timer(&prtd->timer, snd_pcm_mvs_timer, + (unsigned long)prtd); + prtd->ack_ul_count = 0; + prtd->ack_dl_count = 0; + add_timer(&prtd->timer); + + } else { + MM_ERR("ALSA MVS setup is not done"); + rc = -EPERM; + prtd->state = AUDIO_MVS_OPENED; + goto err; + } + +prepairing: + rc = wait_event_interruptible(prtd->prepare_wait, + (prtd->prepare_ack == 2)); + if (rc < 0) { + MM_ERR("Wait event for prepare faild rc %d", rc); + rc = -EINTR; + prtd->state = AUDIO_MVS_OPENED; + goto err; + } else + MM_DBG("Wait event for prepare succeeded\n"); + + prtd->state = AUDIO_MVS_ENABLED; +enabled: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rc = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rc = msm_pcm_capture_prepare(substream); +err: + mutex_unlock(&prtd->prepare_lock); + return rc; +} + +int msm_mvs_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + MM_DBG("%s\n", __func__); + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + return 0; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + if (audio->pcm_playback_irq_pos >= audio->pcm_size) + audio->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + if (audio->pcm_capture_irq_pos >= audio->pcm_capture_size) + audio->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + MM_DBG("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static struct snd_pcm_ops msm_mvs_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_mvs_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int i, ret, offset = 0; + struct snd_pcm *pcm = rtd->pcm; + + audio_mvs_info.mem_chunk = kmalloc( + 2 * MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN, GFP_KERNEL); + if (audio_mvs_info.mem_chunk != NULL) { + audio_mvs_info.in_read = 0; + audio_mvs_info.in_write = 0; + audio_mvs_info.out_read = 0; + audio_mvs_info.out_write = 0; + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + audio_mvs_info.in[i].voc_pkt = + audio_mvs_info.mem_chunk + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + audio_mvs_info.out[i].voc_pkt = + audio_mvs_info.mem_chunk + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + audio_mvs_info.playback_substream = NULL; + audio_mvs_info.capture_substream = NULL; + } else { + MM_ERR("MSM MVS kmalloc failed\n"); + return -ENODEV; + } + + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_mvs_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_mvs_pcm_ops); + + return 0; +} + +struct snd_soc_platform_driver msm_mvs_soc_platform = { + .ops = &msm_mvs_pcm_ops, + .pcm_new = msm_pcm_new, +}; +EXPORT_SYMBOL(msm_mvs_soc_platform); + +static int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_mvs_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-mvs-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_mvs_soc_platform_init(void) +{ + memset(&audio_mvs_info, 0, sizeof(audio_mvs_info)); + mutex_init(&audio_mvs_info.lock); + mutex_init(&audio_mvs_info.prepare_lock); + mutex_init(&audio_mvs_info.in_lock); + mutex_init(&audio_mvs_info.out_lock); + init_waitqueue_head(&audio_mvs_info.wait); + init_waitqueue_head(&audio_mvs_info.prepare_wait); + init_waitqueue_head(&audio_mvs_info.out_wait); + init_waitqueue_head(&audio_mvs_info.in_wait); + wake_lock_init(&audio_mvs_info.suspend_lock, WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + pm_qos_add_request(&audio_mvs_info.pm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_mvs_soc_platform_init); + +static void __exit msm_mvs_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_mvs_soc_platform_exit); + +MODULE_DESCRIPTION("MVS PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-afe.c b/sound/soc/msm/msm-pcm-afe.c new file mode 100644 index 000000000000..bc4a6ac1e908 --- /dev/null +++ b/sound/soc/msm/msm-pcm-afe.c @@ -0,0 +1,621 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-afe.h" + +#define MIN_PERIOD_SIZE (128 * 2) +#define MAX_PERIOD_SIZE (128 * 2 * 2 * 6) +static struct snd_pcm_hardware msm_afe_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_PERIOD_SIZE * 32, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = 32, + .periods_max = 384, + .fifo_size = 0, +}; +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt); +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt); + +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + if (prtd->start) { + pr_debug("sending frame to DSP: poll_time: %d\n", + prtd->poll_time); + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + afe_rt_proxy_port_write( + (prtd->dma_addr + + (prtd->dsp_cnt * + snd_pcm_lib_period_bytes(prtd->substream))), + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + if (prtd->start) { + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + afe_rt_proxy_port_read( + (prtd->dma_addr + (prtd->dsp_cnt + * snd_pcm_lib_period_bytes(prtd->substream))), + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + pr_debug("sending frame rec to DSP: poll_time: %d\n", + prtd->poll_time); + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static void pcm_afe_process_tx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes + (prtd->substream) * + 1000 * 1000)/ + (runtime->rate * + runtime->channels * 2))); + pr_debug("prtd->poll_time: %d", + prtd->poll_time); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_SERVICE_CMD_RTPORT_WR: + pr_debug("write done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static void pcm_afe_process_rx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes(prtd->substream) + * 1000 * 1000)/(runtime->rate + * runtime->channels * 2))); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + pr_debug("prtd->poll_time : %d", prtd->poll_time); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_SERVICE_CMD_RTPORT_RD: + pr_debug("Read done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static int msm_afe_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_tx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return ret; +} + +static int msm_afe_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s\n", __func__); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_rx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return 0; +} + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 16000, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_afe_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = NULL; + int ret = 0; + + prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } else + pr_debug("prtd %x\n", (unsigned int)prtd); + + mutex_init(&prtd->lock); + spin_lock_init(&prtd->dsp_lock); + prtd->dsp_cnt = 0; + + mutex_lock(&prtd->lock); + + runtime->hw = msm_afe_hardware; + prtd->substream = substream; + runtime->private_data = prtd; + mutex_unlock(&prtd->lock); + hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->hrt.function = afe_hrtimer_callback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->hrt.function = afe_hrtimer_rec_callback; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + return 0; +} + +static int msm_afe_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_dma_buffer *dma_buf; + struct snd_pcm_runtime *runtime; + struct pcm_afe_info *prtd; + struct snd_soc_pcm_runtime *rtd = NULL; + struct snd_soc_dai *dai = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + rtd = substream->private_data; + dai = rtd->cpu_dai; + runtime = substream->runtime; + prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } + hrtimer_cancel(&prtd->hrt); + + rc = afe_cmd_memory_unmap(runtime->dma_addr); + if (rc < 0) + pr_err("AFE memory unmap failed\n"); + + pr_debug("release all buffer\n"); + dma_buf = &substream->dma_buffer; + if (dma_buf == NULL) { + pr_debug("dma_buf is NULL\n"); + goto done; + } + + if (dma_buf->area) { + if (msm_subsystem_unmap_buffer(prtd->mem_buffer) < 0) { + pr_err("%s: unmap buffer failed\n", __func__); + prtd->mem_buffer = NULL; + dma_buf->area = NULL; + } + } + + if (dma_buf->addr) + free_contiguous_memory_by_paddr(dma_buf->addr); +done: + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + mutex_unlock(&prtd->lock); + prtd->prepared--; + kfree(prtd); + return 0; +} +static int msm_afe_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + prtd->pcm_irq_pos = 0; + if (prtd->prepared) + return 0; + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_afe_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_afe_capture_prepare(substream); + mutex_unlock(&prtd->lock); + return ret; +} +static int msm_afe_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + int result = 0; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} +static int msm_afe_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: SNDRV_PCM_TRIGGER_START\n", __func__); + prtd->start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + prtd->start = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +static int msm_afe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct pcm_afe_info *prtd = runtime->private_data; + int rc; + unsigned int flags = 0; + + pr_debug("%s:\n", __func__); + + mutex_lock(&prtd->lock); + + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + + dma_buf->addr = allocate_contiguous_ebi_nomap( + runtime->hw.buffer_bytes_max, SZ_4K); + if (!dma_buf->addr) { + pr_err("%s:MSM AFE physical memory allocation failed\n", + __func__); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + flags = MSM_SUBSYSTEM_MAP_KADDR | MSM_SUBSYSTEM_MAP_CACHED; + + prtd->mem_buffer = msm_subsystem_map_buffer(dma_buf->addr, + runtime->hw.buffer_bytes_max, flags, + NULL, 0); + if (IS_ERR((void *) prtd->mem_buffer)) { + pr_err("%s: map_buffer failed error = %ld\n", __func__, + PTR_ERR((void *)prtd->mem_buffer)); + free_contiguous_memory_by_paddr(dma_buf->addr); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + dma_buf->area = prtd->mem_buffer->vaddr; + + pr_debug("%s: dma_buf->area: 0x%p, dma_buf->addr: 0x%x", __func__, + (unsigned int *) dma_buf->area, dma_buf->addr); + + if (!dma_buf->area) { + pr_err("%s: Invalid Virtual address\n", __func__); + if (prtd->mem_buffer) { + msm_subsystem_unmap_buffer(prtd->mem_buffer); + prtd->mem_buffer = NULL; + dma_buf->area = NULL; + } + free_contiguous_memory_by_paddr(dma_buf->addr); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + prtd->dma_addr = (u32) dma_buf->addr; + + mutex_unlock(&prtd->lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + rc = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes); + if (rc < 0) + pr_err("fail to map memory to DSP\n"); + + return rc; +} +static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream)) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static struct snd_pcm_ops msm_afe_ops = { + .open = msm_afe_open, + .hw_params = msm_afe_hw_params, + .trigger = msm_afe_trigger, + .close = msm_afe_close, + .prepare = msm_afe_prepare, + .mmap = msm_afe_mmap, + .pointer = msm_afe_pointer, +}; + + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + pr_debug("%s\n", __func__); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_afe_afe_probe(struct snd_soc_platform *platform) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_afe_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_afe_afe_probe, +}; + +static int msm_afe_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_afe_remove(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_afe_driver = { + .driver = { + .name = "msm-pcm-afe", + .owner = THIS_MODULE, + }, + .probe = msm_afe_probe, + .remove = msm_afe_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + pr_debug("%s\n", __func__); + return platform_driver_register(&msm_afe_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + pr_debug("%s\n", __func__); + platform_driver_unregister(&msm_afe_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("AFE PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-afe.h b/sound/soc/msm/msm-pcm-afe.h new file mode 100644 index 000000000000..cf7a1cb6bb2a --- /dev/null +++ b/sound/soc/msm/msm-pcm-afe.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_AFE_H +#define _MSM_PCM_AFE_H +#include +#include + + +struct pcm_afe_info { + unsigned long dma_addr; + struct snd_pcm_substream *substream; + unsigned int pcm_irq_pos; /* IRQ position */ + struct mutex lock; + spinlock_t dsp_lock; + uint32_t samp_rate; + uint32_t channel_mode; + uint8_t start; + uint32_t dsp_cnt; + uint32_t buf_phys; + int32_t mmap_flag; + int prepared; + struct hrtimer hrt; + int poll_time; + struct msm_mapped_buffer *mem_buffer; +}; + + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ + } + +#endif /*_MSM_PCM_AFE_H*/ diff --git a/sound/soc/msm/msm-pcm-hostless.c b/sound/soc/msm/msm-pcm-hostless.c new file mode 100644 index 000000000000..6569ab0befbf --- /dev/null +++ b/sound/soc/msm/msm-pcm-hostless.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +static struct snd_pcm_ops msm_pcm_hostless_ops = {}; + +static struct snd_soc_platform_driver msm_soc_hostless_platform = { + .ops = &msm_pcm_hostless_ops, +}; + +static int msm_pcm_hostless_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_hostless_platform); +} + +static int msm_pcm_hostless_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_hostless_driver = { + .driver = { + .name = "msm-pcm-hostless", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_hostless_probe, + .remove = msm_pcm_hostless_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_hostless_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_hostless_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Hostless platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-lpa.c b/sound/soc/msm/msm-pcm-lpa.c new file mode 100644 index 000000000000..b2a3eceee1be --- /dev/null +++ b/sound/soc/msm/msm-pcm-lpa.c @@ -0,0 +1,610 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm lpa_audio; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 1024 * 1024, +/* TODO: Check on the lowest period size we can support */ + .period_bytes_min = 128 * 1024, + .period_bytes_max = 256 * 1024, + .periods_min = 4, + .periods_max = 8, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + unsigned long flag = 0; + int i = 0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&the_locks.event_lock, flag); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) { + memset((void *)buf[0].phys + + (prtd->out_head * prtd->pcm_count), + 0, prtd->pcm_count); + } + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + for (i = 0; i < sizeof(struct audio_aio_write_param)/4; + i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: { + if (!atomic_read(&prtd->pending_buffer)) + break; + if (runtime->status->hw_ptr >= + runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, prtd->pcm_count); + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[prtd->out_head].phys; + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[prtd->out_head].phys; + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) + & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } + spin_unlock_irqrestore(&the_locks.event_lock, flag); +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + prtd->out_head = 0; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_debug("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + prtd->enabled = 1; + prtd->cmd_ack = 0; + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("SNDRV_PCM_TRIGGER_START\n"); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_debug("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EPERM; + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + atomic_set(&prtd->pending_buffer, 1); + runtime->private_data = prtd; + lpa_audio.prtd = prtd; + lpa_set_volume(lpa_audio.volume); + ret = q6asm_set_softpause(lpa_audio.prtd->audio_client, &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(lpa_audio.prtd->audio_client, &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int lpa_set_volume(unsigned volume) +{ + int rc = 0; + if (lpa_audio.prtd && lpa_audio.prtd->audio_client) { + rc = q6asm_set_volume(lpa_audio.prtd->audio_client, volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + lpa_audio.volume = volume; + return rc; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int rc = 0; + + /* + If routing is still enabled, we need to issue EOS to + the DSP + To issue EOS to dsp, we need to be run state otherwise + EOS is not honored. + */ + if (msm_routing_check_backend_enabled(soc_prtd->dai_link->be_id)) { + rc = q6asm_run(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->pending_buffer, 0); + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + pr_debug("%s\n", __func__); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("EOS cmd timeout\n"); + prtd->pcm_irq_pos = 0; + } + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + lpa_audio.prtd = NULL; + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + pr_debug("%s\n", __func__); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + pr_debug("%s\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s: pcm_irq_pos = %d\n", __func__, prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EPERM; + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + if (buf == NULL || buf[0].data == NULL) + return -ENOMEM; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int msm_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:" + "timestamp = %lld,\n",__func__, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = msm_pcm_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", + __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-lpa", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + spin_lock_init(&the_locks.event_lock); + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-q6.c b/sound/soc/msm/msm-pcm-q6.c new file mode 100644 index 000000000000..6f6abdbe0e6f --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.c @@ -0,0 +1,740 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 2048 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static uint32_t in_frame_info[CAPTURE_NUM_PERIODS][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + uint32_t idx = 0; + uint32_t size = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + if (q6asm_is_cpu_buf_avail_nolock(IN, + prtd->audio_client, + &size, &idx)) { + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + } + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag + && q6asm_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + break; + } + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + atomic_set(&prtd->start, 1); + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + if (prtd->channel_mode > 2) { + ret = q6asm_enc_cfg_blk_multi_ch_pcm(prtd->audio_client, + prtd->samp_rate, prtd->channel_mode); + } else { + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, + prtd->samp_rate, prtd->channel_mode); + } + + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + prtd->periods = runtime->periods; + + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_hardware_capture; + } + + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_err("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_CAPTURE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + int format = FORMAT_LINEAR_PCM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + /*capture path*/ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (params_channels(params) > 2) + format = FORMAT_MULTI_CHANNEL_LINEAR_PCM; + pr_debug("%s format = :0x%x\n", __func__, format); + + ret = q6asm_open_read(prtd->audio_client, format); + if (ret < 0) { + pr_err("%s: q6asm_open_read failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + if (buf == NULL || buf[0].data == NULL) + return -ENOMEM; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-q6.h b/sound/soc/msm/msm-pcm-q6.h new file mode 100644 index 000000000000..ad20b43d2c35 --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009,2011 The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H +#include +#include + + +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +extern int copy_count; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + spinlock_t event_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +struct msm_audio { + struct snd_pcm_substream *substream; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + uint16_t source; /* Encoding source bit mask */ + + struct audio_client *audio_client; + + uint16_t session_id; + + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t dsp_cnt; + + int abort; /* set when error, like sample rate mismatch */ + + int enabled; + int close_ack; + int cmd_ack; + atomic_t start; + atomic_t out_count; + atomic_t in_count; + atomic_t out_needed; + int out_head; + int periods; + int mmap_flag; + atomic_t pending_buffer; +}; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-pcm-routing.c b/sound/soc/msm/msm-pcm-routing.c new file mode 100644 index 000000000000..9e46c53d1d9c --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.c @@ -0,0 +1,2478 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "qdsp6/q6voice.h" + +struct msm_pcm_routing_bdai_data { + u16 port_id; /* AFE port ID */ + u8 active; /* track if this backend is enabled */ + unsigned long fe_sessions; /* Front-end sessions */ + unsigned long port_sessions; /* track Tx BE ports -> Rx BE */ + unsigned int sample_rate; + unsigned int channel; +}; + +#define INVALID_SESSION -1 +#define SESSION_TYPE_RX 0 +#define SESSION_TYPE_TX 1 + +static struct mutex routing_lock; + +static int fm_switch_enable; +static int fm_pcmrx_switch_enable; + +#define INT_RX_VOL_MAX_STEPS 0x2000 +#define INT_RX_VOL_GAIN 0x2000 + +static int msm_route_fm_vol_control; +static const DECLARE_TLV_DB_LINEAR(fm_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_lpa_vol_control; +static const DECLARE_TLV_DB_LINEAR(lpa_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_multimedia2_vol_control; +static const DECLARE_TLV_DB_LINEAR(multimedia2_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + +static int msm_route_compressed_vol_control; +static const DECLARE_TLV_DB_LINEAR(compressed_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS); + + + +/* Equal to Frontend after last of the MULTIMEDIA SESSIONS */ +#define MAX_EQ_SESSIONS MSM_FRONTEND_DAI_CS_VOICE + +enum { + EQ_BAND1 = 0, + EQ_BAND2, + EQ_BAND3, + EQ_BAND4, + EQ_BAND5, + EQ_BAND6, + EQ_BAND7, + EQ_BAND8, + EQ_BAND9, + EQ_BAND10, + EQ_BAND11, + EQ_BAND12, + EQ_BAND_MAX, +}; + +struct msm_audio_eq_band { + uint16_t band_idx; /* The band index, 0 .. 11 */ + uint32_t filter_type; /* Filter band type */ + uint32_t center_freq_hz; /* Filter band center frequency */ + uint32_t filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + uint32_t q_factor; +} __packed; + +struct msm_audio_eq_stream_config { + uint32_t enable; /* Number of consequtive bands specified */ + uint32_t num_bands; + struct msm_audio_eq_band eq_bands[EQ_BAND_MAX]; +} __packed; + +struct msm_audio_eq_stream_config eq_data[MAX_EQ_SESSIONS]; + +static void msm_send_eq_values(int eq_idx); +/* This array is indexed by back-end DAI ID defined in msm-pcm-routing.h + * If new back-end is defined, add new back-end DAI ID at the end of enum + */ + +union srs_trumedia_params_u { + struct srs_trumedia_params srs_params; + unsigned short int raw_params[1]; +}; +static union srs_trumedia_params_u msm_srs_trumedia_params[2]; +static int srs_port_id = -1; + +static void srs_send_params(int port_id, unsigned int techs, + int param_block_idx) { + pr_debug("SRS %s: called, port_id = %d, techs flags = %u," + " paramblockidx %d", __func__, port_id, techs, + param_block_idx); + /* force all if techs is set to 1 */ + if (techs == 1) + techs = 0xFFFFFFFF; + + if (techs & (1 << SRS_ID_WOWHD)) + srs_trumedia_open(port_id, SRS_ID_WOWHD, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.wowhd); + if (techs & (1 << SRS_ID_CSHP)) + srs_trumedia_open(port_id, SRS_ID_CSHP, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.cshp); + if (techs & (1 << SRS_ID_HPF)) + srs_trumedia_open(port_id, SRS_ID_HPF, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.hpf); + if (techs & (1 << SRS_ID_PEQ)) + srs_trumedia_open(port_id, SRS_ID_PEQ, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.peq); + if (techs & (1 << SRS_ID_HL)) + srs_trumedia_open(port_id, SRS_ID_HL, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.hl); + if (techs & (1 << SRS_ID_GLOBAL)) + srs_trumedia_open(port_id, SRS_ID_GLOBAL, + (void *)&msm_srs_trumedia_params[param_block_idx].srs_params.global); +} + +static struct msm_pcm_routing_bdai_data msm_bedais[MSM_BACKEND_DAI_MAX] = { + { PRIMARY_I2S_RX, 0, 0, 0, 0, 0}, + { PRIMARY_I2S_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_0_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_0_TX, 0, 0, 0, 0, 0}, + { HDMI_RX, 0, 0, 0, 0, 0}, + { INT_BT_SCO_RX, 0, 0, 0, 0, 0}, + { INT_BT_SCO_TX, 0, 0, 0, 0, 0}, + { INT_FM_RX, 0, 0, 0, 0, 0}, + { INT_FM_TX, 0, 0, 0, 0, 0}, + { RT_PROXY_PORT_001_RX, 0, 0, 0, 0, 0}, + { RT_PROXY_PORT_001_TX, 0, 0, 0, 0, 0}, + { PCM_RX, 0, 0, 0, 0, 0}, + { PCM_TX, 0, 0, 0, 0, 0}, + { VOICE_PLAYBACK_TX, 0, 0, 0, 0, 0}, + { VOICE_RECORD_RX, 0, 0, 0, 0, 0}, + { VOICE_RECORD_TX, 0, 0, 0, 0, 0}, + { MI2S_RX, 0, 0, 0, 0, 0}, + { MI2S_TX, 0, 0, 0, 0}, + { SECONDARY_I2S_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_1_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_1_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_4_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_4_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_3_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_3_TX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, + { SLIMBUS_EXTPROC_RX, 0, 0, 0, 0, 0}, + { SECONDARY_PCM_RX, 0, 0, 0, 0, 0}, + { SECONDARY_PCM_TX, 0, 0, 0, 0, 0}, +}; + + +/* Track ASM playback & capture sessions of DAI */ +static int fe_dai_map[MSM_FRONTEND_DAI_MM_SIZE][2] = { + /* MULTIMEDIA1 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA2 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA3 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA4 */ + {INVALID_SESSION, INVALID_SESSION}, +}; + +static uint8_t is_be_dai_extproc(int be_dai) +{ + if (be_dai == MSM_BACKEND_DAI_EXTPROC_RX || + be_dai == MSM_BACKEND_DAI_EXTPROC_TX || + be_dai == MSM_BACKEND_DAI_EXTPROC_EC_TX) + return 1; + else + return 0; +} + +static void msm_pcm_routing_build_matrix(int fedai_id, int dspst_id, + int path_type) +{ + int i, port_type; + struct route_payload payload; + + payload.num_copps = 0; + port_type = (path_type == ADM_PATH_PLAYBACK ? + MSM_AFE_PORT_TYPE_RX : MSM_AFE_PORT_TYPE_TX); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); +} + +void msm_pcm_routing_reg_psthr_stream(int fedai_id, int dspst_id, + int stream_type) +{ + int i, session_type, path_type, port_type; + u32 mode = 0; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + fe_dai_map[fedai_id][session_type] = dspst_id; + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) { + mode = afe_get_port_type(msm_bedais[i].port_id); + adm_connect_afe_port(mode, dspst_id, + msm_bedais[i].port_id); + break; + } + } + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, int stream_type) +{ + int i, session_type, path_type, port_type; + struct route_payload payload; + u32 channels; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID %d\n", __func__, fedai_id); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + payload.num_copps = 0; /* only RX needs to use payload */ + fe_dai_map[fedai_id][session_type] = dspst_id; + /* re-enable EQ if active */ + if (eq_data[fedai_id].enable) + msm_send_eq_values(fedai_id); + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) { + + channels = msm_bedais[i].channel; + + if ((stream_type == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[i].port_id, + path_type, + msm_bedais[i].sample_rate, + msm_bedais[i].channel, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[i].port_id, + path_type, + msm_bedais[i].sample_rate, + msm_bedais[i].channel, + DEFAULT_COPP_TOPOLOGY); + + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + } + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); + + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type) +{ + int i, port_type, session_type; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + port_type = MSM_AFE_PORT_TYPE_RX; + session_type = SESSION_TYPE_RX; + } else { + port_type = MSM_AFE_PORT_TYPE_TX; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (!is_be_dai_extproc(i) && + (afe_get_port_type(msm_bedais[i].port_id) == port_type) && + (msm_bedais[i].active) && + (test_bit(fedai_id, &msm_bedais[i].fe_sessions))) + adm_close(msm_bedais[i].port_id); + } + + fe_dai_map[fedai_id][session_type] = INVALID_SESSION; + + mutex_unlock(&routing_lock); +} + +/* Check if FE/BE route is set */ +static bool msm_pcm_routing_route_is_set(u16 be_id, u16 fe_id) +{ + bool rc = false; + + if (fe_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return rc; + } + + if (test_bit(fe_id, &msm_bedais[be_id].fe_sessions)) + rc = true; + + return rc; +} + +static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) +{ + int session_type, path_type; + u32 channels; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + } + + mutex_lock(&routing_lock); + + if (set) { + if (!test_bit(val, &msm_bedais[reg].fe_sessions) && + (msm_bedais[reg].port_id == VOICE_PLAYBACK_TX)) + voc_start_playback(set); + + set_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + + channels = msm_bedais[reg].channel; + + if ((session_type == SESSION_TYPE_RX) && (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[reg].port_id, + path_type, + msm_bedais[reg].sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[reg].port_id, + path_type, + msm_bedais[reg].sample_rate, channels, + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } else { + if (test_bit(val, &msm_bedais[reg].fe_sessions) && + (msm_bedais[reg].port_id == VOICE_PLAYBACK_TX)) + voc_start_playback(set); + clear_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + adm_close(msm_bedais[reg].port_id); + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } + if ((msm_bedais[reg].port_id == VOICE_RECORD_RX) + || (msm_bedais[reg].port_id == VOICE_RECORD_TX)) + voc_start_record(msm_bedais[reg].port_id, set); + + mutex_unlock(&routing_lock); +} + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + + if (ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == false) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else if (!ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == true) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + return 1; +} + +static void msm_pcm_routing_process_voice(u16 reg, u16 val, int set) +{ + u16 session_id = 0; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val == MSM_FRONTEND_DAI_CS_VOICE) + session_id = voc_get_session_id(VOICE_SESSION_NAME); + else if (val == MSM_FRONTEND_DAI_VOLTE) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOIP_SESSION_NAME); + + pr_debug("%s: FE DAI 0x%x session_id 0x%x\n", + __func__, val, session_id); + + mutex_lock(&routing_lock); + + if (set) + set_bit(val, &msm_bedais[reg].fe_sessions); + else + clear_bit(val, &msm_bedais[reg].fe_sessions); + + mutex_unlock(&routing_lock); + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + voc_set_route_flag(session_id, RX_PATH, set); + if (set) { + voc_set_rxtx_port(session_id, + msm_bedais[reg].port_id, DEV_RX); + + if (voc_get_route_flag(session_id, RX_PATH) && + voc_get_route_flag(session_id, TX_PATH)) + voc_enable_cvp(session_id); + } else { + voc_disable_cvp(session_id); + } + } else { + voc_set_route_flag(session_id, TX_PATH, set); + if (set) { + voc_set_rxtx_port(session_id, + msm_bedais[reg].port_id, DEV_TX); + if (voc_get_route_flag(session_id, RX_PATH) && + voc_get_route_flag(session_id, TX_PATH)) + voc_enable_cvp(session_id); + } else { + voc_disable_cvp(session_id); + } + } +} + +static int msm_routing_get_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + return 1; +} + +static int msm_routing_get_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + mutex_lock(&routing_lock); + set_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + mutex_lock(&routing_lock); + clear_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static int msm_routing_get_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_switch_enable = ucontrol->value.integer.value[0]; + return 1; +} + +static int msm_routing_get_fm_pcmrx_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_pcmrx_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_fm_pcmrx_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_pcmrx_switch_enable = ucontrol->value.integer.value[0]; + return 1; +} + +static int msm_routing_get_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &msm_bedais[mc->reg].port_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, + mc->shift, ucontrol->value.integer.value[0]); + + if (ucontrol->value.integer.value[0]) { + afe_loopback(1, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + set_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } else { + afe_loopback(0, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + clear_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } + + return 1; +} + +static int msm_routing_get_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_fm_vol_control; + return 0; +} + +static int msm_routing_set_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + afe_loopback_gain(INT_FM_TX , ucontrol->value.integer.value[0]); + + msm_route_fm_vol_control = ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_lpa_vol_control; + return 0; +} + +static int msm_routing_set_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!lpa_set_volume(ucontrol->value.integer.value[0])) + msm_route_lpa_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_multimedia2_vol_control; + return 0; +} + +static int msm_routing_set_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + if (!multi_ch_pcm_set_volume(ucontrol->value.integer.value[0])) + msm_route_multimedia2_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_compressed_vol_control; + return 0; +} + +static int msm_routing_set_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!compressed_set_volume(ucontrol->value.integer.value[0])) + msm_route_compressed_vol_control = + ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_srs_trumedia_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_routing_set_srs_trumedia_control_(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int techs = 0; + unsigned short offset, value, max, index; + + max = sizeof(msm_srs_trumedia_params) >> 1; + index = (unsigned short)((ucontrol->value.integer.value[0] & + SRS_PARAM_INDEX_MASK) >> 31); + if (SRS_CMD_UPLOAD == + (ucontrol->value.integer.value[0] & SRS_CMD_UPLOAD)) { + techs = ucontrol->value.integer.value[0] & 0xFF; + pr_debug("SRS %s: send params request, flags = %u", + __func__, techs); + if (srs_port_id >= 0 && techs) + srs_send_params(srs_port_id, techs, index); + return 0; + } + offset = (unsigned short)((ucontrol->value.integer.value[0] & + SRS_PARAM_OFFSET_MASK) >> 16); + value = (unsigned short)(ucontrol->value.integer.value[0] & + SRS_PARAM_VALUE_MASK); + if (offset < max) { + msm_srs_trumedia_params[index].raw_params[offset] = value; + pr_debug("SRS %s: index set... (max %d, requested %d," + " val %d, paramblockidx %d)", __func__, max, offset, + value, index); + } else { + pr_err("SRS %s: index out of bounds! (max %d, requested %d)", + __func__, max, offset); + } + if (offset == 4) { + int i; + for (i = 0; i < max; i++) { + if (i == 0) { + pr_debug("SRS %s: global block start", + __func__); + } + if (i == + (sizeof(struct srs_trumedia_params_GLOBAL) >> 1)) { + break; + pr_debug("SRS %s: wowhd block start at" + " offset %d word offset %d", __func__, + i, i>>1); + } + pr_debug("SRS %s: param_index %d index %d val %d", + __func__, index, i, + msm_srs_trumedia_params[index].raw_params[i]); + } + } + return 0; +} + +static int msm_routing_set_srs_trumedia_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control normal called"); + mutex_lock(&routing_lock); + srs_port_id = SLIMBUS_0_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static int msm_routing_set_srs_trumedia_control_I2S( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control I2S called"); + mutex_lock(&routing_lock); + srs_port_id = PRIMARY_I2S_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static int msm_routing_set_srs_trumedia_control_HDMI( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + int ret; + + pr_debug("SRS control HDMI called"); + mutex_lock(&routing_lock); + srs_port_id = HDMI_RX; + ret = msm_routing_set_srs_trumedia_control_(kcontrol, ucontrol); + mutex_unlock(&routing_lock); + return ret; +} + +static void msm_send_eq_values(int eq_idx) +{ + int result; + struct audio_client *ac = + q6asm_get_audio_client(fe_dai_map[eq_idx][SESSION_TYPE_RX]); + + if (ac == NULL) { + pr_err("%s: Could not get audio client for session: %d\n", + __func__, fe_dai_map[eq_idx][SESSION_TYPE_RX]); + goto done; + } + + result = q6asm_equalizer(ac, &eq_data[eq_idx]); + + if (result < 0) + pr_err("%s: Call to ASM equalizer failed, returned = %d\n", + __func__, result); +done: + return; +} + +static int msm_routing_get_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].enable; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, eq_data[eq_idx].enable); + return 0; +} + +static int msm_routing_put_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].enable = value; + + msm_send_eq_values(eq_idx); + return 0; +} + +static int msm_routing_get_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].num_bands; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, eq_data[eq_idx].num_bands); + return eq_data[eq_idx].num_bands; +} + +static int msm_routing_put_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].num_bands = value; + return 0; +} + +static int msm_routing_get_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + eq_data[eq_idx].eq_bands[band_idx].band_idx; + ucontrol->value.integer.value[1] = + eq_data[eq_idx].eq_bands[band_idx].filter_type; + ucontrol->value.integer.value[2] = + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz; + ucontrol->value.integer.value[3] = + eq_data[eq_idx].eq_bands[band_idx].filter_gain; + ucontrol->value.integer.value[4] = + eq_data[eq_idx].eq_bands[band_idx].q_factor; + + pr_debug("%s: band_idx = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].band_idx); + pr_debug("%s: filter_type = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_type); + pr_debug("%s: center_freq_hz = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz); + pr_debug("%s: filter_gain = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_gain); + pr_debug("%s: q_factor = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].q_factor); + return 0; +} + +static int msm_routing_put_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + eq_data[eq_idx].eq_bands[band_idx].band_idx = + ucontrol->value.integer.value[0]; + eq_data[eq_idx].eq_bands[band_idx].filter_type = + ucontrol->value.integer.value[1]; + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz = + ucontrol->value.integer.value[2]; + eq_data[eq_idx].eq_bands[band_idx].filter_gain = + ucontrol->value.integer.value[3]; + eq_data[eq_idx].eq_bands[band_idx].q_factor = + ucontrol->value.integer.value[4]; + return 0; +} + +static const struct snd_kcontrol_new pri_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_PRI_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SEC_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_MI2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + /* incall music delivery mixer */ +static const struct snd_kcontrol_new incall_music_delivery_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_bt_sco_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_fm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new sec_auxpcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SEC_AUX_PCM_UL_TX", MSM_BACKEND_DAI_SEC_AUXPCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_DL", MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_UL", MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_4_TX", MSM_BACKEND_DAI_SLIMBUS_4_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new pri_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new bt_sco_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new aux_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new sec_aux_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new hdmi_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("VoLTE", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new stub_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_EXTPROC_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voice", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("MI2S_TX_Voice", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voice", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voice", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_CS_VOICE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voice", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voice", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SEC_AUX_PCM_TX_Voice", MSM_BACKEND_DAI_SEC_AUXPCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_volte_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_VoLTE", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_VoLTE", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_VoLTE", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_VOLTE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_VoLTE", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_VoLTE", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SEC_AUX_PCM_TX_VoLTE", MSM_BACKEND_DAI_SEC_AUXPCM_TX, + MSM_FRONTEND_DAI_VOLTE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voip_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voip", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("MI2S_TX_Voip", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voip", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voip", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voip", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voip", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SEC_AUX_PCM_TX_Voip", MSM_BACKEND_DAI_SEC_AUXPCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_stub_mixer_controls[] = { + SOC_SINGLE_EXT("STUB_TX_HL", MSM_BACKEND_DAI_EXTPROC_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("STUB_1_TX_HL", MSM_BACKEND_DAI_EXTPROC_EC_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_MI2S_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("SLIM_3_TX", MSM_BACKEND_DAI_SLIMBUS_3_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new sbus_0_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_1_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_3_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_RX", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_INT_BT_SCO_RX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; +static const struct snd_kcontrol_new bt_sco_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + + +static const struct snd_kcontrol_new hdmi_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_3_TX", MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_SLIMBUS_3_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("MI2S_TX", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_MI2S_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new fm_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_switch_mixer, + msm_routing_put_switch_mixer); + +static const struct snd_kcontrol_new pcm_rx_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_fm_pcmrx_switch_mixer, + msm_routing_put_fm_pcmrx_switch_mixer); + +static const struct snd_kcontrol_new int_fm_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("Internal FM RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_fm_vol_mixer, + msm_routing_set_fm_vol_mixer, fm_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("LPA RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_lpa_vol_mixer, + msm_routing_set_lpa_vol_mixer, lpa_rx_vol_gain), +}; + +static const struct snd_kcontrol_new multimedia2_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("HIFI2 RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_multimedia2_vol_mixer, + msm_routing_set_multimedia2_vol_mixer, multimedia2_rx_vol_gain), +}; + +static const struct snd_kcontrol_new compressed_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("COMPRESSED RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_compressed_vol_mixer, + msm_routing_set_compressed_vol_mixer, compressed_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls_HDMI[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia HDMI", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control_HDMI, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new lpa_SRS_trumedia_controls_I2S[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "SRS TruMedia I2S", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_soc_info_volsw, \ + .get = msm_routing_get_srs_trumedia_control, + .put = msm_routing_set_srs_trumedia_control_I2S, + .private_value = ((unsigned long)&(struct soc_mixer_control) + {.reg = SND_SOC_NOPM, + .shift = 0, + .rshift = 0, + .max = 0xFFFFFFFF, + .platform_max = 0xFFFFFFFF, + .invert = 0 + }) + } +}; + +static const struct snd_kcontrol_new eq_enable_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), +}; + +static const struct snd_kcontrol_new eq_band_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), +}; + +static const struct snd_kcontrol_new eq_coeff_mixer_controls[] = { + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), +}; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Frontend AIF */ + /* Widget name equals to Front-End DAI name, + * Stream name must contains substring of front-end dai name + */ + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOIP_DL", "VoIP Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VoLTE_DL", "VoLTE Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VoLTE_UL", "VoLTE Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOIP_UL", "VoIP Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM0_DL_HL", "SLIMBUS0_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIM0_UL_HL", "SLIMBUS0_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INTFM_DL_HL", "INT_FM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INTFM_UL_HL", "INT_FM_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("HDMI_DL_HL", "HDMI_HOSTLESS Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_I2S_DL_HL", "SEC_I2S_RX_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUXPCM_DL_HL", "AUXPCM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUXPCM_UL_HL", "AUXPCM_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MI2S_UL_HL", "MI2S_TX_HOSTLESS Capture", + 0, 0, 0, 0), + + /* Backend AIF */ + /* Stream name equals to backend dai link stream name + */ + SND_SOC_DAPM_AIF_OUT("PRI_I2S_RX", "Primary I2S Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_I2S_RX", "Secondary I2S Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("MI2S_RX", "MI2S Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRI_I2S_TX", "Primary I2S Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MI2S_TX", "MI2S Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_0_TX", "Slimbus Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_BT_SCO_RX", "Internal BT-SCO Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_BT_SCO_TX", "Internal BT-SCO Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_FM_RX", "Internal FM Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_FM_TX", "Internal FM Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PCM_RX", "AFE Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("PCM_TX", "AFE Capture", + 0, 0, 0 , 0), + /* incall */ + SND_SOC_DAPM_AIF_OUT("VOICE_PLAYBACK_TX", "Voice Farend Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_RX", "Slimbus4 Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_TX", "Voice Uplink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_RX", "Voice Downlink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_4_TX", "Slimbus4 Capture", + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AUX_PCM_RX", "AUX PCM Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUX_PCM_TX", "AUX PCM Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_AUX_PCM_RX", "SEC AUX PCM Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_AUX_PCM_TX", "SEC AUX PCM Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOICE_STUB_DL", "VOICE_STUB Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOICE_STUB_UL", "VOICE_STUB Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("STUB_RX", "Stub Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_TX", "Stub Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_TX", "Slimbus1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_1_TX", "Stub1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_RX", "Slimbus3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_3_TX", "Slimbus3 Capture", 0, 0, 0, 0), + + /* Switch Definitions */ + SND_SOC_DAPM_SWITCH("SLIMBUS_DL_HL", SND_SOC_NOPM, 0, 0, + &fm_switch_mixer_controls), + SND_SOC_DAPM_SWITCH("PCM_RX_DL_HL", SND_SOC_NOPM, 0, 0, + &pcm_rx_switch_mixer_controls), + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("PRI_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_i2s_rx_mixer_controls, ARRAY_SIZE(pri_i2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_i2s_rx_mixer_controls, ARRAY_SIZE(sec_i2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_mixer_controls, ARRAY_SIZE(mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + auxpcm_rx_mixer_controls, ARRAY_SIZE(auxpcm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_AUX_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_auxpcm_rx_mixer_controls, ARRAY_SIZE(sec_auxpcm_rx_mixer_controls)), + /* incall */ + SND_SOC_DAPM_MIXER("Incall_Music Audio Mixer", SND_SOC_NOPM, 0, 0, + incall_music_delivery_mixer_controls, + ARRAY_SIZE(incall_music_delivery_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_mixer_controls)), + /* Voice Mixer */ + SND_SOC_DAPM_MIXER("PRI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, pri_rx_voice_mixer_controls, + ARRAY_SIZE(pri_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + sec_i2s_rx_voice_mixer_controls, + ARRAY_SIZE(sec_i2s_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIM_0_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + slimbus_rx_voice_mixer_controls, + ARRAY_SIZE(slimbus_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + bt_sco_rx_voice_mixer_controls, + ARRAY_SIZE(bt_sco_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + afe_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + aux_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(aux_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_AUX_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + sec_aux_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(sec_aux_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + hdmi_rx_voice_mixer_controls, + ARRAY_SIZE(hdmi_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + mi2s_rx_voice_mixer_controls, + ARRAY_SIZE(mi2s_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voice_mixer_controls, + ARRAY_SIZE(tx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voip_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voip_mixer_controls, + ARRAY_SIZE(tx_voip_mixer_controls)), + SND_SOC_DAPM_MIXER("VoLTE_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_volte_mixer_controls, + ARRAY_SIZE(tx_volte_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_bt_sco_rx_mixer_controls, ARRAY_SIZE(int_bt_sco_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_FM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_fm_rx_mixer_controls, ARRAY_SIZE(int_fm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + afe_pcm_rx_mixer_controls, ARRAY_SIZE(afe_pcm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice Stub Tx Mixer", SND_SOC_NOPM, 0, 0, + tx_voice_stub_mixer_controls, ARRAY_SIZE(tx_voice_stub_mixer_controls)), + SND_SOC_DAPM_MIXER("STUB_RX Mixer", SND_SOC_NOPM, 0, 0, + stub_rx_mixer_controls, ARRAY_SIZE(stub_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX_Voice Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, ARRAY_SIZE(slimbus_3_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sbus_0_rx_port_mixer_controls, + ARRAY_SIZE(sbus_0_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("AUXPCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, auxpcm_rx_port_mixer_controls, + ARRAY_SIZE(auxpcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Port Mixer", SND_SOC_NOPM, 0, 0, + sbus_1_rx_port_mixer_controls, + ARRAY_SIZE(sbus_1_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Port Mixer", SND_SOC_NOPM, 0, 0, + bt_sco_rx_port_mixer_controls, + ARRAY_SIZE(bt_sco_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, afe_pcm_rx_port_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX Port Mixer", + SND_SOC_NOPM, 0, 0, hdmi_rx_port_mixer_controls, + ARRAY_SIZE(hdmi_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_I2S_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sec_i2s_rx_port_mixer_controls, + ARRAY_SIZE(sec_i2s_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sbus_3_rx_port_mixer_controls, + ARRAY_SIZE(sbus_3_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX Port Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_port_mixer_controls, ARRAY_SIZE(mi2s_rx_port_mixer_controls)), + + /* Virtual Pins to force backends ON atm */ + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_INPUT("BE_IN"), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"PRI_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"PRI_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"PRI_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"PRI_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PRI_I2S_RX", NULL, "PRI_RX Audio Mixer"}, + + {"SEC_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SEC_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SEC_I2S_RX", NULL, "SEC_RX Audio Mixer"}, + + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI Mixer", "MultiMedia3", "MM_DL3"}, + {"HDMI Mixer", "MultiMedia4", "MM_DL4"}, + {"HDMI", NULL, "HDMI Mixer"}, + + /* incall */ + {"Incall_Music Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"Incall_Music Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"VOICE_PLAYBACK_TX", NULL, "Incall_Music Audio Mixer"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_4_RX", NULL, "SLIMBUS_4_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"}, + {"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"}, + {"MultiMedia1 Mixer", "SLIM_4_TX", "SLIMBUS_4_TX"}, + {"MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"MI2S_RX", NULL, "MI2S_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"}, + {"MultiMedia1 Mixer", "MI2S_TX", "MI2S_TX"}, + {"MultiMedia2 Mixer", "MI2S_TX", "MI2S_TX"}, + {"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"MultiMedia1 Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"MultiMedia1 Mixer", "SEC_AUX_PCM_UL_TX", "SEC_AUX_PCM_TX"}, + + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Audio Mixer"}, + + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_FM_RX", NULL, "INTERNAL_FM_RX Audio Mixer"}, + + {"AFE_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PCM_RX", NULL, "AFE_PCM_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"MultiMedia1 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + + {"MultiMedia1 Mixer", "AFE_PCM_TX", "PCM_TX"}, + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MultiMedia2 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + + {"AUX_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX Audio Mixer"}, + + {"SEC_AUX_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_AUX_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_AUX_PCM_RX", NULL, "SEC_AUX_PCM_RX Audio Mixer"}, + + {"PRI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"PRI_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"PRI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PRI_I2S_RX", NULL, "PRI_RX_Voice Mixer"}, + + {"SEC_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SEC_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"SEC_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SEC_I2S_RX", NULL, "SEC_RX_Voice Mixer"}, + + {"SLIM_0_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SLIM_0_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"SLIM_0_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SLIMBUS_0_RX", NULL, "SLIM_0_RX_Voice Mixer"}, + + {"INTERNAL_BT_SCO_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX_Voice Mixer"}, + + {"AFE_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AFE_PCM_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"AFE_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PCM_RX", NULL, "AFE_PCM_RX_Voice Mixer"}, + + {"AUX_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AUX_PCM_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"AUX_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX_Voice Mixer"}, + + {"SEC_AUX_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SEC_AUX_PCM_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"SEC_AUX_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SEC_AUX_PCM_RX", NULL, "SEC_AUX_PCM_RX_Voice Mixer"}, + + {"HDMI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"HDMI_RX_Voice Mixer", "VoLTE", "VoLTE_DL"}, + {"HDMI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"HDMI", NULL, "HDMI_RX_Voice Mixer"}, + {"HDMI", NULL, "HDMI_DL_HL"}, + + {"Voice_Tx Mixer", "PRI_TX_Voice", "PRI_I2S_TX"}, + {"Voice_Tx Mixer", "MI2S_TX_Voice", "MI2S_TX"}, + {"Voice_Tx Mixer", "SLIM_0_TX_Voice", "SLIMBUS_0_TX"}, + {"Voice_Tx Mixer", "INTERNAL_BT_SCO_TX_Voice", "INT_BT_SCO_TX"}, + {"Voice_Tx Mixer", "AFE_PCM_TX_Voice", "PCM_TX"}, + {"Voice_Tx Mixer", "AUX_PCM_TX_Voice", "AUX_PCM_TX"}, + {"Voice_Tx Mixer", "SEC_AUX_PCM_TX_Voice", "SEC_AUX_PCM_TX"}, + {"CS-VOICE_UL1", NULL, "Voice_Tx Mixer"}, + {"VoLTE_Tx Mixer", "PRI_TX_VoLTE", "PRI_I2S_TX"}, + {"VoLTE_Tx Mixer", "SLIM_0_TX_VoLTE", "SLIMBUS_0_TX"}, + {"VoLTE_Tx Mixer", "INTERNAL_BT_SCO_TX_VoLTE", "INT_BT_SCO_TX"}, + {"VoLTE_Tx Mixer", "AFE_PCM_TX_VoLTE", "PCM_TX"}, + {"VoLTE_Tx Mixer", "AUX_PCM_TX_VoLTE", "AUX_PCM_TX"}, + {"VoLTE_Tx Mixer", "SEC_AUX_PCM_TX_VoLTE", "SEC_AUX_PCM_TX"}, + {"VoLTE_UL", NULL, "VoLTE_Tx Mixer"}, + {"Voip_Tx Mixer", "PRI_TX_Voip", "PRI_I2S_TX"}, + {"Voip_Tx Mixer", "MI2S_TX_Voip", "MI2S_TX"}, + {"Voip_Tx Mixer", "SLIM_0_TX_Voip", "SLIMBUS_0_TX"}, + {"Voip_Tx Mixer", "INTERNAL_BT_SCO_TX_Voip", "INT_BT_SCO_TX"}, + {"Voip_Tx Mixer", "AFE_PCM_TX_Voip", "PCM_TX"}, + {"Voip_Tx Mixer", "AUX_PCM_TX_Voip", "AUX_PCM_TX"}, + {"Voip_Tx Mixer", "SEC_AUX_PCM_TX_Voip", "SEC_AUX_PCM_TX"}, + + {"VOIP_UL", NULL, "Voip_Tx Mixer"}, + {"SLIMBUS_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_DL_HL"}, + {"SLIM0_UL_HL", NULL, "SLIMBUS_0_TX"}, + {"INT_FM_RX", NULL, "INTFM_DL_HL"}, + {"INTFM_UL_HL", NULL, "INT_FM_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_DL_HL"}, + {"AUXPCM_UL_HL", NULL, "AUX_PCM_TX"}, + {"PCM_RX_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"PCM_RX", NULL, "PCM_RX_DL_HL"}, + {"MI2S_UL_HL", NULL, "MI2S_TX"}, + {"SEC_I2S_RX", NULL, "SEC_I2S_DL_HL"}, + {"SLIMBUS_0_RX Port Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"SLIMBUS_0_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Port Mixer"}, + {"AFE_PCM_RX Port Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"PCM_RX", NULL, "AFE_PCM_RX Port Mixer"}, + + {"AUXPCM_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"AUXPCM_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_RX Port Mixer"}, + + {"Voice Stub Tx Mixer", "STUB_TX_HL", "STUB_TX"}, + {"Voice Stub Tx Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"Voice Stub Tx Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"Voice Stub Tx Mixer", "STUB_1_TX_HL", "STUB_1_TX"}, + {"Voice Stub Tx Mixer", "MI2S_TX", "MI2S_TX"}, + {"Voice Stub Tx Mixer", "SLIM_3_TX", "SLIMBUS_3_TX"}, + {"VOICE_STUB_UL", NULL, "Voice Stub Tx Mixer"}, + + {"STUB_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"STUB_RX", NULL, "STUB_RX Mixer"}, + {"SLIMBUS_1_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Mixer"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"MI2S_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"MI2S_RX", NULL, "MI2S_RX_Voice Mixer"}, + {"HDMI_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + + {"SLIMBUS_3_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX_Voice Mixer"}, + + {"SLIMBUS_1_RX Port Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Port Mixer"}, + {"INTERNAL_BT_SCO_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Port Mixer"}, + {"SLIMBUS_3_RX Port Mixer", "INTERNAL_BT_SCO_RX", "INT_BT_SCO_RX"}, + {"SLIMBUS_3_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX Port Mixer"}, + + + {"HDMI_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"HDMI_RX Port Mixer", "SLIM_3_TX", "SLIMBUS_3_TX"}, + {"HDMI", NULL, "HDMI_RX Port Mixer"}, + + {"SEC_I2S_RX Port Mixer", "MI2S_TX", "MI2S_TX"}, + {"SEC_I2S_RX", NULL, "SEC_I2S_RX Port Mixer"}, + + {"MI2S_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"MI2S_RX", NULL, "MI2S_RX Port Mixer"}, + /* Backend Enablement */ + + {"BE_OUT", NULL, "PRI_I2S_RX"}, + {"BE_OUT", NULL, "SEC_I2S_RX"}, + {"BE_OUT", NULL, "SLIMBUS_0_RX"}, + {"BE_OUT", NULL, "HDMI"}, + {"BE_OUT", NULL, "MI2S_RX"}, + {"PRI_I2S_TX", NULL, "BE_IN"}, + {"MI2S_TX", NULL, "BE_IN"}, + {"SLIMBUS_0_TX", NULL, "BE_IN" }, + {"BE_OUT", NULL, "INT_BT_SCO_RX"}, + {"INT_BT_SCO_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "INT_FM_RX"}, + {"INT_FM_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "PCM_RX"}, + {"PCM_TX", NULL, "BE_IN"}, + {"BE_OUT", NULL, "SLIMBUS_3_RX"}, +}; + +static int msm_pcm_routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + mutex_lock(&routing_lock); + msm_bedais[be_id].sample_rate = params_rate(params); + msm_bedais[be_id].channel = params_channels(params); + mutex_unlock(&routing_lock); + return 0; +} + +static int msm_pcm_routing_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, session_type; + struct msm_pcm_routing_bdai_data *bedai; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + session_type = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 0 : 1); + + mutex_lock(&routing_lock); + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + adm_close(bedai->port_id); + srs_port_id = -1; + } + } + + bedai->active = 0; + bedai->sample_rate = 0; + bedai->channel = 0; + mutex_unlock(&routing_lock); + + return 0; +} + +static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, path_type, session_type; + struct msm_pcm_routing_bdai_data *bedai; + u32 channels; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path_type = ADM_PATH_PLAYBACK; + session_type = SESSION_TYPE_RX; + } else { + path_type = ADM_PATH_LIVE_REC; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + if (bedai->active == 1) + goto done; /* Ignore prepare if back-end already active */ + + /* AFE port is not active at this point. However, still + * go ahead setting active flag under the notion that + * QDSP6 is able to handle ADM starting before AFE port + * is started. + */ + bedai->active = 1; + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + + channels = bedai->channel; + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + && (channels > 2)) + adm_multi_ch_copp_open(bedai->port_id, + path_type, + bedai->sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(bedai->port_id, + path_type, + bedai->sample_rate, + channels, + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(i, + fe_dai_map[i][session_type], path_type); + srs_port_id = bedai->port_id; + srs_send_params(srs_port_id, 1, 0); + } + } + +done: + mutex_unlock(&routing_lock); + + return 0; +} + +static struct snd_pcm_ops msm_routing_pcm_ops = { + .hw_params = msm_pcm_routing_hw_params, + .close = msm_pcm_routing_close, + .prepare = msm_pcm_routing_prepare, +}; + +static unsigned int msm_routing_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + dev_dbg(platform->dev, "reg %x\n", reg); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val) +{ + dev_dbg(platform->dev, "reg %x val %x\n", reg, val); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_probe(struct snd_soc_platform *platform) +{ + snd_soc_dapm_new_controls(&platform->dapm, msm_qdsp6_widgets, + ARRAY_SIZE(msm_qdsp6_widgets)); + snd_soc_dapm_add_routes(&platform->dapm, intercon, + ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&platform->dapm); + + snd_soc_add_platform_controls(platform, + int_fm_vol_mixer_controls, + ARRAY_SIZE(int_fm_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_vol_mixer_controls, + ARRAY_SIZE(lpa_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_enable_mixer_controls, + ARRAY_SIZE(eq_enable_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_band_mixer_controls, + ARRAY_SIZE(eq_band_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_coeff_mixer_controls, + ARRAY_SIZE(eq_coeff_mixer_controls)); + + snd_soc_add_platform_controls(platform, + multimedia2_vol_mixer_controls, + ARRAY_SIZE(multimedia2_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + compressed_vol_mixer_controls, + ARRAY_SIZE(compressed_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls, + ARRAY_SIZE(lpa_SRS_trumedia_controls)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls_HDMI, + ARRAY_SIZE(lpa_SRS_trumedia_controls_HDMI)); + + snd_soc_add_platform_controls(platform, + lpa_SRS_trumedia_controls_I2S, + ARRAY_SIZE(lpa_SRS_trumedia_controls_I2S)); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_routing_platform = { + .ops = &msm_routing_pcm_ops, + .probe = msm_routing_probe, + .read = msm_routing_read, + .write = msm_routing_write, +}; + +static int msm_routing_pcm_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_routing_platform); +} + +static int msm_routing_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_routing_pcm_driver = { + .driver = { + .name = "msm-pcm-routing", + .owner = THIS_MODULE, + }, + .probe = msm_routing_pcm_probe, + .remove = msm_routing_pcm_remove, +}; + +int msm_routing_check_backend_enabled(int fedai_id) +{ + int i; + if (fedai_id >= MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return 0; + } + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if (test_bit(fedai_id, &msm_bedais[i].fe_sessions)) + return msm_bedais[i].active; + } + return 0; +} + +static int __init msm_soc_routing_platform_init(void) +{ + mutex_init(&routing_lock); + return platform_driver_register(&msm_routing_pcm_driver); +} +module_init(msm_soc_routing_platform_init); + +static void __exit msm_soc_routing_platform_exit(void) +{ + platform_driver_unregister(&msm_routing_pcm_driver); +} +module_exit(msm_soc_routing_platform_exit); + +MODULE_DESCRIPTION("MSM routing platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-routing.h b/sound/soc/msm/msm-pcm-routing.h new file mode 100644 index 000000000000..20c06b381bff --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_ROUTING_H +#define _MSM_PCM_ROUTING_H +#include + +#define LPASS_BE_PRI_I2S_RX "PRIMARY_I2S_RX" +#define LPASS_BE_PRI_I2S_TX "PRIMARY_I2S_TX" +#define LPASS_BE_SLIMBUS_0_RX "SLIMBUS_0_RX" +#define LPASS_BE_SLIMBUS_0_TX "SLIMBUS_0_TX" +#define LPASS_BE_HDMI "HDMI" +#define LPASS_BE_INT_BT_SCO_RX "INT_BT_SCO_RX" +#define LPASS_BE_INT_BT_SCO_TX "INT_BT_SCO_TX" +#define LPASS_BE_INT_FM_RX "INT_FM_RX" +#define LPASS_BE_INT_FM_TX "INT_FM_TX" +#define LPASS_BE_AFE_PCM_RX "RT_PROXY_DAI_001_RX" +#define LPASS_BE_AFE_PCM_TX "RT_PROXY_DAI_002_TX" +#define LPASS_BE_AUXPCM_RX "AUX_PCM_RX" +#define LPASS_BE_AUXPCM_TX "AUX_PCM_TX" +#define LPASS_BE_SEC_AUXPCM_RX "SEC_AUX_PCM_RX" +#define LPASS_BE_SEC_AUXPCM_TX "SEC_AUX_PCM_TX" +#define LPASS_BE_VOICE_PLAYBACK_TX "VOICE_PLAYBACK_TX" +#define LPASS_BE_INCALL_RECORD_RX "INCALL_RECORD_TX" +#define LPASS_BE_INCALL_RECORD_TX "INCALL_RECORD_RX" +#define LPASS_BE_SEC_I2S_RX "SECONDARY_I2S_RX" + +#define LPASS_BE_MI2S_RX "MI2S_RX" +#define LPASS_BE_MI2S_TX "MI2S_TX" +#define LPASS_BE_STUB_RX "STUB_RX" +#define LPASS_BE_STUB_TX "STUB_TX" +#define LPASS_BE_SLIMBUS_1_RX "SLIMBUS_1_RX" +#define LPASS_BE_SLIMBUS_1_TX "SLIMBUS_1_TX" +#define LPASS_BE_STUB_1_TX "STUB_1_TX" +#define LPASS_BE_SLIMBUS_3_RX "SLIMBUS_3_RX" +#define LPASS_BE_SLIMBUS_3_TX "SLIMBUS_3_TX" +#define LPASS_BE_SLIMBUS_4_RX "SLIMBUS_4_RX" +#define LPASS_BE_SLIMBUS_4_TX "SLIMBUS_4_TX" + +/* For multimedia front-ends, asm session is allocated dynamically. + * Hence, asm session/multimedia front-end mapping has to be maintained. + * Due to this reason, additional multimedia front-end must be placed before + * non-multimedia front-ends. + */ + +enum { + MSM_FRONTEND_DAI_MULTIMEDIA1 = 0, + MSM_FRONTEND_DAI_MULTIMEDIA2, + MSM_FRONTEND_DAI_MULTIMEDIA3, + MSM_FRONTEND_DAI_MULTIMEDIA4, + MSM_FRONTEND_DAI_CS_VOICE, + MSM_FRONTEND_DAI_VOIP, + MSM_FRONTEND_DAI_AFE_RX, + MSM_FRONTEND_DAI_AFE_TX, + MSM_FRONTEND_DAI_VOICE_STUB, + MSM_FRONTEND_DAI_VOLTE, + MSM_FRONTEND_DAI_MAX, +}; + +#define MSM_FRONTEND_DAI_MM_SIZE (MSM_FRONTEND_DAI_MULTIMEDIA4 + 1) +#define MSM_FRONTEND_DAI_MM_MAX_ID MSM_FRONTEND_DAI_MULTIMEDIA4 + +enum { + MSM_BACKEND_DAI_PRI_I2S_RX = 0, + MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_BACKEND_DAI_INT_FM_RX, + MSM_BACKEND_DAI_INT_FM_TX, + MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, + MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_MI2S_TX, + MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_BACKEND_DAI_SLIMBUS_4_RX, + MSM_BACKEND_DAI_SLIMBUS_4_TX, + MSM_BACKEND_DAI_SLIMBUS_3_RX, + MSM_BACKEND_DAI_SLIMBUS_3_TX, + MSM_BACKEND_DAI_EXTPROC_RX, + MSM_BACKEND_DAI_EXTPROC_TX, + MSM_BACKEND_DAI_EXTPROC_EC_TX, + MSM_BACKEND_DAI_SEC_AUXPCM_RX, + MSM_BACKEND_DAI_SEC_AUXPCM_TX, + MSM_BACKEND_DAI_MAX, +}; + +/* dai_id: front-end ID, + * dspst_id: DSP audio stream ID + * stream_type: playback or capture + */ +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, + int stream_type); +void msm_pcm_routing_reg_psthr_stream(int fedai_id, int dspst_id, + int stream_type); + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type); + +int lpa_set_volume(unsigned volume); + +int msm_routing_check_backend_enabled(int fedai_id); + +int multi_ch_pcm_set_volume(unsigned volume); + +int compressed_set_volume(unsigned volume); + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-pcm-voice.c b/sound/soc/msm/msm-pcm-voice.c new file mode 100644 index 000000000000..deb9bf6d89da --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.c @@ -0,0 +1,558 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-voice.h" +#include "qdsp6/q6voice.h" + +static struct msm_voice voice_info[VOICE_SESSION_INDEX_MAX]; + +static struct snd_pcm_hardware msm_pcm_hardware = { + + .info = (SNDRV_PCM_INFO_INTERLEAVED| + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + + .buffer_bytes_max = 4096 * 2, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 2, + .periods_max = 2, + + .fifo_size = 0, +}; +static int is_volte(struct msm_voice *pvolte) +{ + if (pvolte == &voice_info[VOLTE_SESSION_INDEX]) + return true; + else + return false; +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (!prtd->playback_start) + prtd->playback_start = 1; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (!prtd->capture_start) + prtd->capture_start = 1; + + return 0; +} +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *voice; + + if (!strncmp("VoLTE", substream->pcm->id, 5)) { + voice = &voice_info[VOLTE_SESSION_INDEX]; + pr_debug("%s: Open VoLTE Substream Id=%s\n", + __func__, substream->pcm->id); + } else { + voice = &voice_info[VOICE_SESSION_INDEX]; + pr_debug("%s: Open VOICE Substream Id=%s\n", + __func__, substream->pcm->id); + } + mutex_lock(&voice->lock); + + runtime->hw = msm_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + voice->playback_substream = substream; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + voice->capture_substream = substream; + + voice->instance++; + pr_debug("%s: Instance = %d, Stream ID = %s\n", + __func__ , voice->instance, substream->pcm->id); + runtime->private_data = voice; + + mutex_unlock(&voice->lock); + + return 0; +} +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (prtd->playback_start) + prtd->playback_start = 0; + + prtd->playback_substream = NULL; + + return 0; +} +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (prtd->capture_start) + prtd->capture_start = 0; + prtd->capture_substream = NULL; + + return 0; +} +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + uint16_t session_id = 0; + int ret = 0; + + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + + prtd->instance--; + if (!prtd->playback_start && !prtd->capture_start) { + pr_debug("end voice call\n"); + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + voc_end_voice_call(session_id); + } + mutex_unlock(&prtd->lock); + + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + uint16_t session_id = 0; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + + if (prtd->playback_start && prtd->capture_start) { + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + voc_start_voice_call(session_id); + } + mutex_unlock(&prtd->lock); + + return ret; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + pr_debug("%s: Voice\n", __func__); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + uint16_t session_id = 0; + + pr_debug("%s: cmd = %d\n", __func__, cmd); + if (is_volte(prtd)) + session_id = voc_get_session_id(VOLTE_SESSION_NAME); + else + session_id = voc_get_session_id(VOICE_SESSION_NAME); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("Start & Stop Voice call not handled in Trigger.\n"); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: resume call session_id = %d\n", __func__, + session_id); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + if (prtd->playback_start && prtd->capture_start) + voc_resume_voice_call(session_id); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: pause call session_id=%d\n", + __func__, session_id); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->playback_start) + prtd->playback_start = 0; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (prtd->capture_start) + prtd->capture_start = 0; + } + voc_standby_voice_call(session_id); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int msm_voice_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voice_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int volume = ucontrol->value.integer.value[0]; + pr_debug("%s: volume: %d\n", __func__, volume); + voc_set_rx_vol_index(voc_get_session_id(VOICE_SESSION_NAME), + RX_PATH, volume); + return 0; +} + +static int msm_volte_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volte_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int volume = ucontrol->value.integer.value[0]; + pr_debug("%s: volume: %d\n", __func__, volume); + voc_set_rx_vol_index(voc_get_session_id(VOLTE_SESSION_NAME), + RX_PATH, volume); + return 0; +} + +static int msm_voice_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voice_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOICE_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_volte_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volte_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOLTE_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_voice_rx_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_rx_device_mute(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + +static int msm_voice_rx_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_rx_device_mute(voc_get_session_id(VOICE_SESSION_NAME), mute); + + return 0; +} + +static int msm_volte_rx_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_rx_device_mute(voc_get_session_id(VOLTE_SESSION_NAME)); + return 0; +} + +static int msm_volte_rx_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_rx_device_mute(voc_get_session_id(VOLTE_SESSION_NAME), mute); + + return 0; +} + +static const char const *tty_mode[] = {"OFF", "HCO", "VCO", "FULL"}; +static const struct soc_enum msm_tty_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(4, tty_mode), +}; + +static int msm_voice_tty_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_tty_mode(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + +static int msm_voice_tty_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int tty_mode = ucontrol->value.integer.value[0]; + + pr_debug("%s: tty_mode=%d\n", __func__, tty_mode); + + voc_set_tty_mode(voc_get_session_id(VOICE_SESSION_NAME), tty_mode); + + return 0; +} +static int msm_voice_widevoice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int wv_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: wv enable=%d\n", __func__, wv_enable); + + voc_set_widevoice_enable(voc_get_session_id(VOICE_SESSION_NAME), + wv_enable); + + return 0; +} + +static int msm_voice_widevoice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_widevoice_enable(voc_get_session_id(VOICE_SESSION_NAME)); + return 0; +} + + +static int msm_voice_slowtalk_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int st_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: st enable=%d\n", __func__, st_enable); + + voc_set_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_ST, st_enable); + + return 0; +} + +static int msm_voice_slowtalk_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_ST); + return 0; +} + +static int msm_voice_fens_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int fens_enable = ucontrol->value.integer.value[0]; + + pr_debug("%s: fens enable=%d\n", __func__, fens_enable); + + voc_set_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_FENS, fens_enable); + + return 0; +} + +static int msm_voice_fens_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voc_get_pp_enable(voc_get_session_id(VOICE_SESSION_NAME), + MODULE_ID_VOICE_MODULE_FENS); + return 0; +} + +static struct snd_kcontrol_new msm_voice_controls[] = { + SOC_SINGLE_EXT("Voice Rx Device Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voice_rx_device_mute_get, + msm_voice_rx_device_mute_put), + SOC_SINGLE_EXT("Voice Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voice_mute_get, msm_voice_mute_put), + SOC_SINGLE_EXT("Voice Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_voice_volume_get, msm_voice_volume_put), + SOC_ENUM_EXT("TTY Mode", msm_tty_mode_enum[0], msm_voice_tty_mode_get, + msm_voice_tty_mode_put), + SOC_SINGLE_EXT("Widevoice Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_widevoice_get, msm_voice_widevoice_put), + SOC_SINGLE_EXT("Slowtalk Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_slowtalk_get, msm_voice_slowtalk_put), + SOC_SINGLE_EXT("FENS Enable", SND_SOC_NOPM, 0, 1, 0, + msm_voice_fens_get, msm_voice_fens_put), + SOC_SINGLE_EXT("VoLTE Rx Device Mute", SND_SOC_NOPM, 0, 1, 0, + msm_volte_rx_device_mute_get, + msm_volte_rx_device_mute_put), + SOC_SINGLE_EXT("VoLTE Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_volte_mute_get, msm_volte_mute_put), + SOC_SINGLE_EXT("VoLTE Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_volte_volume_get, msm_volte_volume_put), +}; + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, +}; + + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_pcm_voice_probe(struct snd_soc_platform *platform) +{ + snd_soc_add_platform_controls(platform, msm_voice_controls, + ARRAY_SIZE(msm_voice_controls)); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_pcm_voice_probe, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-voice", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + memset(&voice_info, 0, sizeof(voice_info)); + mutex_init(&voice_info[VOICE_SESSION_INDEX].lock); + mutex_init(&voice_info[VOLTE_SESSION_INDEX].lock); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Voice PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-voice.h b/sound/soc/msm/msm-pcm-voice.h new file mode 100644 index 000000000000..f6388d7920ad --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_VOICE_H +#define _MSM_PCM_VOICE_H +#include + +enum { + VOICE_SESSION_INDEX, + VOLTE_SESSION_INDEX, + VOICE_SESSION_INDEX_MAX, +}; + +struct msm_voice { + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + int instance; + + struct mutex lock; + + uint32_t samp_rate; + uint32_t channel_mode; + + int playback_start; + int capture_start; +}; + +#endif /*_MSM_PCM_VOICE_H*/ diff --git a/sound/soc/msm/msm-pcm-voip.c b/sound/soc/msm/msm-pcm-voip.c new file mode 100644 index 000000000000..57edece67c95 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voip.c @@ -0,0 +1,1169 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" +#include "qdsp6/q6voice.h" + +#define VOIP_MAX_Q_LEN 10 +#define VOIP_MAX_VOC_PKT_SIZE 640 +#define VOIP_MIN_VOC_PKT_SIZE 320 + +/* Length of the DSP frame info header added to the voc packet. */ +#define DSP_FRAME_HDR_LEN 1 + +#define MODE_IS127 0x2 +#define MODE_4GV_NB 0x3 +#define MODE_4GV_WB 0x4 +#define MODE_AMR 0x5 +#define MODE_AMR_WB 0xD +#define MODE_PCM 0xC + +enum format { + FORMAT_S16_LE = 2, + FORMAT_SPECIAL = 31, +}; + + +enum amr_rate_type { + AMR_RATE_4750, /* AMR 4.75 kbps */ + AMR_RATE_5150, /* AMR 5.15 kbps */ + AMR_RATE_5900, /* AMR 5.90 kbps */ + AMR_RATE_6700, /* AMR 6.70 kbps */ + AMR_RATE_7400, /* AMR 7.40 kbps */ + AMR_RATE_7950, /* AMR 7.95 kbps */ + AMR_RATE_10200, /* AMR 10.20 kbps */ + AMR_RATE_12200, /* AMR 12.20 kbps */ + AMR_RATE_6600, /* AMR-WB 6.60 kbps */ + AMR_RATE_8850, /* AMR-WB 8.85 kbps */ + AMR_RATE_12650, /* AMR-WB 12.65 kbps */ + AMR_RATE_14250, /* AMR-WB 14.25 kbps */ + AMR_RATE_15850, /* AMR-WB 15.85 kbps */ + AMR_RATE_18250, /* AMR-WB 18.25 kbps */ + AMR_RATE_19850, /* AMR-WB 19.85 kbps */ + AMR_RATE_23050, /* AMR-WB 23.05 kbps */ + AMR_RATE_23850, /* AMR-WB 23.85 kbps */ + AMR_RATE_UNDEF +}; + +enum voip_state { + VOIP_STOPPED, + VOIP_STARTED, +}; + +struct voip_frame { + union { + uint32_t frame_type; + uint32_t packet_rate; + } header; + uint32_t len; + uint8_t voc_pkt[VOIP_MAX_VOC_PKT_SIZE]; +}; + +struct voip_buf_node { + struct list_head list; + struct voip_frame frame; +}; + +struct voip_drv_info { + enum voip_state state; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct list_head in_queue; + struct list_head free_in_queue; + + struct list_head out_queue; + struct list_head free_out_queue; + + wait_queue_head_t out_wait; + wait_queue_head_t in_wait; + + struct mutex lock; + struct mutex in_lock; + struct mutex out_lock; + + spinlock_t dsp_lock; + + uint32_t mode; + uint32_t rate_type; + uint32_t rate; + uint32_t dtx_mode; + + uint8_t capture_start; + uint8_t playback_start; + + uint8_t playback_instance; + uint8_t capture_instance; + + unsigned int play_samp_rate; + unsigned int cap_samp_rate; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ +}; + +static int voip_get_media_type(uint32_t mode, + unsigned int samp_rate); +static int voip_get_rate_type(uint32_t mode, + uint32_t rate, + uint32_t *rate_type); +static int msm_voip_mode_rate_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int msm_voip_mode_rate_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static struct voip_drv_info voip_info; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED), + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_SPECIAL, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = sizeof(struct voip_buf_node) * VOIP_MAX_Q_LEN, + .period_bytes_min = VOIP_MIN_VOC_PKT_SIZE, + .period_bytes_max = VOIP_MAX_VOC_PKT_SIZE, + .periods_min = VOIP_MAX_Q_LEN, + .periods_max = VOIP_MAX_Q_LEN, + .fifo_size = 0, +}; + + +static int msm_voip_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mute = ucontrol->value.integer.value[0]; + + pr_debug("%s: mute=%d\n", __func__, mute); + + voc_set_tx_mute(voc_get_session_id(VOIP_SESSION_NAME), TX_PATH, mute); + + return 0; +} + +static int msm_voip_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voip_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int volume = ucontrol->value.integer.value[0]; + + pr_debug("%s: volume: %d\n", __func__, volume); + + voc_set_rx_vol_index(voc_get_session_id(VOIP_SESSION_NAME), + RX_PATH, + volume); + return 0; +} +static int msm_voip_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voip_dtx_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + voip_info.dtx_mode = ucontrol->value.integer.value[0]; + + pr_debug("%s: dtx: %d\n", __func__, voip_info.dtx_mode); + + mutex_unlock(&voip_info.lock); + + return 0; +} +static int msm_voip_dtx_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + ucontrol->value.integer.value[0] = voip_info.dtx_mode; + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static struct snd_kcontrol_new msm_voip_controls[] = { + SOC_SINGLE_EXT("Voip Tx Mute", SND_SOC_NOPM, 0, 1, 0, + msm_voip_mute_get, msm_voip_mute_put), + SOC_SINGLE_EXT("Voip Rx Volume", SND_SOC_NOPM, 0, 5, 0, + msm_voip_volume_get, msm_voip_volume_put), + SOC_SINGLE_MULTI_EXT("Voip Mode Rate Config", SND_SOC_NOPM, 0, 23850, + 0, 2, msm_voip_mode_rate_config_get, + msm_voip_mode_rate_config_put), + SOC_SINGLE_EXT("Voip Dtx Mode", SND_SOC_NOPM, 0, 1, 0, + msm_voip_dtx_mode_get, msm_voip_dtx_mode_put), +}; + +static int msm_pcm_voip_probe(struct snd_soc_platform *platform) +{ + snd_soc_add_platform_controls(platform, msm_voip_controls, + ARRAY_SIZE(msm_voip_controls)); + + return 0; +} + +/* sample rate supported */ +static unsigned int supported_sample_rates[] = {8000, 16000}; + +/* capture path */ +static void voip_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data) +{ + struct voip_buf_node *buf_node = NULL; + struct voip_drv_info *prtd = private_data; + unsigned long dsp_flags; + + if (prtd->capture_substream == NULL) + return; + + /* Copy up-link packet into out_queue. */ + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + + /* discarding UL packets till start is received */ + if (!list_empty(&prtd->free_out_queue) && prtd->capture_start) { + buf_node = list_first_entry(&prtd->free_out_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + switch (prtd->mode) { + case MODE_AMR_WB: + case MODE_AMR: { + /* Remove the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + buf_node->frame.header.frame_type = + ((*voc_pkt) & 0xF0) >> 4; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + list_add_tail(&buf_node->list, &prtd->out_queue); + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + /* Remove the DSP frame info header. + * Header format: + * Bits 0-3: frame rate + */ + buf_node->frame.header.packet_rate = (*voc_pkt) & 0x0F; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, &prtd->out_queue); + break; + } + default: { + buf_node->frame.len = pkt_len; + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + list_add_tail(&buf_node->list, &prtd->out_queue); + } + } + pr_debug("ul_pkt: pkt_len =%d, frame.len=%d\n", pkt_len, + buf_node->frame.len); + prtd->pcm_capture_irq_pos += prtd->pcm_capture_count; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + snd_pcm_period_elapsed(prtd->capture_substream); + } else { + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + pr_err("UL data dropped\n"); + } + + wake_up(&prtd->out_wait); +} + +/* playback path */ +static void voip_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data) +{ + struct voip_buf_node *buf_node = NULL; + struct voip_drv_info *prtd = private_data; + unsigned long dsp_flags; + + + if (prtd->playback_substream == NULL) + return; + + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + + if (!list_empty(&prtd->in_queue) && prtd->playback_start) { + buf_node = list_first_entry(&prtd->in_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + switch (prtd->mode) { + case MODE_AMR: + case MODE_AMR_WB: { + /* Add the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + *voc_pkt = ((buf_node->frame.header.frame_type & + 0x0F) << 4) | (prtd->rate_type & 0x0F); + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + list_add_tail(&buf_node->list, &prtd->free_in_queue); + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + /* Add the DSP frame info header. Header format: + * Bits 0-3 : Frame rate + */ + *voc_pkt = buf_node->frame.header.packet_rate & 0x0F; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &prtd->free_in_queue); + break; + } + default: { + *pkt_len = buf_node->frame.len; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &prtd->free_in_queue); + } + } + pr_debug("dl_pkt: pkt_len=%d, frame_len=%d\n", *pkt_len, + buf_node->frame.len); + prtd->pcm_playback_irq_pos += prtd->pcm_count; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + snd_pcm_period_elapsed(prtd->playback_substream); + } else { + *pkt_len = 0; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + pr_err("DL data not available\n"); + } + wake_up(&prtd->in_wait); +} + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + prtd->play_samp_rate = runtime->rate; + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + int ret = 0; + + prtd->cap_samp_rate = runtime->rate; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + pr_debug("%s: Trigger start\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->capture_start = 1; + else + prtd->playback_start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->playback_start = 0; + else + prtd->capture_start = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = &voip_info; + int ret = 0; + + pr_debug("%s, VoIP\n", __func__); + mutex_lock(&prtd->lock); + + runtime->hw = msm_pcm_hardware; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + goto err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->playback_substream = substream; + prtd->playback_instance++; + } else { + prtd->capture_substream = substream; + prtd->capture_instance++; + } + runtime->private_data = prtd; +err: + mutex_unlock(&prtd->lock); + + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + struct voip_buf_node *buf_node = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + int count = frames_to_bytes(runtime, frames); + pr_debug("%s: count = %d, frames=%d\n", __func__, count, (int)frames); + + ret = wait_event_interruptible_timeout(prtd->in_wait, + (!list_empty(&prtd->free_in_queue) || + prtd->state == VOIP_STOPPED), + 1 * HZ); + if (ret > 0) { + mutex_lock(&prtd->in_lock); + if (count <= VOIP_MAX_VOC_PKT_SIZE) { + buf_node = + list_first_entry(&prtd->free_in_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + if (prtd->mode == MODE_PCM) { + ret = copy_from_user(&buf_node->frame.voc_pkt, + buf, count); + buf_node->frame.len = count; + } else + ret = copy_from_user(&buf_node->frame, + buf, count); + list_add_tail(&buf_node->list, &prtd->in_queue); + } else { + pr_err("%s: Write cnt %d is > VOIP_MAX_VOC_PKT_SIZE\n", + __func__, count); + ret = -ENOMEM; + } + + mutex_unlock(&prtd->in_lock); + } else if (ret == 0) { + pr_err("%s: No free DL buffs\n", __func__); + ret = -ETIMEDOUT; + } else { + pr_err("%s: playback copy was interrupted\n", __func__); + } + + return ret; +} +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int count = 0; + struct voip_buf_node *buf_node = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + count = frames_to_bytes(runtime, frames); + + pr_debug("%s: count = %d\n", __func__, count); + + ret = wait_event_interruptible_timeout(prtd->out_wait, + (!list_empty(&prtd->out_queue) || + prtd->state == VOIP_STOPPED), + 1 * HZ); + + if (ret > 0) { + mutex_lock(&prtd->out_lock); + + if (count <= VOIP_MAX_VOC_PKT_SIZE) { + buf_node = list_first_entry(&prtd->out_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + if (prtd->mode == MODE_PCM) + ret = copy_to_user(buf, + &buf_node->frame.voc_pkt, + count); + else + ret = copy_to_user(buf, + &buf_node->frame, + count); + if (ret) { + pr_err("%s: Copy to user retuned %d\n", + __func__, ret); + ret = -EFAULT; + } + list_add_tail(&buf_node->list, + &prtd->free_out_queue); + } else { + pr_err("%s: Read count %d > VOIP_MAX_VOC_PKT_SIZE\n", + __func__, count); + ret = -ENOMEM; + } + + mutex_unlock(&prtd->out_lock); + + } else if (ret == 0) { + pr_err("%s: No UL data available\n", __func__); + ret = -ETIMEDOUT; + } else { + pr_err("%s: Read was interrupted\n", __func__); + ret = -ERESTARTSYS; + } + return ret; +} +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct list_head *ptr = NULL; + struct list_head *next = NULL; + struct voip_buf_node *buf_node = NULL; + struct snd_dma_buffer *p_dma_buf, *c_dma_buf; + struct snd_pcm_substream *p_substream, *c_substream; + struct snd_pcm_runtime *runtime; + struct voip_drv_info *prtd; + + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + runtime = substream->runtime; + prtd = runtime->private_data; + + wake_up(&prtd->out_wait); + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->playback_instance--; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->capture_instance--; + + if (!prtd->playback_instance && !prtd->capture_instance) { + if (prtd->state == VOIP_STARTED) { + prtd->state = VOIP_STOPPED; + voc_end_voice_call( + voc_get_session_id(VOIP_SESSION_NAME)); + voc_register_mvs_cb(NULL, NULL, prtd); + } + /* release all buffer */ + /* release in_queue and free_in_queue */ + pr_debug("release all buffer\n"); + p_substream = prtd->playback_substream; + if (p_substream == NULL) { + pr_debug("p_substream is NULL\n"); + goto capt; + } + p_dma_buf = &p_substream->dma_buffer; + if (p_dma_buf == NULL) { + pr_debug("p_dma_buf is NULL\n"); + goto capt; + } + if (p_dma_buf->area != NULL) { + mutex_lock(&prtd->in_lock); + list_for_each_safe(ptr, next, &prtd->in_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + list_for_each_safe(ptr, next, &prtd->free_in_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + dma_free_coherent(p_substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, p_dma_buf->area, + p_dma_buf->addr); + p_dma_buf->area = NULL; + mutex_unlock(&prtd->in_lock); + } + /* release out_queue and free_out_queue */ +capt: c_substream = prtd->capture_substream; + if (c_substream == NULL) { + pr_debug("c_substream is NULL\n"); + goto done; + } + c_dma_buf = &c_substream->dma_buffer; + if (c_substream == NULL) { + pr_debug("c_dma_buf is NULL.\n"); + goto done; + } + if (c_dma_buf->area != NULL) { + mutex_lock(&prtd->out_lock); + list_for_each_safe(ptr, next, &prtd->out_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + list_for_each_safe(ptr, next, &prtd->free_out_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + dma_free_coherent(c_substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, c_dma_buf->area, + c_dma_buf->addr); + c_dma_buf->area = NULL; + mutex_unlock(&prtd->out_lock); + } +done: + prtd->capture_substream = NULL; + prtd->playback_substream = NULL; + } + mutex_unlock(&prtd->lock); + + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + uint32_t media_type = 0; + uint32_t rate_type = 0; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + + if ((runtime->format != FORMAT_SPECIAL) && + ((prtd->mode == MODE_AMR) || (prtd->mode == MODE_AMR_WB) || + (prtd->mode == MODE_IS127) || (prtd->mode == MODE_4GV_NB) || + (prtd->mode == MODE_4GV_WB))) { + pr_err("mode:%d and format:%u are not mached\n", + prtd->mode, (uint32_t)runtime->format); + ret = -EINVAL; + goto done; + } + + if ((runtime->format != FORMAT_S16_LE) && + (prtd->mode == MODE_PCM)) { + pr_err("mode:%d and format:%u are not mached\n", + prtd->mode, (uint32_t)runtime->format); + ret = -EINVAL; + goto done; + } + + if (prtd->playback_instance && prtd->capture_instance + && (prtd->state != VOIP_STARTED)) { + + ret = voip_get_rate_type(prtd->mode, + prtd->rate, + &rate_type); + if (ret < 0) { + pr_err("fail at getting rate_type\n"); + ret = -EINVAL; + goto done; + } + prtd->rate_type = rate_type; + media_type = voip_get_media_type(prtd->mode, + prtd->play_samp_rate); + if (media_type < 0) { + pr_err("fail at getting media_type\n"); + ret = -EINVAL; + goto done; + } + pr_debug(" media_type=%d, rate_type=%d\n", media_type, + rate_type); + if ((prtd->play_samp_rate == 8000) && + (prtd->cap_samp_rate == 8000)) + voc_config_vocoder(media_type, rate_type, + VSS_NETWORK_ID_VOIP_NB, + voip_info.dtx_mode); + else if ((prtd->play_samp_rate == 16000) && + (prtd->cap_samp_rate == 16000)) + voc_config_vocoder(media_type, rate_type, + VSS_NETWORK_ID_VOIP_WB, + voip_info.dtx_mode); + else { + pr_debug("%s: Invalid rate playback %d, capture %d\n", + __func__, prtd->play_samp_rate, + prtd->cap_samp_rate); + goto done; + } + voc_register_mvs_cb(voip_process_ul_pkt, + voip_process_dl_pkt, prtd); + voc_start_voice_call(voc_get_session_id(VOIP_SESSION_NAME)); + + prtd->state = VOIP_STARTED; + } +done: + mutex_unlock(&prtd->lock); + + return ret; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + if (prtd->pcm_playback_irq_pos >= prtd->pcm_size) + prtd->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + if (prtd->pcm_capture_irq_pos >= prtd->pcm_capture_size) + prtd->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s\n", __func__); + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct voip_buf_node *buf_node = NULL; + int i = 0, offset = 0; + + pr_debug("%s: voip\n", __func__); + + mutex_lock(&voip_info.lock); + + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + + dma_buf->area = dma_alloc_coherent(substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, + &dma_buf->addr, GFP_KERNEL); + if (!dma_buf->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + buf_node = (void *)dma_buf->area + offset; + + mutex_lock(&voip_info.in_lock); + list_add_tail(&buf_node->list, + &voip_info.free_in_queue); + mutex_unlock(&voip_info.in_lock); + offset = offset + sizeof(struct voip_buf_node); + } + } else { + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + buf_node = (void *) dma_buf->area + offset; + mutex_lock(&voip_info.out_lock); + list_add_tail(&buf_node->list, + &voip_info.free_out_queue); + mutex_unlock(&voip_info.out_lock); + offset = offset + sizeof(struct voip_buf_node); + } + } + + mutex_unlock(&voip_info.lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int msm_voip_mode_rate_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + ucontrol->value.integer.value[0] = voip_info.mode; + ucontrol->value.integer.value[1] = voip_info.rate; + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static int msm_voip_mode_rate_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + mutex_lock(&voip_info.lock); + + voip_info.mode = ucontrol->value.integer.value[0]; + voip_info.rate = ucontrol->value.integer.value[1]; + + pr_debug("%s: mode=%d,rate=%d\n", __func__, voip_info.mode, + voip_info.rate); + + mutex_unlock(&voip_info.lock); + + return 0; +} + +static int voip_get_rate_type(uint32_t mode, uint32_t rate, + uint32_t *rate_type) +{ + int ret = 0; + + switch (mode) { + case MODE_AMR: { + switch (rate) { + case 4750: + *rate_type = AMR_RATE_4750; + break; + case 5150: + *rate_type = AMR_RATE_5150; + break; + case 5900: + *rate_type = AMR_RATE_5900; + break; + case 6700: + *rate_type = AMR_RATE_6700; + break; + case 7400: + *rate_type = AMR_RATE_7400; + break; + case 7950: + *rate_type = AMR_RATE_7950; + break; + case 10200: + *rate_type = AMR_RATE_10200; + break; + case 12200: + *rate_type = AMR_RATE_12200; + break; + default: + pr_err("wrong rate for AMR NB.\n"); + ret = -EINVAL; + break; + } + break; + } + case MODE_AMR_WB: { + switch (rate) { + case 6600: + *rate_type = AMR_RATE_6600 - AMR_RATE_6600; + break; + case 8850: + *rate_type = AMR_RATE_8850 - AMR_RATE_6600; + break; + case 12650: + *rate_type = AMR_RATE_12650 - AMR_RATE_6600; + break; + case 14250: + *rate_type = AMR_RATE_14250 - AMR_RATE_6600; + break; + case 15850: + *rate_type = AMR_RATE_15850 - AMR_RATE_6600; + break; + case 18250: + *rate_type = AMR_RATE_18250 - AMR_RATE_6600; + break; + case 19850: + *rate_type = AMR_RATE_19850 - AMR_RATE_6600; + break; + case 23050: + *rate_type = AMR_RATE_23050 - AMR_RATE_6600; + break; + case 23850: + *rate_type = AMR_RATE_23850 - AMR_RATE_6600; + break; + default: + pr_err("wrong rate for AMR_WB.\n"); + ret = -EINVAL; + break; + } + break; + } + case MODE_PCM: { + *rate_type = 0; + break; + } + case MODE_IS127: + case MODE_4GV_NB: + case MODE_4GV_WB: { + switch (rate) { + case VOC_0_RATE: + case VOC_8_RATE: + case VOC_4_RATE: + case VOC_2_RATE: + case VOC_1_RATE: + *rate_type = rate; + break; + default: + pr_err("wrong rate for IS127/4GV_NB/WB.\n"); + ret = -EINVAL; + break; + } + break; + } + default: + pr_err("wrong mode type.\n"); + ret = -EINVAL; + } + pr_debug("%s, mode=%d, rate=%u, rate_type=%d\n", + __func__, mode, rate, *rate_type); + return ret; +} + +static int voip_get_media_type(uint32_t mode, + unsigned int samp_rate) +{ + uint32_t media_type; + + pr_debug("%s: mode=%d, samp_rate=%d\n", __func__, + mode, samp_rate); + switch (mode) { + case MODE_AMR: + media_type = VSS_MEDIA_ID_AMR_NB_MODEM; + break; + case MODE_AMR_WB: + media_type = VSS_MEDIA_ID_AMR_WB_MODEM; + break; + case MODE_PCM: + if (samp_rate == 8000) + media_type = VSS_MEDIA_ID_PCM_NB; + else + media_type = VSS_MEDIA_ID_PCM_WB; + break; + case MODE_IS127: /* EVRC-A */ + media_type = VSS_MEDIA_ID_EVRC_MODEM; + break; + case MODE_4GV_NB: /* EVRC-B */ + media_type = VSS_MEDIA_ID_4GV_NB_MODEM; + break; + case MODE_4GV_WB: /* EVRC-WB */ + media_type = VSS_MEDIA_ID_4GV_WB_MODEM; + break; + default: + pr_debug(" input mode is not supported\n"); + media_type = -EINVAL; + } + + pr_debug("%s: media_type is 0x%x\n", __func__, media_type); + + return media_type; +} + + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + pr_debug("msm_asoc_pcm_new\n"); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_pcm_voip_probe, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-voip-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + memset(&voip_info, 0, sizeof(voip_info)); + voip_info.mode = MODE_PCM; + mutex_init(&voip_info.lock); + mutex_init(&voip_info.in_lock); + mutex_init(&voip_info.out_lock); + + spin_lock_init(&voip_info.dsp_lock); + + init_waitqueue_head(&voip_info.out_wait); + init_waitqueue_head(&voip_info.in_wait); + + INIT_LIST_HEAD(&voip_info.in_queue); + INIT_LIST_HEAD(&voip_info.free_in_queue); + INIT_LIST_HEAD(&voip_info.out_queue); + INIT_LIST_HEAD(&voip_info.free_out_queue); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm.c b/sound/soc/msm/msm-pcm.c new file mode 100644 index 000000000000..d40ca67a010f --- /dev/null +++ b/sound/soc/msm/msm-pcm.c @@ -0,0 +1,646 @@ +/* sound/soc/msm/msm-pcm.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, 2012 The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" + +#define MAX_DATA_SIZE 496 +#define AUDPP_ALSA_DECODER (-1) + +#define DB_TABLE_INDEX (50) + +#define audio_send_queue_recbs(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecBitStreamQueue, cmd, len) +#define audio_send_queue_rec(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecCmdQueue, cmd, len) + +int intcnt; + +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__ ((packed)); + +/* Table contains dB to raw value mapping */ +static const unsigned decoder_db_table[] = { + + 31 , /* -50 dB */ + 35 , 39 , 44 , 50 , 56 , + 63 , 70 , 79 , 89 , 99 , + 112 , 125 , 141 , 158 , 177 , + 199 , 223 , 251 , 281 , 316 , + 354 , 398 , 446 , 501 , 562 , + 630 , 707 , 794 , 891 , 999 , + 1122 , 1258 , 1412 , 1584 , 1778 , + 1995 , 2238 , 2511 , 2818 , 3162 , + 3548 , 3981 , 4466 , 5011 , 5623 , + 6309 , 7079 , 7943 , 8912 , 10000 , + 11220 , 12589 , 14125 , 15848 , 17782 , + 19952 , 22387 , 25118 , 28183 , 31622 , + 35481 , 39810 , 44668 , 50118 , 56234 , + 63095 , 70794 , 79432 , 89125 , 100000 , + 112201 , 125892 , 141253 , 158489 , 177827 , + 199526 , 223872 , 251188 , 281838 , 316227 , + 354813 , 398107 , 446683 , 501187 , 562341 , + 630957 , 707945 , 794328 , 891250 , 1000000 , + 1122018 , 1258925 , 1412537 , 1584893 , 1778279 , + 1995262 , 2238721 , 2511886 , 2818382 , 3162277 , + 3548133 /* 51 dB */ + +}; + +static unsigned compute_db_raw(int db) +{ + unsigned reg_val = 0; /* Computed result for correspondent db */ + /* Check if the given db is out of range */ + if (db <= MIN_DB) + return 0; + else if (db > MAX_DB) + db = MAX_DB; /* If db is too high then set to max */ + reg_val = decoder_db_table[DB_TABLE_INDEX+db]; + return reg_val; +} + +int msm_audio_volume_update(unsigned id, + int volume, int pan) +{ + unsigned vol_raw; + + vol_raw = compute_db_raw(volume); + printk(KERN_INFO "volume: %8x vol_raw: %8x \n", volume, vol_raw); + return audpp_set_volume_and_pan(id, vol_raw, pan); +} +EXPORT_SYMBOL(msm_audio_volume_update); + +void alsa_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct msm_audio *prtd = data; + struct buffer *frame; + unsigned long flag; + + switch (id) { + case AUDPP_MSG_STATUS_MSG: + break; + case AUDPP_MSG_SPA_BANDS: + break; + case AUDPP_MSG_HOST_PCM_INTF_MSG:{ + unsigned id = msg[2]; + unsigned idx = msg[3] - 1; + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + printk(KERN_ERR "bogus id\n"); + break; + } + if (idx > 1) { + printk(KERN_ERR "bogus buffer idx\n"); + break; + } + /* Update with actual sent buffer size */ + if (prtd->out[idx].used != BUF_INVALID_LEN) + prtd->pcm_irq_pos += prtd->out[idx].used; + + if (prtd->pcm_irq_pos > prtd->pcm_size) + prtd->pcm_irq_pos = prtd->pcm_count; + + if (prtd->ops->playback) + prtd->ops->playback(prtd); + + if (prtd->mmap_flag) + break; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running) { + prtd->out[idx].used = 0; + frame = prtd->out + prtd->out_tail; + if (frame->used) { + alsa_dsp_send_buffer(prtd, + prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + } else { + prtd->out_needed++; + } + wake_up(&the_locks.write_wait); + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + pr_info("alsa_dsp_event: PCMDMAMISSED %d\n", msg[0]); + prtd->eos_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + prtd->out_needed = 0; + prtd->running = 1; + audio_dsp_out_enable(prtd, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + prtd->running = 0; + } else { + printk(KERN_ERR "alsa_dsp_event:CFG_MSG=%d\n", msg[0]); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"alsa_dsp_event: arm9 event\n"); + break; + default: + printk(KERN_ERR "alsa_dsp_event: UNKNOWN (%d)\n", id); + } +} + +void alsa_audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audpre: event too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + printk(KERN_ERR "audpre: err_index %d\n", msg[0]); + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audpre: arm9 event\n"); + break; + default: + printk(KERN_ERR "audpre: unknown event %d\n", id); + } +} + +void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct msm_audio *prtd = data; + unsigned long flag; + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audrec: event/msg too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_UPDATE) { + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_ENA) + audrec_encoder_config(prtd); + else + prtd->running = 0; + } + break; + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG:{ + prtd->running = 1; + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: + printk(KERN_ERR "audrec: ERROR %x\n", msg[0]); + break; + case AUDREC_MSG_PACKET_READY_MSG: + alsa_get_dsp_frames(prtd); + ++intcnt; + if (prtd->channel_mode == 1) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } else if ((prtd->channel_mode == 0) && (intcnt % 2 == 0)) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audrec: arm9 event\n"); + break; + default: + printk(KERN_ERR "audrec: unknown event %d\n", id); + } +} + +struct msm_adsp_ops aud_pre_adsp_ops = { + .event = alsa_audpre_dsp_event, +}; + +struct msm_adsp_ops aud_rec_adsp_ops = { + .event = audrec_dsp_event, +}; + +int alsa_adsp_configure(struct msm_audio *prtd) +{ + int ret, i; + + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->data = prtd->playback_substream->dma_buffer.area; + prtd->phys = prtd->playback_substream->dma_buffer.addr; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->data = prtd->capture_substream->dma_buffer.area; + prtd->phys = prtd->capture_substream->dma_buffer.addr; + } + if (!prtd->data) { + ret = -ENOMEM; + goto err1; + } + + ret = audmgr_open(&prtd->audmgr); + if (ret) + goto err2; + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->out_buffer_size = PLAYBACK_DMASZ; + prtd->out_sample_rate = 44100; + prtd->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + prtd->out_weight = 100; + + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_44100; + prtd->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_44100; + prtd->channel_mode = AUDREC_CMD_STEREO_MODE_STEREO; + prtd->buffer_size = STEREO_DATA_SIZE; + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + prtd->tx_agc_cfg.cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + prtd->ns_cfg.cmd_id = AUDPREPROC_CMD_CFG_NS_PARAMS; + prtd->iir_cfg.cmd_id = + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + ret = msm_adsp_get("AUDPREPROCTASK", + &prtd->audpre, &aud_pre_adsp_ops, prtd); + if (ret) + goto err3; + ret = msm_adsp_get("AUDRECTASK", + &prtd->audrec, &aud_rec_adsp_ops, prtd); + if (ret) { + msm_adsp_put(prtd->audpre); + goto err3; + } + prtd->dsp_cnt = 0; + prtd->in_head = 0; + prtd->in_tail = 0; + prtd->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + prtd->in[i].size = 0; + prtd->in[i].read = 0; + } + } + + return 0; + +err3: + audmgr_close(&prtd->audmgr); + +err2: + prtd->data = NULL; +err1: + return ret; +} +EXPORT_SYMBOL(alsa_adsp_configure); + +int alsa_audio_configure(struct msm_audio *prtd) +{ + struct audmgr_config cfg; + int rc; + + if (prtd->enabled) + return 0; + + /* refuse to start if we're not ready with first buffer */ + if (!prtd->out[0].used) + return -EIO; + + cfg.tx_rate = 0; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (audpp_enable(AUDPP_ALSA_DECODER, alsa_dsp_event, prtd)) { + printk(KERN_ERR "audio: audpp_enable() failed\n"); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + + prtd->enabled = 1; + return 0; +} +EXPORT_SYMBOL(alsa_audio_configure); + +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int rc = 0; + + mutex_lock(&the_locks.write_lock); + while (count > 0) { + frame = prtd->out + prtd->out_head; + rc = wait_event_interruptible(the_locks.write_wait, + (frame->used == 0) + || (prtd->stopped)); + if (rc < 0) + break; + if (prtd->stopped) { + rc = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + rc = -EFAULT; + break; + } + frame->used = xfer; + prtd->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + frame = prtd->out + prtd->out_tail; + if (frame->used && prtd->out_needed) { + alsa_dsp_send_buffer(prtd, prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + prtd->out_needed--; + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + } + mutex_unlock(&the_locks.write_lock); + if (buf > start) + return buf - start; + return rc; +} +EXPORT_SYMBOL(alsa_send_buffer); + +int alsa_audio_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + audio_dsp_out_enable(prtd, 0); + wake_up(&the_locks.write_wait); + audpp_disable(AUDPP_ALSA_DECODER, prtd); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audio_disable); + +int alsa_audrec_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + alsa_rec_dsp_enable(prtd, 0); + wake_up(&the_locks.read_wait); + msm_adsp_disable(prtd->audpre); + msm_adsp_disable(prtd->audrec); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + prtd->opened = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audrec_disable); + +static int audio_dsp_read_buffer(struct msm_audio *prtd, uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + /* Both WAV and AAC use AUDREC_CMD_TYPE_0 */ + cmd.type = AUDREC_CMD_TYPE_0; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(prtd, &cmd, sizeof(cmd)); +} + +int audrec_encoder_config(struct msm_audio *prtd) +{ + audrec_cmd_arec0param_cfg cmd; + uint16_t *data = (void *)prtd->data; + unsigned n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_AREC0PARAM_CFG; + cmd.ptr_to_extpkt_buffer_msw = prtd->phys >> 16; + cmd.ptr_to_extpkt_buffer_lsw = prtd->phys; + cmd.buf_len = FRAME_NUM; /* Both WAV and AAC use 8 frames */ + cmd.samp_rate_index = prtd->samp_rate_index; + /* 0 for mono, 1 for stereo */ + cmd.stereo_mode = prtd->channel_mode; + cmd.rec_quality = 0x1C00; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + + for (n = 0; n < FRAME_NUM; n++) { + prtd->in[n].data = data + 4; + data += (4 + (prtd->channel_mode ? 2048 : 1024)); + } + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} + +int audio_dsp_out_enable(struct msm_audio *prtd, int yes) +{ + audpp_cmd_pcm_intf cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.object_num = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = prtd->out[0].addr; + cmd.write_buf1MSW = prtd->out[0].addr >> 16; + cmd.write_buf1_len = 0; + cmd.write_buf2LSW = prtd->out[1].addr; + cmd.write_buf2MSW = prtd->out[1].addr >> 16; + cmd.write_buf2_len = prtd->out[1].used; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = prtd->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = prtd->out_sample_rate; + cmd.channel_mode = prtd->out_channel_mode; + } + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&the_locks.read_lock); + while (count > 0) { + rc = wait_event_interruptible(the_locks.read_wait, + (prtd->in_count > 0) + || prtd->stopped); + if (rc < 0) + break; + + if (prtd->stopped) { + rc = -EBUSY; + break; + } + + index = prtd->in_tail; + data = (uint8_t *) prtd->in[index].data; + size = prtd->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (index != prtd->in_tail) { + /* overrun: data is invalid, we need to retry */ + spin_unlock_irqrestore(&the_locks.read_dsp_lock, + flag); + continue; + } + prtd->in[index].size = 0; + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + prtd->in_count--; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + count -= size; + buf += size; + } else { + break; + } + } + mutex_unlock(&the_locks.read_lock); + return rc; +} +EXPORT_SYMBOL(alsa_buffer_read); + +int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len) +{ + audpp_cmd_pcm_intf_send_buffer cmd; + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.host_pcm_object = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable) +{ + audrec_cmd_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_CFG; + cmd.type_0 = enable ? AUDREC_CMD_TYPE_0_ENA : AUDREC_CMD_TYPE_0_DIS; + cmd.type_0 |= (AUDREC_CMD_TYPE_0_UPDATE | prtd->type); + cmd.type_1 = 0; + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(alsa_rec_dsp_enable); + +void alsa_get_dsp_frames(struct msm_audio *prtd) +{ + struct audio_frame *frame; + uint32_t index = 0; + unsigned long flag; + + if (prtd->type == AUDREC_CMD_TYPE_0_INDEX_WAV) { + index = prtd->in_head; + + frame = + (void *)(((char *)prtd->in[index].data) - sizeof(*frame)); + + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->in[index].size = frame->bytes; + + prtd->in_head = (prtd->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (prtd->in_head == prtd->in_tail) + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + else + prtd->in_count++; + + audio_dsp_read_buffer(prtd, prtd->dsp_cnt++); + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + wake_up(&the_locks.read_wait); + } else { + /* TODO AAC not supported yet. */ + } +} +EXPORT_SYMBOL(alsa_get_dsp_frames); diff --git a/sound/soc/msm/msm-pcm.h b/sound/soc/msm/msm-pcm.h new file mode 100644 index 000000000000..d391d9a8bde0 --- /dev/null +++ b/sound/soc/msm/msm-pcm.h @@ -0,0 +1,204 @@ +/* sound/soc/msm/msm-pcm.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, 2012 The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + +#include +#include +#include +#include +#include +#include + +#include <../arch/arm/mach-msm/qdsp5/adsp.h> +#include <../arch/arm/mach-msm/qdsp5/audmgr.h> + + +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define CAPTURE_DMASZ (FRAME_SIZE * FRAME_NUM) + +#define BUFSZ (960 * 5) +#define PLAYBACK_DMASZ (BUFSZ * 2) + +#define MSM_PLAYBACK_DEFAULT_VOLUME 0 /* 0dB */ +#define MSM_PLAYBACK_DEFAULT_PAN 0 + +#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 +#define MAX_BUFFER_PLAYBACK_SIZE \ + (4800*4) +/* 2048 frames (Mono), 1024 frames (Stereo) */ +#define CAPTURE_SIZE 4096 +#define MAX_BUFFER_CAPTURE_SIZE (4096*4) +#define MAX_PERIOD_SIZE BUFSZ +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define MAX_DB (16) +#define MIN_DB (-50) +#define PCMPLAYBACK_DECODERID 5 + +/* 0xFFFFFFFF Indicates not to be used for audio data copy */ +#define BUF_INVALID_LEN 0xFFFFFFFF + +extern int copy_count; +extern int intcnt; + +struct msm_volume { + bool update; + int volume; /* Volume parameter, in dB Scale */ + int pan; +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + struct mutex lock; + struct mutex write_lock; + struct mutex read_lock; + spinlock_t read_dsp_lock; + spinlock_t write_dsp_lock; + spinlock_t mixer_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio_event_callbacks { + /* event is called from interrupt context when a message + * arrives from the DSP. + */ + void (*playback)(void *); + void (*capture)(void *); +}; + + +struct msm_audio { + struct buffer out[2]; + struct buffer_rec in[8]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + atomic_t out_bytes; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct audmgr audmgr; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + + /* audpre settings */ + audpreproc_cmd_cfg_agc_params tx_agc_cfg; + audpreproc_cmd_cfg_ns_params ns_cfg; + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + audpreproc_cmd_cfg_iir_tuning_filter_params iir_cfg; + + struct msm_audio_event_callbacks *ops; + + int dir; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int eos_ack; + int mmap_flag; + int period; +}; + + + +/* platform data */ +extern int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len); +extern int audio_dsp_out_enable(struct msm_audio *prtd, int yes); +extern struct snd_soc_platform_driver msm_soc_platform; + +int audrec_encoder_config(struct msm_audio *prtd); +extern void alsa_get_dsp_frames(struct msm_audio *prtd); +extern int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable); +extern int alsa_audrec_disable(struct msm_audio *prtd); +extern int alsa_audio_configure(struct msm_audio *prtd); +extern int alsa_audio_disable(struct msm_audio *prtd); +extern int alsa_adsp_configure(struct msm_audio *prtd); +extern int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos); +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos); +int msm_audio_volume_update(unsigned id, + int volume, int pan); +extern struct audio_locks the_locks; +extern struct msm_volume msm_vol_ctl; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-voip.c b/sound/soc/msm/msm-voip.c new file mode 100644 index 000000000000..51e6c868fbd3 --- /dev/null +++ b/sound/soc/msm/msm-voip.c @@ -0,0 +1,610 @@ +/* Copyright (c) 2011, The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + + +static struct audio_voip_info_type audio_voip_info; +static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data); +static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data); + +struct msm_audio_mvs_frame { + uint32_t frame_type; + uint32_t len; + uint8_t voc_pkt[MVS_MAX_VOC_PKT_SIZE]; +}; + +struct audio_mvs_buf_node { + struct list_head list; + struct msm_audio_mvs_frame frame; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN, + .period_bytes_min = MVS_MAX_VOC_PKT_SIZE, + .period_bytes_max = MVS_MAX_VOC_PKT_SIZE, + .periods_min = VOIP_MAX_Q_LEN, + .periods_max = VOIP_MAX_Q_LEN, + .fifo_size = 0, +}; + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + + struct audio_voip_info_type *audio = &audio_voip_info; + pr_debug("%s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 1; + else + audio->capture_start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 0; + else + audio->capture_start = 0; + break; + default: + break; + } + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_voip_info_type *audio = &audio_voip_info; + struct audio_mvs_release_msg release_msg; + + pr_debug("%s\n", __func__); + memset(&release_msg, 0, sizeof(release_msg)); + mutex_lock(&audio->lock); + + audio->instance--; + wake_up(&audio->out_wait); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_state = AUDIO_MVS_CLOSED; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + audio->capture_state = AUDIO_MVS_CLOSED; + if (!audio->instance) { + /* Release MVS. */ + release_msg.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + /* Derigstering the callbacks with voice driver */ + voice_register_mvs_cb(NULL, NULL, audio); + } else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + NULL, audio); + } else { + voice_register_mvs_cb(NULL, audio_mvs_process_dl_pkt, + audio); + } + + mutex_unlock(&audio->lock); + + wake_unlock(&audio->suspend_lock); + pm_qos_update_request(&audio->pm_qos_req, PM_QOS_DEFAULT_VALUE); + /* Release the IO buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + audio->in_write = 0; + audio->in_read = 0; + memset(audio->in[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio->playback_substream = NULL; + } else { + audio->out_write = 0; + audio->out_read = 0; + memset(audio->out[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio->capture_substream = NULL; + } + return rc; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + pr_debug("%s\n", __func__); + mutex_lock(&audio->lock); + + if (audio->playback_substream == NULL || + audio->capture_substream == NULL) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + audio->playback_substream = substream; + runtime->hw = msm_pcm_hardware; + audio_voip_info.in_read = 0; + audio_voip_info.in_write = 0; + if (audio->playback_state < AUDIO_MVS_OPENED) + audio->playback_state = AUDIO_MVS_OPENED; + } else if (substream->stream == + SNDRV_PCM_STREAM_CAPTURE) { + audio->capture_substream = substream; + runtime->hw = msm_pcm_hardware; + audio_voip_info.out_read = 0; + audio_voip_info.out_write = 0; + if (audio->capture_state < AUDIO_MVS_OPENED) + audio->capture_state = AUDIO_MVS_OPENED; + } + } else { + ret = -EPERM; + goto err; + } + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_debug("%s:snd_pcm_hw_constraint_integer failed\n", __func__); + goto err; + } + audio->instance++; + +err: + mutex_unlock(&audio->lock); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + uint32_t index; + pr_debug("%s\n", __func__); + + rc = wait_event_timeout(audio->in_wait, + (audio->in_write - audio->in_read <= VOIP_MAX_Q_LEN-1), + 1 * HZ); + if (rc < 0) { + pr_debug("%s: write was interrupted\n", __func__); + return -ERESTARTSYS; + } + + if (audio->playback_state == AUDIO_MVS_ENABLED) { + index = audio->in_write % VOIP_MAX_Q_LEN; + count = frames_to_bytes(runtime, frames); + if (count == MVS_MAX_VOC_PKT_SIZE) { + pr_debug("%s:write index = %d\n", __func__, index); + rc = copy_from_user(audio->in[index].voc_pkt, buf, + count); + if (!rc) { + audio->in[index].len = count; + audio->in_write++; + } else { + pr_debug("%s:Copy from user returned %d\n", + __func__, rc); + rc = -EFAULT; + } + } else + rc = -ENOMEM; + + } else { + pr_debug("%s:Write performed in invalid state %d\n", + __func__, audio->playback_state); + rc = -EINVAL; + } + return rc; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, + void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + uint32_t index = 0; + + pr_debug("%s\n", __func__); + + /* Ensure the driver has been enabled. */ + if (audio->capture_state != AUDIO_MVS_ENABLED) { + pr_debug("%s:Read performed in invalid state %d\n", + __func__, audio->capture_state); + return -EPERM; + } + rc = wait_event_timeout(audio->out_wait, + ((audio->out_read < audio->out_write) || + (audio->capture_state == AUDIO_MVS_CLOSING) || + (audio->capture_state == AUDIO_MVS_CLOSED)), + 1 * HZ); + + if (rc < 0) { + pr_debug("%s: Read was interrupted\n", __func__); + return -ERESTARTSYS; + } + + if (audio->capture_state == AUDIO_MVS_CLOSING + || audio->capture_state == AUDIO_MVS_CLOSED) { + pr_debug("%s:EBUSY STATE\n", __func__); + rc = -EBUSY; + } else { + count = frames_to_bytes(runtime, frames); + index = audio->out_read % VOIP_MAX_Q_LEN; + pr_debug("%s:index=%d\n", __func__, index); + if (audio->out[index].len <= count) { + rc = copy_to_user(buf, + audio->out[index].voc_pkt, + audio->out[index].len); + if (rc) { + pr_debug("%s:Copy to user %d\n", + __func__, rc); + rc = -EFAULT; + } else + audio->out_read++; + } else { + pr_debug("%s:returning ENOMEM\n", __func__); + rc = -ENOMEM; + } + } + return rc; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + pr_debug("%s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +/* Capture path */ +static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data) +{ + struct audio_voip_info_type *audio = private_data; + uint32_t index; + static int i; + pr_debug("%s\n", __func__); + + if (audio->capture_substream == NULL) + return; + index = audio->out_write % VOIP_MAX_Q_LEN; + memcpy(audio->out[index].voc_pkt, voc_pkt, pkt_len); + audio->out[index].len = pkt_len; + audio->out_write++; + wake_up(&audio->out_wait); + i++; + if (audio->capture_start) { + audio->pcm_capture_irq_pos += audio->pcm_count; + if (!(i % 2)) + snd_pcm_period_elapsed(audio->capture_substream); + } +} + +/* Playback path */ +static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data) +{ + struct audio_voip_info_type *audio = private_data; + uint32_t index; + static int i; + pr_debug("%s\n", __func__); + + if (audio->playback_substream == NULL) + return; + if ((audio->in_write - audio->in_read >= 0) + && (audio->playback_start)) { + index = audio->in_read % VOIP_MAX_Q_LEN; + *pkt_len = audio->pcm_count; + memcpy(voc_pkt, audio->in[index].voc_pkt, *pkt_len); + audio->in_read++; + wake_up(&audio->in_wait); + i++; + audio->pcm_playback_irq_pos += audio->pcm_count; + if (!(i%2)) + snd_pcm_period_elapsed(audio->playback_substream); + pr_debug("%s:read_index=%d\n", __func__, index); + } +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_voip_info_type *prtd = &audio_voip_info; + pr_debug("%s\n", __func__); + prtd->pcm_playback_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + pr_debug("%s:prtd->pcm_playback_size:%d\n", + __func__, prtd->pcm_playback_size); + pr_debug("%s:prtd->pcm_count:%d\n", __func__, prtd->pcm_count); + + mutex_lock(&prtd->prepare_lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->playback_state == AUDIO_MVS_ENABLED) + goto enabled; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (prtd->capture_state == AUDIO_MVS_ENABLED) + goto enabled; + } + + pr_debug("%s:Register cbs with voice driver check audio_mvs_driver\n", + __func__); + if (prtd->instance == 2) { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + audio_mvs_process_dl_pkt, + prtd); + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + voice_register_mvs_cb(NULL, + audio_mvs_process_dl_pkt, + prtd); + } else { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + NULL, + prtd); + } + } + +enabled: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->playback_state = AUDIO_MVS_ENABLED; + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + /* rate and channels are sent to audio driver */ + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prtd->capture_state = AUDIO_MVS_ENABLED; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + } + mutex_unlock(&prtd->prepare_lock); + return rc; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + if (audio->pcm_playback_irq_pos >= audio->pcm_playback_size) + audio->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + if (audio->pcm_capture_irq_pos >= audio->pcm_capture_size) + audio->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static struct snd_pcm_ops msm_mvs_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int i, ret, offset = 0; + struct snd_pcm_substream *substream = NULL; + struct snd_dma_buffer *dma_buffer = NULL; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_mvs_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_mvs_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (!substream) + return -ENOMEM; + + dma_buffer = &substream->dma_buffer; + dma_buffer->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buffer->dev.dev = card->dev; + dma_buffer->private_data = NULL; + dma_buffer->area = dma_alloc_coherent(card->dev, + (MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN), + &dma_buffer->addr, GFP_KERNEL); + if (!dma_buffer->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + dma_buffer->bytes = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN; + memset(dma_buffer->area, 0, MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio_voip_info.in_read = 0; + audio_voip_info.in_write = 0; + audio_voip_info.out_read = 0; + audio_voip_info.out_write = 0; + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + audio_voip_info.in[i].voc_pkt = + dma_buffer->area + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (!substream) + return -ENOMEM; + + dma_buffer = &substream->dma_buffer; + dma_buffer->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buffer->dev.dev = card->dev; + dma_buffer->private_data = NULL; + dma_buffer->area = dma_alloc_coherent(card->dev, + (MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN), + &dma_buffer->addr, GFP_KERNEL); + if (!dma_buffer->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + memset(dma_buffer->area, 0, MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + dma_buffer->bytes = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN; + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + audio_voip_info.out[i].voc_pkt = + dma_buffer->area + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + audio_voip_info.playback_substream = NULL; + audio_voip_info.capture_substream = NULL; + + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +struct snd_soc_platform_driver msm_mvs_soc_platform = { + .ops = &msm_mvs_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL(msm_mvs_soc_platform); + +static int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_mvs_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-mvs-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_mvs_soc_platform_init(void) +{ + memset(&audio_voip_info, 0, sizeof(audio_voip_info)); + mutex_init(&audio_voip_info.lock); + mutex_init(&audio_voip_info.prepare_lock); + init_waitqueue_head(&audio_voip_info.out_wait); + init_waitqueue_head(&audio_voip_info.in_wait); + wake_lock_init(&audio_voip_info.suspend_lock, WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + pm_qos_add_request(&audio_voip_info.pm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_mvs_soc_platform_init); + +static void __exit msm_mvs_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_mvs_soc_platform_exit); + +MODULE_DESCRIPTION("MVS PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7201.c b/sound/soc/msm/msm7201.c new file mode 100644 index 000000000000..ba368d114c9c --- /dev/null +++ b/sound/soc/msm/msm7201.c @@ -0,0 +1,424 @@ +/* linux/sound/soc/msm/msm7201.c + * + * Copyright (c) 2008-2009, 2011, 2012 The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" +#include +#include + +static struct msm_rpc_endpoint *snd_ep; +static uint32_t snd_mute_ear_mute; +static uint32_t snd_mute_mic_mute; + +struct msm_snd_rpc_ids { + unsigned long prog; + unsigned long vers; + unsigned long vers2; + unsigned long rpc_set_snd_device; + unsigned long rpc_set_device_vol; + int device; +}; + +static struct msm_snd_rpc_ids snd_rpc_ids; + +static struct platform_device *msm_audio_snd_device; + +static int snd_msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Volume Param, in dB */ + uinfo->value.integer.min = MIN_DB; + uinfo->value.integer.max = MAX_DB; + return 0; +} + +static int snd_msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spin_lock_irq(&the_locks.mixer_lock); + ucontrol->value.integer.value[0] = msm_vol_ctl.volume; + spin_unlock_irq(&the_locks.mixer_lock); + return 0; +} + +static int snd_msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + int volume; + + volume = ucontrol->value.integer.value[0]; + spin_lock_irq(&the_locks.mixer_lock); + change = (msm_vol_ctl.volume != volume); + if (change) { + msm_vol_ctl.volume = volume; + msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + } + spin_unlock_irq(&the_locks.mixer_lock); + return 0; +} + +static int snd_msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + /* + * The number of devices supported is 26 (0 to 25) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 36; + return 0; +} + +static int snd_msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = (uint32_t)snd_rpc_ids.device; + ucontrol->value.integer.value[1] = snd_mute_ear_mute; + ucontrol->value.integer.value[2] = snd_mute_mic_mute; + return 0; +} + +int msm_snd_init_rpc_ids(void) +{ + snd_rpc_ids.prog = 0x30000002; + snd_rpc_ids.vers = 0x00020001; + snd_rpc_ids.vers2 = 0x00030001; + /* + * The magic number 2 corresponds to the rpc call + * index for snd_set_device + */ + snd_rpc_ids.rpc_set_snd_device = 2; + snd_rpc_ids.rpc_set_device_vol = 3; + return 0; +} + +int msm_snd_rpc_connect(void) +{ + if (snd_ep) { + printk(KERN_INFO "%s: snd_ep already connected\n", __func__); + return 0; + } + + /* Initialize rpc ids */ + if (msm_snd_init_rpc_ids()) { + printk(KERN_ERR "%s: snd rpc ids initialization failed\n" + , __func__); + return -ENODATA; + } + + snd_ep = msm_rpc_connect_compatible(snd_rpc_ids.prog, + snd_rpc_ids.vers, 0); + if (IS_ERR(snd_ep)) { + printk(KERN_DEBUG "%s failed (compatible VERS = %ld) \ + trying again with another API\n", + __func__, snd_rpc_ids.vers); + snd_ep = + msm_rpc_connect_compatible(snd_rpc_ids.prog, + snd_rpc_ids.vers2, 0); + } + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: failed (compatible VERS = %ld)\n", + __func__, snd_rpc_ids.vers2); + snd_ep = NULL; + return -EAGAIN; + } + return 0; +} + +int msm_snd_rpc_close(void) +{ + int rc = 0; + + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: snd handle unavailable, rc = %ld\n", + __func__, PTR_ERR(snd_ep)); + return -EAGAIN; + } + + rc = msm_rpc_close(snd_ep); + snd_ep = NULL; + + if (rc < 0) { + printk(KERN_ERR "%s: close rpc failed! rc = %d\n", + __func__, rc); + return -EAGAIN; + } else + printk(KERN_INFO "rpc close success\n"); + + return rc; +} + +static int snd_msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_start_req { + struct rpc_request_hdr hdr; + uint32_t rpc_snd_device; + uint32_t snd_mute_ear_mute; + uint32_t snd_mute_mic_mute; + uint32_t callback_ptr; + uint32_t client_data; + } req; + + snd_rpc_ids.device = (int)ucontrol->value.integer.value[0]; + + if (ucontrol->value.integer.value[1] > 1) + ucontrol->value.integer.value[1] = 1; + if (ucontrol->value.integer.value[2] > 1) + ucontrol->value.integer.value[2] = 1; + + req.hdr.type = 0; + req.hdr.rpc_vers = 2; + + req.rpc_snd_device = cpu_to_be32(snd_rpc_ids.device); + req.snd_mute_ear_mute = + cpu_to_be32((int)ucontrol->value.integer.value[1]); + req.snd_mute_mic_mute = + cpu_to_be32((int)ucontrol->value.integer.value[2]); + req.callback_ptr = -1; + req.client_data = cpu_to_be32(0); + + req.hdr.prog = snd_rpc_ids.prog; + req.hdr.vers = snd_rpc_ids.vers; + + rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_snd_device , + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n", + __func__, rc); + } else { + printk(KERN_INFO "snd device connected\n"); + snd_mute_ear_mute = ucontrol->value.integer.value[1]; + snd_mute_mic_mute = ucontrol->value.integer.value[2]; + printk(KERN_ERR "%s: snd_mute_ear_mute =%d, snd_mute_mic_mute = %d\n", + __func__, snd_mute_ear_mute, snd_mute_mic_mute); + } + + return rc; +} + +static int snd_msm_device_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Device/Volume */ + + /* + * The number of devices supported is 37 (0 to 36) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 36; + return 0; +} + +static int snd_msm_device_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_vol_req { + struct rpc_request_hdr hdr; + uint32_t device; + uint32_t method; + uint32_t volume; + uint32_t cb_func; + uint32_t client_data; + } req; + + snd_rpc_ids.device = (int)ucontrol->value.integer.value[0]; + + if ((ucontrol->value.integer.value[1] < 0) || + (ucontrol->value.integer.value[1] > 6)) { + pr_err("Device volume should be in range of 1 to 6\n"); + return -EINVAL; + } + if ((ucontrol->value.integer.value[0] > 36) || + (ucontrol->value.integer.value[0] < 0)) { + pr_err("Device range supported is 0 to 36\n"); + return -EINVAL; + } + + req.device = cpu_to_be32((int)ucontrol->value.integer.value[0]); + req.method = cpu_to_be32(0); + req.volume = cpu_to_be32((int)ucontrol->value.integer.value[1]); + req.cb_func = -1; + req.client_data = cpu_to_be32(0); + + rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_device_vol , + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n", + __func__, rc); + } else { + printk(KERN_ERR "%s: device [%d] volume set to [%d]\n", + __func__, (int)ucontrol->value.integer.value[0], + (int)ucontrol->value.integer.value[1]); + } + + return rc; +} + +/* Supported range -50dB to 18dB */ +static const DECLARE_TLV_DB_LINEAR(db_scale_linear, -5000, 1800); + +#define MSM_EXT(xname, xindex, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +#define MSM_EXT_TLV(xname, xindex, fp_info, fp_get, fp_put, addr, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE), \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, .tlv.p = tlv_array, \ + .private_value = addr, \ +} + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT_TLV("PCM Playback Volume", 0, snd_msm_volume_info, \ + snd_msm_volume_get, snd_msm_volume_put, 0, db_scale_linear), + MSM_EXT("device", 0, snd_msm_device_info, snd_msm_device_get, \ + snd_msm_device_put, 0), + MSM_EXT("Device Volume", 0, snd_msm_device_vol_info, NULL, \ + snd_msm_device_vol_put, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + + pr_err("msm_soc: ALSA MSM Mixer Setting\n"); + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], NULL)); + if (err < 0) + return err; + } + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + + mutex_init(&the_locks.lock); + mutex_init(&the_locks.write_lock); + mutex_init(&the_locks.read_lock); + spin_lock_init(&the_locks.read_dsp_lock); + spin_lock_init(&the_locks.write_dsp_lock); + spin_lock_init(&the_locks.mixer_lock); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + msm_vol_ctl.volume = MSM_PLAYBACK_DEFAULT_VOLUME; + msm_vol_ctl.pan = MSM_PLAYBACK_DEFAULT_PAN; + + ret = msm_new_mixer(codec); + if (ret < 0) { + pr_err("msm_soc: ALSA MSM Mixer Fail\n"); + } + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + ret = msm_snd_rpc_connect(); + snd_mute_ear_mute = 0; + snd_mute_mic_mute = 0; + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + msm_snd_rpc_close(); + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7k-pcm.c b/sound/soc/msm/msm7k-pcm.c new file mode 100644 index 000000000000..841bfb81cd21 --- /dev/null +++ b/sound/soc/msm/msm7k-pcm.c @@ -0,0 +1,699 @@ +/* linux/sound/soc/msm/msm7k-pcm.c + * + * Copyright (c) 2008-2009, 2012 The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" + +#define SND_DRIVER "snd_msm" +#define MAX_PCM_DEVICES SNDRV_CARDS +#define MAX_PCM_SUBSTREAMS 1 + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +int copy_count; + +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); + + +static unsigned convert_dsp_samp_index(unsigned index) +{ + switch (index) { + case 48000: + return AUDREC_CMD_SAMP_RATE_INDX_48000; + case 44100: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + case 32000: + return AUDREC_CMD_SAMP_RATE_INDX_32000; + case 24000: + return AUDREC_CMD_SAMP_RATE_INDX_24000; + case 22050: + return AUDREC_CMD_SAMP_RATE_INDX_22050; + case 16000: + return AUDREC_CMD_SAMP_RATE_INDX_16000; + case 12000: + return AUDREC_CMD_SAMP_RATE_INDX_12000; + case 11025: + return AUDREC_CMD_SAMP_RATE_INDX_11025; + case 8000: + return AUDREC_CMD_SAMP_RATE_INDX_8000; + default: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + } +} + +static unsigned convert_samp_rate(unsigned hz) +{ + switch (hz) { + case 48000: + return RPC_AUD_DEF_SAMPLE_RATE_48000; + case 44100: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + case 32000: + return RPC_AUD_DEF_SAMPLE_RATE_32000; + case 24000: + return RPC_AUD_DEF_SAMPLE_RATE_24000; + case 22050: + return RPC_AUD_DEF_SAMPLE_RATE_22050; + case 16000: + return RPC_AUD_DEF_SAMPLE_RATE_16000; + case 12000: + return RPC_AUD_DEF_SAMPLE_RATE_12000; + case 11025: + return RPC_AUD_DEF_SAMPLE_RATE_11025; + case 8000: + return RPC_AUD_DEF_SAMPLE_RATE_8000; + default: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + } +} + +static struct snd_pcm_hardware msm_pcm_playback_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = 4800 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 4800, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_capture_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_CAPTURE_SIZE, + .period_bytes_min = CAPTURE_SIZE, + .period_bytes_max = CAPTURE_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void msm_pcm_enqueue_data(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned int period_size; + + pr_debug("prtd->out_tail =%d mmap_flag=%d\n", + prtd->out_tail, prtd->mmap_flag); + period_size = snd_pcm_lib_period_bytes(substream); + alsa_dsp_send_buffer(prtd, prtd->out_tail, period_size); + prtd->out_tail ^= 1; + ++copy_count; + prtd->period++; + if (unlikely(prtd->period >= runtime->periods)) + prtd->period = 0; + +} + +static void playback_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->playback_substream); + if (prtd->mmap_flag) { + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) + return; + if (!prtd->stopped) + msm_pcm_enqueue_data(prtd->playback_substream); + else + prtd->out_needed++; + } +} + +static void capture_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->capture_substream); +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->out_sample_rate = runtime->rate; + prtd->out_channel_mode = runtime->channels; + + if (prtd->enabled | !(prtd->mmap_flag)) + return 0; + + prtd->data = substream->dma_buffer.area; + prtd->phys = substream->dma_buffer.addr; + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + + prtd->out[0].used = prtd->pcm_count; + prtd->out[1].used = prtd->pcm_count; + + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct audmgr_config cfg; + int rc; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = convert_samp_rate(runtime->rate); + prtd->samp_rate_index = convert_dsp_samp_index(runtime->rate); + prtd->channel_mode = (runtime->channels - 1); + prtd->buffer_size = prtd->channel_mode ? STEREO_DATA_SIZE : \ + MONO_DATA_SIZE; + + if (prtd->enabled == 1) + return 0; + + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + + cfg.tx_rate = convert_samp_rate(runtime->rate); + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(prtd->audpre)) { + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + if (msm_adsp_enable(prtd->audrec)) { + msm_adsp_disable(prtd->audpre); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + prtd->enabled = 1; + alsa_rec_dsp_enable(prtd, 1); + + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned long flag = 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + if (!prtd->out_needed) { + prtd->stopped = 0; + break; + } + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running == 1) { + if (prtd->stopped == 1) { + prtd->stopped = 0; + prtd->period = 0; + if (prtd->pcm_irq_pos == 0) { + prtd->out_tail = 0; + msm_pcm_enqueue_data( + prtd->playback_substream); + prtd->out_needed--; + } else { + prtd->out_tail = 1; + msm_pcm_enqueue_data( + prtd->playback_substream); + prtd->out_needed--; + } + if (prtd->out_needed) { + prtd->out_tail ^= 1; + msm_pcm_enqueue_data( + prtd->playback_substream); + prtd->out_needed--; + } + } + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + prtd->stopped = 1; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos == prtd->pcm_size) + prtd->pcm_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0, rc1 = 0, rc2 = 0; + int fbytes = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + int monofbytes = 0; + char *bufferp = NULL; + + fbytes = frames_to_bytes(runtime, frames); + monofbytes = fbytes / 2; + if (runtime->channels == 2) { + rc = alsa_buffer_read(prtd, buf, fbytes, NULL); + } else { + bufferp = buf; + rc1 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + bufferp = buf + monofbytes ; + rc2 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + rc = rc1 + rc2; + } + prtd->pcm_buf_pos += fbytes; + return rc; +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + alsa_audrec_disable(prtd); + audmgr_close(&prtd->audmgr); + msm_adsp_put(prtd->audrec); + msm_adsp_put(prtd->audpre); + kfree(prtd); + + return 0; +} + +struct msm_audio_event_callbacks snd_msm_audio_ops = { + .playback = playback_event_handler, + .capture = capture_event_handler, +}; + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + return ret; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->playback_substream = substream; + prtd->eos_ack = 0; + ret = msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + prtd->capture_substream = substream; + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + goto out; + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd->ops = &snd_msm_audio_ops; + prtd->out[0].used = BUF_INVALID_LEN; + prtd->out_head = 1; /* point to second buffer on startup */ + runtime->private_data = prtd; + + ret = alsa_adsp_configure(prtd); + if (ret) + goto out; + copy_count = 0; + return 0; + + out: + kfree(prtd); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 1; + int fbytes = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + rc = alsa_send_buffer(prtd, buf, fbytes, NULL); + ++copy_count; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + return rc; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + int rc = 0; + + pr_debug("%s()\n", __func__); + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + if (prtd->enabled) + rc = wait_event_interruptible(the_locks.eos_wait, + prtd->eos_ack); + + alsa_audio_disable(prtd); + audmgr_close(&prtd->audmgr); + kfree(prtd); + + return 0; +} + + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; + +} + +int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->out_head = 0; /* point to First buffer on startup */ + prtd->mmap_flag = 1; + runtime->dma_bytes = snd_pcm_lib_period_bytes(substream)*2; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + if (!stream) + size = PLAYBACK_DMASZ; + else + size = CAPTURE_DMASZ; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + ret = pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + + ret = pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + msm_pcm_free_dma_buffers(pcm); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-dai.c b/sound/soc/msm/msm7kv2-dai.c new file mode 100644 index 000000000000..00a96fb33076 --- /dev/null +++ b/sound/soc/msm/msm7kv2-dai.c @@ -0,0 +1,149 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, The Linux Foundation. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.c. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm7kv2-pcm.h" + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static int asoc_msm_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int asoc_msm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_msm_codec_driver = { + .probe = asoc_msm_codec_probe, + .remove = asoc_msm_codec_remove, + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_msm_cpu_driver = { + .probe = asoc_msm_cpu_probe, + .remove = asoc_msm_cpu_remove, + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_msm_codec_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_codec_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_msm_cpu_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_cpu_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-dsp.c b/sound/soc/msm/msm7kv2-dsp.c new file mode 100644 index 000000000000..8484a8f3cca0 --- /dev/null +++ b/sound/soc/msm/msm7kv2-dsp.c @@ -0,0 +1,633 @@ +/* Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm7kv2-pcm.h" + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_id & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_id & 0x0000FFFF),\ + cmd, len) + +static int alsa_dsp_read_buffer(struct msm_audio *audio, + uint32_t read_cnt); +static void alsa_get_dsp_frames(struct msm_audio *prtd); +static int alsa_in_param_config(struct msm_audio *audio); + +static int alsa_in_mem_config(struct msm_audio *audio); +static int alsa_in_enc_config(struct msm_audio *audio, int enable); + +int intcnt; +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__ ((packed)); + +void alsa_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct msm_audio *prtd = data; + struct buffer *frame; + unsigned long flag = 0; + + MM_DBG("\n"); + switch (id) { + case AUDPP_MSG_HOST_PCM_INTF_MSG: { + unsigned id = msg[3]; + unsigned idx = msg[4] - 1; + + MM_DBG("HOST_PCM id %d idx %d\n", id, idx); + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + MM_ERR("bogus id\n"); + break; + } + if (idx > 1) { + MM_ERR("bogus buffer idx\n"); + break; + } + + /* Update with actual sent buffer size */ + if (prtd->out[idx].used != BUF_INVALID_LEN) + prtd->pcm_irq_pos += prtd->out[idx].used; + + if (prtd->pcm_irq_pos > prtd->pcm_size) + prtd->pcm_irq_pos = prtd->pcm_count; + + if (prtd->ops->playback) + prtd->ops->playback(prtd); + + if (prtd->mmap_flag) + break; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running) { + prtd->out[idx].used = 0; + frame = prtd->out + prtd->out_tail; + if (frame->used) { + alsa_dsp_send_buffer( + prtd, prtd->out_tail, frame->used); + /* Reset eos_ack flag to avoid stale + * PCMDMAMISS been considered + */ + prtd->eos_ack = 0; + prtd->out_tail ^= 1; + } else { + prtd->out_needed++; + } + wake_up(&the_locks.write_wait); + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + MM_INFO("PCMDMAMISSED %d\n", msg[0]); + prtd->eos_ack++; + MM_DBG("PCMDMAMISSED Count per Buffer %d\n", prtd->eos_ack); + wake_up(&the_locks.eos_wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + prtd->out_needed = 0; + prtd->running = 1; + audpp_dsp_set_vol_pan(prtd->session_id, &prtd->vol_pan, + POPP); + audpp_route_stream(prtd->session_id, + msm_snddev_route_dec(prtd->session_id)); + audio_dsp_out_enable(prtd, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + prtd->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + default: + MM_DBG("UNKNOWN (%d)\n", id); + } +} + +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct msm_audio *prtd = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG\n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) + alsa_in_param_config(prtd); + else { /* Encoder disable success */ + prtd->running = 0; + alsa_in_record_config(prtd, 0); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG\n"); + alsa_in_mem_config(prtd); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG\n"); + wake_up(&the_locks.enable_wait); + break; + } + default: + MM_DBG("Unknown Event id %d\n", id); + } +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct msm_audio *prtd = data; + unsigned long flag = 0; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("AUDREC_CMD_MEM_CFG_DONE_MSG\n"); + prtd->running = 1; + alsa_in_record_config(prtd, 1); + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + prtd->stopped = 1; + wake_up(&the_locks.read_wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + MM_DBG("AUDREC_UP_PACKET_READY_MSG\n"); + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + alsa_get_dsp_frames(prtd); + ++intcnt; + if (prtd->channel_mode == 1) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } else if ((prtd->channel_mode == 0) && (intcnt % 2 == 0)) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } + break; + } + default: + MM_DBG("Unknown Event id %d\n", id); + } +} + +struct msm_adsp_ops alsa_audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + +int alsa_audio_configure(struct msm_audio *prtd) +{ + if (prtd->enabled) + return 0; + + MM_DBG("\n"); + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->out_weight = 100; + if (audpp_enable(-1, alsa_dsp_event, prtd)) { + MM_ERR("audpp_enable() failed\n"); + return -ENODEV; + } + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + if (audpreproc_enable(prtd->session_id, + &audpreproc_dsp_event, prtd)) { + MM_ERR("audpreproc_enable failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(prtd->audrec)) { + MM_ERR("msm_adsp_enable(audrec) enable failed\n"); + audpreproc_disable(prtd->session_id, prtd); + return -ENODEV; + } + alsa_in_enc_config(prtd, 1); + + } + prtd->enabled = 1; + return 0; +} +EXPORT_SYMBOL(alsa_audio_configure); + +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag = 0; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int ret = 0; + + MM_DBG("\n"); + mutex_lock(&the_locks.write_lock); + while (count > 0) { + frame = prtd->out + prtd->out_head; + ret = wait_event_interruptible(the_locks.write_wait, + (frame->used == 0) + || (prtd->stopped)); + if (ret < 0) + break; + if (prtd->stopped) { + ret = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + ret = -EFAULT; + break; + } + frame->used = xfer; + prtd->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + frame = prtd->out + prtd->out_tail; + if (frame->used && prtd->out_needed) { + alsa_dsp_send_buffer(prtd, prtd->out_tail, + frame->used); + /* Reset eos_ack flag to avoid stale + * PCMDMAMISS been considered + */ + prtd->eos_ack = 0; + prtd->out_tail ^= 1; + prtd->out_needed--; + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + } + mutex_unlock(&the_locks.write_lock); + if (buf > start) + return buf - start; + return ret; +} +EXPORT_SYMBOL(alsa_send_buffer); + +int alsa_audio_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + MM_DBG("\n"); + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + audio_dsp_out_enable(prtd, 0); + wake_up(&the_locks.write_wait); + audpp_disable(-1, prtd); + prtd->out_needed = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audio_disable); + +int alsa_audrec_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + prtd->enabled = 0; + alsa_in_enc_config(prtd, 0); + wake_up(&the_locks.read_wait); + msm_adsp_disable(prtd->audrec); + prtd->out_needed = 0; + audpreproc_disable(prtd->session_id, prtd); + } + return 0; +} +EXPORT_SYMBOL(alsa_audrec_disable); + +static int alsa_in_enc_config(struct msm_audio *prtd, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + cmd.stream_id = prtd->session_id; + + if (enable) + cmd.audrec_enc_type = prtd->type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int alsa_in_param_config(struct msm_audio *prtd) +{ + struct audpreproc_audrec_cmd_parm_cfg_wav cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = prtd->session_id; + + cmd.aud_rec_samplerate_idx = prtd->samp_rate; + cmd.aud_rec_stereo_mode = prtd->channel_mode; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +int alsa_in_record_config(struct msm_audio *prtd, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = prtd->session_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + cmd.source_mix_mask = prtd->source; + if (prtd->session_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int alsa_in_mem_config(struct msm_audio *prtd) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) prtd->data; + int n; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = prtd->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = prtd->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + prtd->in[n].data = data + 4; + data += (4 + (prtd->channel_mode ? 2048 : 1024)); + MM_DBG("0x%8x\n", (int)(prtd->in[n].data - 8)); + } + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audrec_send_audrecqueue(prtd, &cmd, sizeof(cmd)); +} + +int audio_dsp_out_enable(struct msm_audio *prtd, int yes) +{ + struct audpp_cmd_pcm_intf cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = prtd->session_id; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = prtd->out[0].addr; + cmd.write_buf1MSW = prtd->out[0].addr >> 16; + cmd.write_buf1_len = prtd->out[0].size; + cmd.write_buf2LSW = prtd->out[1].addr; + cmd.write_buf2MSW = prtd->out[1].addr >> 16; + if (prtd->out[1].used) + cmd.write_buf2_len = prtd->out[1].used; + else + cmd.write_buf2_len = prtd->out[1].size; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = prtd->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = prtd->out_sample_rate; + cmd.channel_mode = prtd->out_channel_mode; + } + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + void *data; + uint32_t index; + uint32_t size; + int ret = 0; + + mutex_lock(&the_locks.read_lock); + while (count > 0) { + ret = wait_event_interruptible(the_locks.read_wait, + (prtd->in_count > 0) + || prtd->stopped || + prtd->abort); + + if (ret < 0) + break; + + if (prtd->stopped) { + ret = -EBUSY; + break; + } + + if (prtd->abort) { + MM_DBG(" prtd->abort !\n"); + ret = -EPERM; /* Not permitted due to abort */ + break; + } + + index = prtd->in_tail; + data = (uint8_t *) prtd->in[index].data; + size = prtd->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + ret = -EFAULT; + break; + } + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (index != prtd->in_tail) { + /* overrun: data is invalid, we need to retry */ + spin_unlock_irqrestore(&the_locks.read_dsp_lock, + flag); + continue; + } + prtd->in[index].size = 0; + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + prtd->in_count--; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + count -= size; + buf += size; + } else { + break; + } + } + mutex_unlock(&the_locks.read_lock); + return ret; +} +EXPORT_SYMBOL(alsa_buffer_read); + +int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len) +{ + struct audpp_cmd_pcm_intf_send_buffer cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = prtd->session_id; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int alsa_dsp_read_buffer(struct msm_audio *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} + +static void alsa_get_dsp_frames(struct msm_audio *prtd) +{ + struct audio_frame *frame; + uint32_t index = 0; + unsigned long flag; + + if (prtd->type == ENC_TYPE_WAV) { + index = prtd->in_head; + + frame = + (void *)(((char *)prtd->in[index].data) - sizeof(*frame)); + + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->in[index].size = frame->bytes; + MM_DBG("frame = %08x\n", (unsigned int) frame); + MM_DBG("prtd->in[index].size = %08x\n", + (unsigned int) prtd->in[index].size); + + prtd->in_head = (prtd->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (prtd->in_head == prtd->in_tail) + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + else + prtd->in_count++; + + prtd->pcm_irq_pos += frame->bytes; + alsa_dsp_read_buffer(prtd, prtd->dsp_cnt++); + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + wake_up(&the_locks.read_wait); + } +} diff --git a/sound/soc/msm/msm7kv2-pcm.c b/sound/soc/msm/msm7kv2-pcm.c new file mode 100644 index 000000000000..ac42e9ada5d3 --- /dev/null +++ b/sound/soc/msm/msm7kv2-pcm.c @@ -0,0 +1,774 @@ +/* Copyright (c) 2008-2010, The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm7kv2-pcm.h" +#include +#include + +#define HOSTPCM_STREAM_ID 5 + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +int copy_count; + +static struct snd_pcm_hardware msm_pcm_playback_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_PLAYBACK_SIZE, + .period_bytes_min = BUFSZ, + .period_bytes_max = BUFSZ, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_capture_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_CAPTURE_SIZE, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 4, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; +static void alsa_out_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct msm_audio *prtd = (struct msm_audio *) private_data; + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + prtd->source |= (0x1 << evt_payload->routing_id); + if (prtd->running == 1 && prtd->enabled == 1) + audpp_route_stream(prtd->session_id, prtd->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + prtd->source &= ~(0x1 << evt_payload->routing_id); + if (prtd->running == 1 && prtd->enabled == 1) + audpp_route_stream(prtd->session_id, prtd->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + prtd->vol_pan.volume = evt_payload->session_vol; + MM_DBG("AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + prtd->vol_pan.volume); + if (prtd->running) + audpp_set_volume_and_pan(prtd->session_id, + prtd->vol_pan.volume, + 0, POPP); + break; + default: + MM_DBG("Unknown Event\n"); + break; + } +} + +static void alsa_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct msm_audio *prtd = (struct msm_audio *) private_data; + MM_DBG("evt_id = 0x%8x\n", evt_id); + + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + prtd->source |= (0x1 << evt_payload->routing_id); + + if ((prtd->running == 1) && (prtd->enabled == 1)) + alsa_in_record_config(prtd, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + prtd->source &= ~(0x1 << evt_payload->routing_id); + + if (!prtd->running || !prtd->enabled) + break; + + /* Turn off as per source */ + if (prtd->source) + alsa_in_record_config(prtd, 1); + else + /* Turn off all */ + alsa_in_record_config(prtd, 0); + + break; + } + case AUDDEV_EVT_FREQ_CHG: { + MM_DBG("Encoder Driver got sample rate change event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if (prtd->running == 1) { + /* Stop Recording sample rate does not match + with device sample rate */ + if (evt_payload->freq_info.sample_rate != + prtd->samp_rate) { + alsa_in_record_config(prtd, 0); + prtd->abort = 1; + wake_up(&the_locks.read_wait); + } + } + break; + } + default: + MM_DBG("Unknown Event\n"); + break; + } +} + +static void msm_pcm_enqueue_data(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned int period_size; + + MM_DBG("prtd->out_tail =%d mmap_flag=%d\n", + prtd->out_tail, prtd->mmap_flag); + period_size = snd_pcm_lib_period_bytes(substream); + alsa_dsp_send_buffer(prtd, prtd->out_tail, period_size); + prtd->out_tail ^= 1; + ++copy_count; + prtd->period++; + if (unlikely(prtd->period >= runtime->periods)) + prtd->period = 0; + +} + +static void event_handler(void *data) +{ + struct msm_audio *prtd = data; + MM_DBG("\n"); + snd_pcm_period_elapsed(prtd->substream); + if (prtd->mmap_flag) { + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) + return; + if (!prtd->stopped) + msm_pcm_enqueue_data(prtd->substream); + else + prtd->out_needed++; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + MM_DBG("\n"); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + if (prtd->enabled) + return 0; + + MM_DBG("\n"); + /* rate and channels are sent to audio driver */ + prtd->out_sample_rate = runtime->rate; + prtd->out_channel_mode = runtime->channels; + prtd->data = prtd->substream->dma_buffer.area; + prtd->phys = prtd->substream->dma_buffer.addr; + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + + if (prtd->enabled | !(prtd->mmap_flag)) + return 0; + + prtd->out[0].used = prtd->pcm_count; + prtd->out[1].used = prtd->pcm_count; + + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + uint32_t freq; + MM_DBG("\n"); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->type = ENC_TYPE_WAV; + prtd->samp_rate = runtime->rate; + prtd->channel_mode = (runtime->channels - 1); + prtd->buffer_size = prtd->channel_mode ? STEREO_DATA_SIZE : \ + MONO_DATA_SIZE; + + if (prtd->enabled) + return 0; + + freq = prtd->samp_rate; + + prtd->data = prtd->substream->dma_buffer.area; + prtd->phys = prtd->substream->dma_buffer.addr; + MM_DBG("prtd->data =%08x\n", (unsigned int)prtd->data); + MM_DBG("prtd->phys =%08x\n", (unsigned int)prtd->phys); + + mutex_lock(&the_locks.lock); + ret = alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + if (ret) + return ret; + ret = wait_event_interruptible(the_locks.enable_wait, + prtd->running != 0); + MM_DBG("state prtd->running = %d ret = %d\n", prtd->running, ret); + + if (prtd->running == 0) + ret = -ENODEV; + else + ret = 0; + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned long flag = 0; + int ret = 0; + + MM_DBG("\n"); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + if (!prtd->out_needed) { + prtd->stopped = 0; + break; + } + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running == 1) { + if (prtd->stopped == 1) { + prtd->stopped = 0; + prtd->period = 0; + if (prtd->pcm_irq_pos == 0) { + prtd->out_tail = 0; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } else { + prtd->out_tail = 1; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } + if (prtd->out_needed) { + prtd->out_tail ^= 1; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } + } + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + prtd->stopped = 1; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct msm_audio_event_callbacks snd_msm_audio_ops = { + .playback = event_handler, + .capture = event_handler, +}; + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + int i = 0; + int session_attrb, sessionid; + + MM_DBG("\n"); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + return ret; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->opened) { + kfree(prtd); + return -EBUSY; + } + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->eos_ack = 0; + prtd->session_id = HOSTPCM_STREAM_ID; + prtd->device_events = AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_STREAM_VOL_CHG | + AUDDEV_EVT_DEV_RLS; + prtd->source = msm_snddev_route_dec(prtd->session_id); + MM_ERR("Register device event listener\n"); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_DEC, prtd->session_id, + alsa_out_listener, (void *) prtd); + if (ret) { + MM_ERR("failed to register device event listener\n"); + goto evt_error; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + session_attrb = ENC_TYPE_WAV; + sessionid = audpreproc_aenc_alloc(session_attrb, + &prtd->module_name, &prtd->queue_id); + if (sessionid < 0) { + MM_ERR("AUDREC not available\n"); + kfree(prtd); + return -ENODEV; + } + prtd->session_id = sessionid; + MM_DBG("%s\n", prtd->module_name); + ret = msm_adsp_get(prtd->module_name, &prtd->audrec, + &alsa_audrec_adsp_ops, prtd); + if (ret < 0) { + audpreproc_aenc_free(prtd->session_id); + kfree(prtd); + return -ENODEV; + } + + prtd->abort = 0; + prtd->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + prtd->source = msm_snddev_route_enc(prtd->session_id); + MM_ERR("Register device event listener\n"); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_ENC, prtd->session_id, + alsa_in_listener, (void *) prtd); + if (ret) { + MM_ERR("failed to register device event listener\n"); + audpreproc_aenc_free(prtd->session_id); + msm_adsp_put(prtd->audrec); + goto evt_error; + } + } + prtd->substream = substream; + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + MM_ERR("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + MM_ERR("snd_pcm_hw_constraint_integer failed\n"); + + prtd->ops = &snd_msm_audio_ops; + prtd->out[0].used = BUF_INVALID_LEN; + prtd->out[1].used = 0; + prtd->out_head = 1; /* point to second buffer on startup */ + prtd->out_tail = 0; + prtd->dsp_cnt = 0; + prtd->in_head = 0; + prtd->in_tail = 0; + prtd->in_count = 0; + prtd->out_needed = 0; + for (i = 0; i < FRAME_NUM; i++) { + prtd->in[i].size = 0; + prtd->in[i].read = 0; + } + prtd->vol_pan.volume = 0x2000; + prtd->vol_pan.pan = 0x0; + prtd->opened = 1; + runtime->private_data = prtd; + + copy_count = 0; + return 0; +evt_error: + kfree(prtd); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + MM_DBG("%d\n", fbytes); + ret = alsa_send_buffer(prtd, buf, fbytes, NULL); + ++copy_count; + prtd->pcm_buf_pos += fbytes; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + ret = alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + int ret = 0; + + MM_DBG("\n"); + if ((!prtd->mmap_flag) && prtd->enabled) { + ret = wait_event_interruptible(the_locks.eos_wait, + (!(prtd->out[0].used) && !(prtd->out[1].used))); + + if (ret < 0) + goto done; + } + + /* PCM DMAMISS message is sent only once in + * hpcm interface. So, wait for buffer complete + * and teos flag. + */ + if (prtd->enabled) + ret = wait_event_interruptible(the_locks.eos_wait, + prtd->eos_ack); + +done: + alsa_audio_disable(prtd); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, prtd->session_id); + kfree(prtd); + + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0, rc1 = 0, rc2 = 0; + int fbytes = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + int monofbytes = 0; + char *bufferp = NULL; + + if (prtd->abort) + return -EPERM; + + fbytes = frames_to_bytes(runtime, frames); + MM_DBG("%d\n", fbytes); + monofbytes = fbytes / 2; + if (runtime->channels == 2) { + ret = alsa_buffer_read(prtd, buf, fbytes, NULL); + } else { + bufferp = buf; + rc1 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + bufferp = buf + monofbytes ; + rc2 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + ret = rc1 + rc2; + } + prtd->pcm_buf_pos += fbytes; + MM_DBG("prtd->pcm_buf_pos =%d, prtd->mmap_flag =%d\n", + prtd->pcm_buf_pos, prtd->mmap_flag); + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + + MM_DBG("\n"); + ret = msm_snddev_withdraw_freq(prtd->session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, prtd->session_id); + prtd->abort = 0; + wake_up(&the_locks.enable_wait); + alsa_audrec_disable(prtd); + audpreproc_aenc_free(prtd->session_id); + msm_adsp_put(prtd->audrec); + kfree(prtd); + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + MM_DBG("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + if (prtd->pcm_irq_pos == prtd->pcm_size) + prtd->pcm_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->out_head = 0; /* point to First buffer on startup */ + prtd->mmap_flag = 1; + runtime->dma_bytes = snd_pcm_lib_period_bytes(substream)*2; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int pcm_preallocate_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + if (!stream) + size = PLAYBACK_DMASZ; + else + size = CAPTURE_DMASZ; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + msm_pcm_free_buffers(pcm); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-pcm.h b/sound/soc/msm/msm7kv2-pcm.h new file mode 100644 index 000000000000..bcf6b4d42ea7 --- /dev/null +++ b/sound/soc/msm/msm7kv2-pcm.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define CAPTURE_DMASZ (FRAME_SIZE * FRAME_NUM) + +#define BUFSZ (960 * 5) +#define PLAYBACK_DMASZ (BUFSZ * 2) + +#define MSM_PLAYBACK_DEFAULT_VOLUME 0 /* 0dB */ +#define MSM_PLAYBACK_DEFAULT_PAN 0 + +#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 +#define MAX_BUFFER_PLAYBACK_SIZE \ + PLAYBACK_DMASZ +/* 2048 frames (Mono), 1024 frames (Stereo) */ +#define CAPTURE_SIZE 4096 +#define MAX_BUFFER_CAPTURE_SIZE (4096*4) +#define MAX_PERIOD_SIZE BUFSZ +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define MAX_DB (16) +#define MIN_DB (-50) +#define PCMPLAYBACK_DECODERID 5 + +/* 0xFFFFFFFF Indicates not to be used for audio data copy */ +#define BUF_INVALID_LEN 0xFFFFFFFF +#define EVENT_MSG_ID ((uint16_t)~0) + +#define AUDDEC_DEC_PCM 0 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +extern int copy_count; +extern int intcnt; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + struct mutex lock; + struct mutex write_lock; + struct mutex read_lock; + spinlock_t read_dsp_lock; + spinlock_t write_dsp_lock; + spinlock_t mixer_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio_event_callbacks { + /* event is called from interrupt context when a message + * arrives from the DSP. + */ + void (*playback)(void *); + void (*capture)(void *); +}; + + +struct msm_audio { + struct buffer out[2]; + struct buffer_rec in[8]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + atomic_t out_bytes; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct snd_pcm_substream *substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + uint16_t source; /* Encoding source bit mask */ + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + struct msm_adsp_module *audplay; + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + + uint16_t session_id; + uint32_t out_bits; /* bits per sample */ + const char *module_name; + unsigned queue_id; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + uint32_t device_events; /* device events interested in */ + int abort; /* set when error, like sample rate mismatch */ + + /* audpre settings */ + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + + struct msm_audio_event_callbacks *ops; + + int dir; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int eos_ack; + int mmap_flag; + int period; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + + + +/* platform data */ +extern int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len); +extern int audio_dsp_out_enable(struct msm_audio *prtd, int yes); +extern struct snd_soc_platform_driver msm_soc_platform; + +extern int audrec_encoder_config(struct msm_audio *prtd); +extern int alsa_audrec_disable(struct msm_audio *prtd); +extern int alsa_audio_configure(struct msm_audio *prtd); +extern int alsa_audio_disable(struct msm_audio *prtd); +extern int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos); +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos); +extern struct msm_adsp_ops alsa_audrec_adsp_ops; +extern int alsa_in_record_config(struct msm_audio *prtd, int enable); +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm7x30.c b/sound/soc/msm/msm7x30.c new file mode 100644 index 000000000000..664baabf3a92 --- /dev/null +++ b/sound/soc/msm/msm7x30.c @@ -0,0 +1,1004 @@ +/* Copyright (c) 2008-2011, The Linux Foundation. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm7kv2-pcm.h" +#include +#include +#include +#include + +static struct platform_device *msm_audio_snd_device; +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); +static struct snd_kcontrol_new snd_msm_controls[]; + +char snddev_name[AUDIO_DEV_CTL_MAX_DEV][44]; +#define MSM_MAX_VOLUME 0x2000 +#define MSM_VOLUME_STEP ((MSM_MAX_VOLUME+17)/100) /* 17 added to avoid + more deviation */ +#define LOOPBACK_ENABLE 0x1 +#define LOOPBACK_DISABLE 0x0 + +static int device_index; /* Count of Device controls */ +static int simple_control; /* Count of simple controls*/ +static int src_dev; +static int dst_dev; +static int loopback_status; + + +static int msm_scontrol_count_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + return 0; +} + +static int msm_scontrol_count_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = simple_control; + return 0; +} + +static int msm_v_call_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int msm_v_call_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_call_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int start = ucontrol->value.integer.value[0]; + if (start) + broadcast_event(AUDDEV_EVT_START_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + else + broadcast_event(AUDDEV_EVT_END_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + return 0; +} + +static int msm_v_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; +} + +static int msm_v_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + return msm_set_voice_mute(dir, mute); +} + +static int msm_v_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + return msm_set_voice_vol(dir, volume); +} + +static int msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Volume and 10-base multiply factor*/ + uinfo->value.integer.min = 0; + + /* limit the muliply factor to 4 decimal digit */ + uinfo->value.integer.max = 1000000; + return 0; +} +static int msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + int session_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + int factor = ucontrol->value.integer.value[2]; + u32 session_mask = 0; + + + if (factor > 10000) + return -EINVAL; + + if ((volume < 0) || (volume/factor > 100)) + return -EINVAL; + + volume = (MSM_VOLUME_STEP * volume); + + /* Convert back to original decimal point by removing the 10-base factor + * and discard the fractional portion + */ + + volume = volume/factor; + + if (volume > MSM_MAX_VOLUME) + volume = MSM_MAX_VOLUME; + + /* Only Decoder volume control supported */ + session_mask = (0x1 << (session_id) << (8 * ((int)AUDDEV_CLNT_DEC-1))); + msm_vol_ctl.volume = volume; + MM_DBG("session_id %d, volume %d", session_id, volume); + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, DEVICE_IGNORE, + session_mask); + + return ret; +} + +static int msm_voice_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_voice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + uint32_t rx_dev_id; + uint32_t tx_dev_id; + struct msm_snddev_info *rx_dev_info; + struct msm_snddev_info *tx_dev_info; + int set = ucontrol->value.integer.value[2]; + u32 session_mask; + + if (!set) + return -EPERM; + /* Rx Device Routing */ + rx_dev_id = ucontrol->value.integer.value[0]; + rx_dev_info = audio_dev_ctrl_find_dev(rx_dev_id); + + if (IS_ERR(rx_dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(rx_dev_info); + return rc; + } + + if (!(rx_dev_info->capability & SNDDEV_CAP_RX)) { + MM_ERR("First Dev is supposed to be RX\n"); + return -EFAULT; + } + + MM_DBG("route cfg %d STREAM_VOICE_RX type\n", + rx_dev_id); + + msm_set_voc_route(rx_dev_info, AUDIO_ROUTE_STREAM_VOICE_RX, + rx_dev_id); + + session_mask = 0x1 << (8 * ((int)AUDDEV_CLNT_VOC-1)); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, rx_dev_id, session_mask); + + + /* Tx Device Routing */ + tx_dev_id = ucontrol->value.integer.value[1]; + tx_dev_info = audio_dev_ctrl_find_dev(tx_dev_id); + + if (IS_ERR(tx_dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(tx_dev_info); + return rc; + } + + if (!(tx_dev_info->capability & SNDDEV_CAP_TX)) { + MM_ERR("Second Dev is supposed to be Tx\n"); + return -EFAULT; + } + + MM_DBG("route cfg %d %d type\n", + tx_dev_id, AUDIO_ROUTE_STREAM_VOICE_TX); + + msm_set_voc_route(tx_dev_info, AUDIO_ROUTE_STREAM_VOICE_TX, + tx_dev_id); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, tx_dev_id, session_mask); + + if (rx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, rx_dev_id, session_mask); + + if (tx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, tx_dev_id, session_mask); + + return rc; +} + +static int msm_voice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + struct msm_snddev_info *dst_dev_info; + struct msm_snddev_info *src_dev_info; + int tx_freq = 0; + int rx_freq = 0; + u32 set_freq = 0; + + set = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + MM_INFO("device %s set %d\n", dev_info->name, set); + + if (set) { + if (!dev_info->opened) { + set_freq = dev_info->sample_rate; + if (!msm_device_is_voice(route_cfg.dev_id)) { + msm_get_voc_freq(&tx_freq, &rx_freq); + if (dev_info->capability & SNDDEV_CAP_TX) + set_freq = tx_freq; + + if (set_freq == 0) + set_freq = dev_info->sample_rate; + } else + set_freq = dev_info->sample_rate; + + + MM_ERR("device freq =%d\n", set_freq); + rc = dev_info->dev_ops.set_freq(dev_info, set_freq); + if (rc < 0) { + MM_ERR("device freq failed!\n"); + return rc; + } + dev_info->set_sample_rate = rc; + rc = 0; + rc = dev_info->dev_ops.open(dev_info); + if (rc < 0) { + MM_ERR("Enabling %s failed", dev_info->name); + return rc; + } + dev_info->opened = 1; + broadcast_event(AUDDEV_EVT_DEV_RDY, route_cfg.dev_id, + SESSION_IGNORE); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, SESSION_IGNORE); + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if ((dst_dev_info->opened) && + (src_dev_info->opened)) { + pr_debug("%d: Enable afe_loopback\n", + __LINE__); + afe_ext_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 1; + } + } + } + } else { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + MM_ERR("Snd device failed close!\n"); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + SESSION_IGNORE); + } + if (loopback_status == 1) { + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + pr_debug("%d: Disable afe_loopback\n", + __LINE__); + afe_ext_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 0; + } + } + } + + } + return rc; +} + +static int msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + + ucontrol->value.integer.value[0] = dev_info->copp_id; + ucontrol->value.integer.value[1] = dev_info->capability; + + return 0; +} + +static int msm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int enc_freq = 0; + int requested_freq = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + int session_id = ucontrol->value.integer.value[0]; + int set = ucontrol->value.integer.value[2]; + u32 session_mask = 0; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + + if (ucontrol->id.numid == 2) + route_cfg.stream_type = AUDIO_ROUTE_STREAM_PLAYBACK; + else + route_cfg.stream_type = AUDIO_ROUTE_STREAM_REC; + + MM_DBG("route cfg %d %d type for popp %d set value %d\n", + route_cfg.dev_id, route_cfg.stream_type, session_id, set); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + if (route_cfg.stream_type == AUDIO_ROUTE_STREAM_PLAYBACK) { + rc = msm_snddev_set_dec(session_id, dev_info->copp_id, set); + session_mask = + (0x1 << (session_id) << (8 * ((int)AUDDEV_CLNT_DEC-1))); + if (!set) { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + session_mask); + + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + } + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, + session_mask); + } + } + } else { + rc = msm_snddev_set_enc(session_id, dev_info->copp_id, set); + session_mask = + (0x1 << (session_id)) << (8 * ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + enc_freq = msm_snddev_get_enc_freq(session_id); + requested_freq = enc_freq; + if (enc_freq > 0) { + rc = msm_snddev_request_freq(&enc_freq, + session_id, + SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d" + "sample rate requested %d\n", + enc_freq, requested_freq); + if ((rc <= 0) || (enc_freq != requested_freq)) { + MM_DBG("msm_snddev_withdraw_freq\n"); + rc = msm_snddev_withdraw_freq + (session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + broadcast_event(AUDDEV_EVT_FREQ_CHG, + route_cfg.dev_id, + SESSION_IGNORE); + } + } + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, + session_mask); + } + } + } + + if (rc < 0) { + MM_ERR("device could not be assigned!\n"); + return -EFAULT; + } + + return rc; +} + +static int msm_device_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_device_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + ucontrol->value.integer.value[0] = dev_info->dev_volume; + + return 0; +} + +static int msm_device_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + MM_DBG("dev_id = %d, volume = %d\n", dev_id, volume); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + MM_ERR("audio_dev_ctrl_find_dev failed. %ld\n", + PTR_ERR(dev_info)); + return rc; + } + + MM_DBG("dev_name = %s dev_id = %d, volume = %d\n", + dev_info->name, dev_id, volume); + + if (dev_info->dev_ops.set_device_volume) + rc = dev_info->dev_ops.set_device_volume(dev_info, volume); + else { + MM_INFO("device %s does not support device volume " + "control.", dev_info->name); + return -EPERM; + } + + return rc; +} + +static int msm_reset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0; + return 0; +} + +static int msm_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + MM_DBG("Resetting all devices\n"); + return msm_reset_all_device(); +} + + +static int msm_dual_mic_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + /*Max value is decided based on MAX ENC sessions*/ + uinfo->value.integer.max = MAX_AUDREC_SESSIONS - 1; + return 0; +} + +static int msm_dual_mic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int enc_session_id = ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = + msm_get_dual_mic_config(enc_session_id); + MM_DBG("session id = %d, config = %ld\n", enc_session_id, + ucontrol->value.integer.value[1]); + return 0; +} + +static int msm_dual_mic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int enc_session_id = ucontrol->value.integer.value[0]; + int dual_mic_config = ucontrol->value.integer.value[1]; + MM_DBG("session id = %d, config = %d\n", enc_session_id, + dual_mic_config); + return msm_set_dual_mic_config(enc_session_id, dual_mic_config); +} + +static int msm_device_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int msm_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dev_id = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + struct msm_snddev_info *dev_info; + int afe_dev_id = 0; + int volume = 0x4000; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id %d\n", dev_id); + return PTR_ERR(dev_info); + } + + if (dev_info->capability & SNDDEV_CAP_RX) + return -EPERM; + + MM_DBG("Muting device id %d(%s)\n", dev_id, dev_info->name); + + if (dev_info->copp_id == 0) + afe_dev_id = AFE_HW_PATH_CODEC_TX; + if (dev_info->copp_id == 1) + afe_dev_id = AFE_HW_PATH_AUXPCM_TX; + if (dev_info->copp_id == 2) + afe_dev_id = AFE_HW_PATH_MI2S_TX; + if (mute) + volume = 0; + afe_device_volume_ctrl(afe_dev_id, volume); + return 0; +} + +static int msm_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_snddev_info *src_dev_info = NULL; /* TX device */ + struct msm_snddev_info *dst_dev_info = NULL; /* RX device */ + int dst_dev_id = ucontrol->value.integer.value[0]; + int src_dev_id = ucontrol->value.integer.value[1]; + int set = ucontrol->value.integer.value[2]; + + pr_debug("%s: set=%d\n", __func__, set); + + dst_dev_info = audio_dev_ctrl_find_dev(dst_dev_id); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + if (!(dst_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("Destination device %d is not RX device\n", + dst_dev_id); + return -EFAULT; + } + + src_dev_info = audio_dev_ctrl_find_dev(src_dev_id); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if (!(src_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("Source device %d is not TX device\n", src_dev_id); + return -EFAULT; + } + + if (set) { + pr_debug("%s:%d:Enabling AFE_Loopback\n", __func__, __LINE__); + src_dev = src_dev_id; + dst_dev = dst_dev_id; + loopback_status = 1; + if ((dst_dev_info->opened) && (src_dev_info->opened)) + afe_ext_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } else { + pr_debug("%s:%d:Disabling AFE_Loopback\n", __func__, __LINE__); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + loopback_status = 0; + afe_ext_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } + return 0; +} + +static struct snd_kcontrol_new snd_dev_controls[AUDIO_DEV_CTL_MAX_DEV]; + +static int snd_dev_ctl_index(int idx) +{ + struct msm_snddev_info *dev_info; + + dev_info = audio_dev_ctrl_find_dev(idx); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + return PTR_ERR(dev_info); + } + if (sizeof(dev_info->name) <= 44) + sprintf(&snddev_name[idx][0] , "%s", dev_info->name); + + snd_dev_controls[idx].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_dev_controls[idx].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_dev_controls[idx].name = &snddev_name[idx][0]; + snd_dev_controls[idx].index = idx; + snd_dev_controls[idx].info = msm_device_info; + snd_dev_controls[idx].get = msm_device_get; + snd_dev_controls[idx].put = msm_device_put; + snd_dev_controls[idx].private_value = 0; + return 0; + +} + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT("Count", msm_scontrol_count_info, msm_scontrol_count_get, \ + NULL, 0), + MSM_EXT("Stream", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Record", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Voice", msm_voice_info, msm_voice_get, \ + msm_voice_put, 0), + MSM_EXT("Volume", msm_volume_info, msm_volume_get, \ + msm_volume_put, 0), + MSM_EXT("VoiceVolume", msm_v_volume_info, msm_v_volume_get, \ + msm_v_volume_put, 0), + MSM_EXT("VoiceMute", msm_v_mute_info, msm_v_mute_get, \ + msm_v_mute_put, 0), + MSM_EXT("Voice Call", msm_v_call_info, msm_v_call_get, \ + msm_v_call_put, 0), + MSM_EXT("Device_Volume", msm_device_volume_info, + msm_device_volume_get, msm_device_volume_put, 0), + MSM_EXT("Reset", msm_reset_info, + msm_reset_get, msm_reset_put, 0), + MSM_EXT("DualMic Switch", msm_dual_mic_info, + msm_dual_mic_get, msm_dual_mic_put, 0), + MSM_EXT("Device_Mute", msm_device_mute_info, + msm_device_mute_get, msm_device_mute_put, 0), + MSM_EXT("Sound Device Loopback", msm_loopback_info, + msm_loopback_get, msm_loopback_put, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + int dev_cnt; + + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], NULL)); + if (err < 0) + MM_ERR("ERR adding ctl\n"); + } + dev_cnt = msm_snddev_devcount(); + + for (idx = 0; idx < dev_cnt; idx++) { + if (!snd_dev_ctl_index(idx)) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_dev_controls[idx], NULL)); + if (err < 0) + MM_ERR("ERR adding ctl\n"); + } else + return 0; + } + simple_control = ARRAY_SIZE(snd_msm_controls); + device_index = simple_control + 1; + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + ret = msm_new_mixer(codec); + if (ret < 0) + MM_ERR("msm_soc: ALSA MSM Mixer Fail\n"); + + mutex_init(&the_locks.lock); + mutex_init(&the_locks.write_lock); + mutex_init(&the_locks.read_lock); + spin_lock_init(&the_locks.read_dsp_lock); + spin_lock_init(&the_locks.write_dsp_lock); + spin_lock_init(&the_locks.mixer_lock); + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +#ifdef CONFIG_SND_MVS_SOC +{ + .name = "MSM Primary Voip", + .stream_name = "MVS", + .cpu_dai_name = "mvs-cpu-dai.0", + .platform_name = "msm-mvs-audio.0", + .codec_name = "mvs-codec-dai.0", + .codec_dai_name = "mvs-codec-dai", +}, +#endif +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660-apq-wm8903.c b/sound/soc/msm/msm8660-apq-wm8903.c new file mode 100644 index 000000000000..f93a2695bd07 --- /dev/null +++ b/sound/soc/msm/msm8660-apq-wm8903.c @@ -0,0 +1,725 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wm8903.h" + +#define MSM_GPIO_CLASS_D0_EN 80 +#define MSM_GPIO_CLASS_D1_EN 81 + +#define MSM_CDC_MIC_I2S_MCLK 108 + +static int msm8660_spk_func; +static int msm8660_headset_func; +static int msm8660_headphone_func; + +static struct clk *mic_bit_clk; +static struct clk *spkr_osr_clk; +static struct clk *spkr_bit_clk; +static struct clk *wm8903_mclk; + +static int rx_hw_param_status; +static int tx_hw_param_status; +/* Platform specific logic */ + +enum { + GET_ERR, + SET_ERR, + ENABLE_ERR, + NONE +}; + +enum { + FUNC_OFF, + FUNC_ON, +}; + +static struct wm8903_vdd { + struct regulator *reg_id; + const char *name; + u32 voltage; +} wm8903_vdds[] = { + { NULL, "8058_l16", 1800000 }, + { NULL, "8058_l0", 1200000 }, + { NULL, "8058_s3", 1800000 }, +}; + +static void classd_amp_pwr(int enable) +{ + int rc; + + pr_debug("%s, enable = %d\n", __func__, enable); + if (enable) { + /* currently external PA isn't used for LINEOUTL */ + rc = gpio_request(MSM_GPIO_CLASS_D0_EN, "CLASSD0_EN"); + if (rc) { + pr_err("%s: spkr PA gpio %d request failed\n", + __func__, MSM_GPIO_CLASS_D0_EN); + return; + } + gpio_direction_output(MSM_GPIO_CLASS_D0_EN, 1); + gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 1); + rc = gpio_request(MSM_GPIO_CLASS_D1_EN, "CLASSD1_EN"); + if (rc) { + pr_err("%s: spkr PA gpio %d request failed\n", + __func__, MSM_GPIO_CLASS_D1_EN); + return; + } + gpio_direction_output(MSM_GPIO_CLASS_D1_EN, 1); + gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 1); + } else { + gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 0); + gpio_free(MSM_GPIO_CLASS_D0_EN); + + gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 0); + gpio_free(MSM_GPIO_CLASS_D1_EN); + } +} + +static void extern_poweramp_on(void) +{ + pr_debug("%s: enable stereo spkr amp\n", __func__); + classd_amp_pwr(1); +} + +static void extern_poweramp_off(void) +{ + pr_debug("%s: disable stereo spkr amp\n", __func__); + classd_amp_pwr(0); +} + +static int msm8660_wm8903_powerup(void) +{ + int rc = 0, index, stage = NONE; + struct wm8903_vdd *vdd = NULL; + + for (index = 0; index < ARRAY_SIZE(wm8903_vdds); index++) { + vdd = &wm8903_vdds[index]; + vdd->reg_id = regulator_get(NULL, vdd->name); + if (IS_ERR(vdd->reg_id)) { + pr_err("%s: Unable to get %s\n", __func__, vdd->name); + stage = GET_ERR; + rc = -ENODEV; + break; + } + + rc = regulator_set_voltage(vdd->reg_id, + vdd->voltage, vdd->voltage); + if (rc) { + pr_err("%s: unable to set %s voltage to %dV\n", + __func__, vdd->name, vdd->voltage); + stage = SET_ERR; + break; + } + + rc = regulator_enable(vdd->reg_id); + if (rc) { + pr_err("%s:failed to enable %s\n", __func__, vdd->name); + stage = ENABLE_ERR; + break; + } + } + + if (index != ARRAY_SIZE(wm8903_vdds)) { + if (stage != GET_ERR) { + vdd = &wm8903_vdds[index]; + regulator_put(vdd->reg_id); + vdd->reg_id = NULL; + } + + while (index--) { + vdd = &wm8903_vdds[index]; + regulator_disable(vdd->reg_id); + regulator_put(vdd->reg_id); + vdd->reg_id = NULL; + } + } + + return rc; +} + +static void msm8660_wm8903_powerdown(void) +{ + int index = ARRAY_SIZE(wm8903_vdds); + struct wm8903_vdd *vdd = NULL; + + while (index--) { + vdd = &wm8903_vdds[index]; + if (vdd->reg_id) { + regulator_disable(vdd->reg_id); + regulator_put(vdd->reg_id); + } + } +} + +static int msm8660_wm8903_enable_mclk(int enable) +{ + int ret = 0; + + if (enable) { + ret = gpio_request(MSM_CDC_MIC_I2S_MCLK, "I2S_Clock"); + if (ret != 0) { + pr_err("%s: failed to request GPIO\n", __func__); + return ret; + } + + wm8903_mclk = clk_get_sys(NULL, "i2s_mic_osr_clk"); + if (IS_ERR(wm8903_mclk)) { + pr_err("Failed to get i2s_mic_osr_clk\n"); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + return IS_ERR(wm8903_mclk); + } + /* Master clock OSR 256 */ + clk_set_rate(wm8903_mclk, 48000 * 256); + ret = clk_prepare_enable(wm8903_mclk); + if (ret != 0) { + pr_err("Unable to enable i2s_mic_osr_clk\n"); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + clk_put(wm8903_mclk); + return ret; + } + } else { + if (wm8903_mclk) { + clk_disable_unprepare(wm8903_mclk); + clk_put(wm8903_mclk); + gpio_free(MSM_CDC_MIC_I2S_MCLK); + wm8903_mclk = NULL; + } + } + + return ret; +} + +static int msm8660_wm8903_prepare(void) +{ + int ret = 0; + + ret = msm8660_wm8903_powerup(); + if (ret) { + pr_err("Unable to powerup wm8903\n"); + return ret; + } + + ret = msm8660_wm8903_enable_mclk(1); + if (ret) { + pr_err("Unable to enable mclk to wm8903\n"); + return ret; + } + + return ret; +} + +static void msm8660_wm8903_unprepare(void) +{ + msm8660_wm8903_powerdown(); + msm8660_wm8903_enable_mclk(0); +} + +static int msm8660_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int rate = params_rate(params), ret = 0; + + pr_debug("Enter %s rate = %d\n", __func__, rate); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (rx_hw_param_status) + return 0; + /* wm8903 run @ LRC*256 */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256, + SND_SOC_CLOCK_IN); + snd_soc_dai_digital_mute(codec_dai, 0); + if (ret < 0) { + pr_err("can't set rx codec clk configuration\n"); + return ret; + } + clk_set_rate(wm8903_mclk, rate * 256); + /* set as slave mode CPU */ + clk_set_rate(spkr_bit_clk, 0); + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + rx_hw_param_status++; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (tx_hw_param_status) + return 0; + clk_set_rate(wm8903_mclk, rate * 256); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("can't set tx codec clk configuration\n"); + return ret; + } + clk_set_rate(mic_bit_clk, 0); + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + tx_hw_param_status++; + } + return 0; +} + +static int msm8660_i2s_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("Enter %s\n", __func__); + /* ON Dragonboard, I2S between wm8903 and CPU is shared by + * CODEC_SPEAKER and CODEC_MIC therefore CPU only can operate + * as input SLAVE mode. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* config WM8903 in Mater mode */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_I2S); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + /* config CPU in SLAVE mode */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret != 0) { + pr_err("cpu_dai set_fmt error\n"); + return ret; + } + spkr_osr_clk = clk_get_sys(NULL, "i2s_spkr_osr_clk"); + if (IS_ERR(spkr_osr_clk)) { + pr_err("Failed to get i2s_spkr_osr_clk\n"); + return PTR_ERR(spkr_osr_clk); + } + clk_set_rate(spkr_osr_clk, 48000 * 256); + ret = clk_prepare_enable(spkr_osr_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_spkr_osr_clk\n"); + clk_put(spkr_osr_clk); + return ret; + } + spkr_bit_clk = clk_get_sys(NULL, "i2s_spkr_bit_clk"); + if (IS_ERR(spkr_bit_clk)) { + pr_err("Failed to get i2s_spkr_bit_clk\n"); + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + return PTR_ERR(spkr_bit_clk); + } + clk_set_rate(spkr_bit_clk, 0); + ret = clk_prepare_enable(spkr_bit_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_spkr_bit_clk\n"); + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + clk_put(spkr_bit_clk); + return ret; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* config WM8903 in Mater mode */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_I2S); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + /* config CPU in SLAVE mode */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret != 0) { + pr_err("codec_dai set_fmt error\n"); + return ret; + } + + mic_bit_clk = clk_get_sys(NULL, "i2s_mic_bit_clk"); + if (IS_ERR(mic_bit_clk)) { + pr_err("Failed to get i2s_mic_bit_clk\n"); + return PTR_ERR(mic_bit_clk); + } + clk_set_rate(mic_bit_clk, 0); + ret = clk_prepare_enable(mic_bit_clk); + if (ret != 0) { + pr_err("Unable to enable i2s_mic_bit_clk\n"); + clk_put(mic_bit_clk); + return ret; + } + } + return ret; +} + +static void msm8660_i2s_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("Enter %s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_hw_param_status = 0; + rx_hw_param_status = 0; + if (spkr_bit_clk) { + clk_disable_unprepare(spkr_bit_clk); + clk_put(spkr_bit_clk); + spkr_bit_clk = NULL; + } + if (spkr_osr_clk) { + clk_disable_unprepare(spkr_osr_clk); + clk_put(spkr_osr_clk); + spkr_osr_clk = NULL; + } + if (mic_bit_clk) { + clk_disable_unprepare(mic_bit_clk); + clk_put(mic_bit_clk); + mic_bit_clk = NULL; + } + } +} + +static void msm8660_ext_control(struct snd_soc_codec *codec) +{ + /* set the enpoints to their new connetion states */ + if (msm8660_spk_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + + /* set the enpoints to their new connetion states */ + if (msm8660_headset_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Headset Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headset Jack"); + + /* set the enpoints to their new connetion states */ + if (msm8660_headphone_func == FUNC_ON) + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(&codec->dapm); +} + +static int msm8660_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_spk_func; + return 0; +} + +static int msm8660_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8660_spk_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_spk_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_get_hs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_headset_func; + return 0; +} + +static int msm8660_set_hs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8660_headset_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_headset_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_get_hph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm8660_headphone_func; + return 0; +} + +static int msm8660_set_hph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8660_headphone_func == ucontrol->value.integer.value[0]) + return 0; + + msm8660_headphone_func = ucontrol->value.integer.value[0]; + msm8660_ext_control(codec); + return 1; +} + +static int msm8660_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + extern_poweramp_on(); + else + extern_poweramp_off(); + return 0; +} + +static struct snd_soc_ops machine_ops = { + .startup = msm8660_i2s_startup, + .shutdown = msm8660_i2s_shutdown, + .hw_params = msm8660_i2s_hw_params, +}; + +static const struct snd_soc_dapm_widget msm8660_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", msm8660_spkramp_event), + SND_SOC_DAPM_MIC("Headset Jack", NULL), + SND_SOC_DAPM_MIC("Headphone Jack", NULL), + /* to fix a bug in wm8903.c, where audio doesn't function + * after suspend/resume + */ + SND_SOC_DAPM_SUPPLY("CLK_SYS_ENA", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Match with wm8903 codec line out pin */ + {"Ext Spk", NULL, "LINEOUTL"}, + {"Ext Spk", NULL, "LINEOUTR"}, + /* Headset connects to IN3L with Bias */ + {"IN3L", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Jack"}, + /* Headphone connects to IN3R with Bias */ + {"IN3R", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headphone Jack"}, + {"ADCL", NULL, "CLK_SYS_ENA"}, + {"ADCR", NULL, "CLK_SYS_ENA"}, + {"DACL", NULL, "CLK_SYS_ENA"}, + {"DACR", NULL, "CLK_SYS_ENA"}, +}; + +static const char *cmn_status[] = {"Off", "On"}; +static const struct soc_enum msm8660_enum[] = { + SOC_ENUM_SINGLE_EXT(2, cmn_status), +}; + +static const struct snd_kcontrol_new wm8903_msm8660_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8660_enum[0], msm8660_get_spk, + msm8660_set_spk), + SOC_ENUM_EXT("Headset Function", msm8660_enum[0], msm8660_get_hs, + msm8660_set_hs), + SOC_ENUM_EXT("Headphone Function", msm8660_enum[0], msm8660_get_hph, + msm8660_set_hph), +}; + +static int msm8660_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int err; + + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + snd_soc_dapm_enable_pin(&codec->dapm, "CLK_SYS_ENA"); + + err = snd_soc_add_controls(codec, wm8903_msm8660_controls, + ARRAY_SIZE(wm8903_msm8660_controls)); + if (err < 0) + return err; + + snd_soc_dapm_new_controls(&codec->dapm, msm8660_dapm_widgets, + ARRAY_SIZE(msm8660_dapm_widgets)); + + snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(&codec->dapm); + + return 0; +} + +static int pri_i2s_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + rate->min = rate->max = 48000; + return 0; +} +/* + * LPA Needs only RX BE DAI links. + * Hence define seperate BE list for lpa + */ +static const char *lpa_mm_be[] = { + LPASS_BE_PRI_I2S_RX, +}; + +static struct snd_soc_dsp_link lpa_fe_media = { + .supported_be = lpa_mm_be, + .num_be = ARRAY_SIZE(lpa_mm_be), + .fe_playback_channels = 2, + .fe_capture_channels = 1, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; + +static const char *mm1_be[] = { + LPASS_BE_PRI_I2S_RX, + LPASS_BE_PRI_I2S_TX, + LPASS_BE_HDMI, +}; + +static struct snd_soc_dsp_link fe_media = { + .supported_be = mm1_be, + .num_be = ARRAY_SIZE(mm1_be), + .fe_playback_channels = 2, + .fe_capture_channels = 1, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, SND_SOC_DSP_TRIGGER_POST}, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8660_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8660 Media", + .stream_name = "MultiMedia", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8660 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_PRI_I2S_RX, + .stream_name = "Primary I2S Playback", + .cpu_dai_name = "msm-dai-q6.0", + .platform_name = "msm-pcm-routing", + .codec_name = "wm8903-codec.3-001a", + .codec_dai_name = "wm8903-hifi", + .no_pcm = 1, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .ops = &machine_ops, + .init = &msm8660_audrx_init, + .be_id = MSM_BACKEND_DAI_PRI_I2S_RX + }, + { + .name = LPASS_BE_PRI_I2S_TX, + .stream_name = "Primary I2S Capture", + .cpu_dai_name = "msm-dai-q6.1", + .platform_name = "msm-pcm-routing", + .codec_name = "wm8903-codec.3-001a", + .codec_dai_name = "wm8903-hifi", + .no_pcm = 1, + .ops = &machine_ops, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .be_id = MSM_BACKEND_DAI_PRI_I2S_TX + }, + /* LPA frontend DAI link*/ + { + .name = "MSM8660 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .dsp_link = &lpa_fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* HDMI backend DAI link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_codec = 1, + .no_pcm = 1, + .be_hw_params_fixup = pri_i2s_be_hw_params_fixup, + .be_id = MSM_BACKEND_DAI_HDMI_RX + }, +}; + +struct snd_soc_card snd_soc_card_msm8660 = { + .name = "msm8660-snd-card", + .dai_link = msm8660_dai, + .num_links = ARRAY_SIZE(msm8660_dai), +}; + +static struct platform_device *msm_snd_device; + +static int __init msm_audio_init(void) +{ + int ret = 0; + + if (machine_is_msm8x60_dragon()) { + /* wm8903 audio codec needs to power up and mclk existing + before it's probed */ + ret = msm8660_wm8903_prepare(); + if (ret) { + pr_err("failed to prepare wm8903 audio codec\n"); + return ret; + } + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + msm8660_wm8903_unprepare(); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm8660); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + msm8660_wm8903_unprepare(); + return ret; + } + } + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + msm8660_wm8903_unprepare(); + platform_device_unregister(msm_snd_device); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8660"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660.c b/sound/soc/msm/msm8660.c new file mode 100644 index 000000000000..6d7d2eec3849 --- /dev/null +++ b/sound/soc/msm/msm8660.c @@ -0,0 +1,342 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8660-pcm.h" +#include "../codecs/timpani.h" + +#define PM8058_GPIO_BASE NR_MSM_GPIOS +#define PM8901_GPIO_BASE (PM8058_GPIO_BASE + \ + PM8058_GPIOS + PM8058_MPPS) +#define PM8901_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + PM8901_GPIO_BASE) +#define GPIO_EXPANDER_GPIO_BASE \ + (PM8901_GPIO_BASE + PM8901_MPPS) + +static struct clk *rx_osr_clk; +static struct clk *rx_bit_clk; +static struct clk *tx_osr_clk; +static struct clk *tx_bit_clk; + +static int rx_hw_param_status; +static int tx_hw_param_status; +/* Platform specific logic */ + +static int timpani_rx_route_enable(void) +{ + int ret = 0; + pr_debug("%s\n", __func__); + ret = gpio_request(109, "I2S_Clock"); + if (ret != 0) { + pr_err("%s: I2s clk gpio 109 request" + "failed\n", __func__); + return ret; + } + return ret; +} + +static int timpani_rx_route_disable(void) +{ + int ret = 0; + pr_debug("%s\n", __func__); + gpio_free(109); + return ret; +} + + +#define GPIO_CLASS_D1_EN (GPIO_EXPANDER_GPIO_BASE + 0) +#define PM8901_MPP_3 (2) /* PM8901 MPP starts from 0 */ +static void config_class_d1_gpio(int enable) +{ + int rc; + + if (enable) { + rc = gpio_request(GPIO_CLASS_D1_EN, "CLASSD1_EN"); + if (rc) { + pr_err("%s: spkr pamp gpio %d request" + "failed\n", __func__, GPIO_CLASS_D1_EN); + return; + } + gpio_direction_output(GPIO_CLASS_D1_EN, 1); + gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 1); + } else { + gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 0); + gpio_free(GPIO_CLASS_D1_EN); + } +} + +static void config_class_d0_gpio(int enable) +{ + int rc; + + if (enable) { + rc = pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 1); + + if (rc) { + pr_err("%s: CLASS_D0_EN failed\n", __func__); + return; + } + + rc = gpio_request(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), + "CLASSD0_EN"); + + if (rc) { + pr_err("%s: spkr pamp gpio pm8901 mpp3 request" + "failed\n", __func__); + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + return; + } + + gpio_direction_output(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); + gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); + + } else { + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 0); + gpio_free(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3)); + } +} + +static void timpani_poweramp_on(void) +{ + + pr_debug("%s: enable stereo spkr amp\n", __func__); + timpani_rx_route_enable(); + config_class_d0_gpio(1); + config_class_d1_gpio(1); +} + +static void timpani_poweramp_off(void) +{ + + pr_debug("%s: disable stereo spkr amp\n", __func__); + timpani_rx_route_disable(); + config_class_d0_gpio(0); + config_class_d1_gpio(0); +} + +static int msm8660_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (rx_hw_param_status) + return 0; + clk_set_rate(rx_osr_clk, rate * 256); + rx_hw_param_status++; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (tx_hw_param_status) + return 0; + clk_set_rate(tx_osr_clk, rate * 256); + tx_hw_param_status++; + } + return 0; +} + +static int msm8660_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rx_osr_clk = clk_get(NULL, "i2s_spkr_osr_clk"); + if (IS_ERR(rx_osr_clk)) { + pr_debug("Failed to get i2s_spkr_osr_clk\n"); + return PTR_ERR(rx_osr_clk); + } + /* Master clock OSR 256 */ + /* Initially set to Lowest sample rate Needed */ + clk_set_rate(rx_osr_clk, 8000 * 256); + ret = clk_prepare_enable(rx_osr_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_spkr_osr_clk\n"); + clk_put(rx_osr_clk); + return ret; + } + rx_bit_clk = clk_get(NULL, "i2s_spkr_bit_clk"); + if (IS_ERR(rx_bit_clk)) { + pr_debug("Failed to get i2s_spkr_bit_clk\n"); + clk_disable_unprepare(rx_osr_clk); + clk_put(rx_osr_clk); + return PTR_ERR(rx_bit_clk); + } + clk_set_rate(rx_bit_clk, 8); + ret = clk_prepare_enable(rx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_spkr_bit_clk\n"); + clk_put(rx_bit_clk); + clk_disable_unprepare(rx_osr_clk); + clk_put(rx_osr_clk); + return ret; + } + timpani_poweramp_on(); + msleep(30); + /* End of platform specific logic */ + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_osr_clk = clk_get(NULL, "i2s_mic_osr_clk"); + if (IS_ERR(tx_osr_clk)) { + pr_debug("Failed to get i2s_mic_osr_clk\n"); + return PTR_ERR(tx_osr_clk); + } + /* Master clock OSR 256 */ + clk_set_rate(tx_osr_clk, 8000 * 256); + ret = clk_prepare_enable(tx_osr_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_mic_osr_clk\n"); + clk_put(tx_osr_clk); + return ret; + } + tx_bit_clk = clk_get(NULL, "i2s_mic_bit_clk"); + if (IS_ERR(tx_bit_clk)) { + pr_debug("Failed to get i2s_mic_bit_clk\n"); + clk_disable_unprepare(tx_osr_clk); + clk_put(tx_osr_clk); + return PTR_ERR(tx_bit_clk); + } + clk_set_rate(tx_bit_clk, 8); + ret = clk_prepare_enable(tx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_mic_bit_clk\n"); + clk_put(tx_bit_clk); + clk_disable_unprepare(tx_osr_clk); + clk_put(tx_osr_clk); + return ret; + } + msm_snddev_enable_dmic_power(); + msleep(30); + } + return ret; +} + +/* + * TODO: rx/tx_hw_param_status should be a counter in the below code + * when driver starts supporting mutisession else setting it to 0 + * will stop audio in all sessions. + */ +static void msm8660_shutdown(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rx_hw_param_status = 0; + timpani_poweramp_off(); + msleep(30); + if (rx_bit_clk) { + clk_disable_unprepare(rx_bit_clk); + clk_put(rx_bit_clk); + rx_bit_clk = NULL; + } + if (rx_osr_clk) { + clk_disable_unprepare(rx_osr_clk); + clk_put(rx_osr_clk); + rx_osr_clk = NULL; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_hw_param_status = 0; + msm_snddev_disable_dmic_power(); + msleep(30); + if (tx_bit_clk) { + clk_disable_unprepare(tx_bit_clk); + clk_put(tx_bit_clk); + tx_bit_clk = NULL; + } + if (tx_osr_clk) { + clk_disable_unprepare(tx_osr_clk); + clk_put(tx_osr_clk); + tx_osr_clk = NULL; + } + } +} + +static struct snd_soc_ops machine_ops = { + .startup = msm8660_startup, + .shutdown = msm8660_shutdown, + .hw_params = msm8660_hw_params, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8660_dai[] = { + { + .name = "Audio Rx", + .stream_name = "Audio Rx", + .cpu_dai = &msm_cpu_dai[0], + .codec_dai = &timpani_codec_dai[0], + .ops = &machine_ops, + }, + { + .name = "Audio Tx", + .stream_name = "Audio Tx", + .cpu_dai = &msm_cpu_dai[5], + .codec_dai = &timpani_codec_dai[1], + .ops = &machine_ops, + } +}; + +struct snd_soc_card snd_soc_card_msm8660 = { + .name = "msm8660-pcm-audio", + .dai_link = msm8660_dai, + .num_links = ARRAY_SIZE(msm8660_dai), + .platform = &msm8660_soc_platform, +}; + +/* msm_audio audio subsystem */ +static struct snd_soc_device msm_snd_devdata = { + .card = &snd_soc_card_msm8660, + .codec_dev = &soc_codec_dev_timpani, +}; + +static struct platform_device *msm_snd_device; + + +static int __init msm_audio_init(void) +{ + int ret; + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &msm_snd_devdata); + + msm_snd_devdata.dev = &msm_snd_device->dev; + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + return ret; + } + + return ret; +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_snd_device); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8660"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8930.c b/sound/soc/msm/msm8930.c new file mode 100644 index 000000000000..4b2bda91ae0b --- /dev/null +++ b/sound/soc/msm/msm8930.c @@ -0,0 +1,1274 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wcd9304.h" + +/* 8930 machine driver */ + +#define MSM8930_SPK_ON 1 +#define MSM8930_SPK_OFF 0 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define SPK_AMP_POS 0x1 +#define SPK_AMP_NEG 0x2 +#define SPKR_BOOST_GPIO 15 +#define DEFAULT_PMIC_SPK_GAIN 0x0D +#define SITAR_EXT_CLK_RATE 12288000 + +#define SITAR_MBHC_DEF_BUTTONS 8 +#define SITAR_MBHC_DEF_RLOADS 5 + +#define GPIO_AUX_PCM_DOUT 63 +#define GPIO_AUX_PCM_DIN 64 +#define GPIO_AUX_PCM_SYNC 65 +#define GPIO_AUX_PCM_CLK 66 + +static int msm8930_spk_control; +static int msm8930_slim_0_rx_ch = 1; +static int msm8930_slim_0_tx_ch = 1; +static int msm8930_pmic_spk_gain = DEFAULT_PMIC_SPK_GAIN; + +static int msm8930_ext_spk_pamp; +static int msm8930_btsco_rate = BTSCO_RATE_8KHZ; +static int msm8930_btsco_ch = 1; + +static struct clk *codec_clk; +static int clk_users; + +static int msm8930_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm8930_enable_codec_ext_clk( + struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct sitar_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = SITAR_MICBIAS2, + .mclk_cb_fn = msm8930_enable_codec_ext_clk, + .mclk_rate = SITAR_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + + +static void msm8930_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm8930_spk_control = %d", __func__, msm8930_spk_control); + if (msm8930_spk_control == MSM8930_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk left Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Left Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm8930_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_spk_control = %d", __func__, msm8930_spk_control); + ucontrol->value.integer.value[0] = msm8930_spk_control; + return 0; +} +static int msm8930_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8930_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm8930_spk_control = ucontrol->value.integer.value[0]; + msm8930_ext_control(codec); + return 1; +} + +static void msm8960_ext_spk_power_amp_on(u32 spk) +{ + int ret = 0; + + if (spk & (SPK_AMP_POS | SPK_AMP_NEG)) { + if ((msm8930_ext_spk_pamp & SPK_AMP_POS) && + (msm8930_ext_spk_pamp & SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8930_ext_spk_pamp |= spk; + + if ((msm8930_ext_spk_pamp & SPK_AMP_POS) && + (msm8930_ext_spk_pamp & SPK_AMP_NEG)) { + + if (machine_is_msm8930_mtp() + || machine_is_msm8930_fluid()) { + pr_debug("%s: Configure Speaker Boost GPIO %u", + __func__, SPKR_BOOST_GPIO); + ret = gpio_request(SPKR_BOOST_GPIO, + "SPKR_BOOST_EN"); + if (ret) { + pr_err("%s: Failed to configure speaker boost " + "gpio %u\n", __func__, SPKR_BOOST_GPIO); + return; + } + + pr_debug("%s: Enable Speaker boost gpio %u\n", + __func__, SPKR_BOOST_GPIO); + gpio_direction_output(SPKR_BOOST_GPIO, 1); + } + + pm8xxx_spk_enable(MSM8930_SPK_ON); + pr_debug("%s: slepping 4 ms after turning on external " + " Left Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (SPK_AMP_POS | SPK_AMP_NEG)) { + if (!msm8930_ext_spk_pamp) + return; + if (machine_is_msm8930_mtp() + || machine_is_msm8930_fluid()) { + pr_debug("%s: Free speaker boost gpio %u\n", + __func__, SPKR_BOOST_GPIO); + gpio_direction_output(SPKR_BOOST_GPIO, 0); + gpio_free(SPKR_BOOST_GPIO); + } + + pm8xxx_spk_enable(MSM8930_SPK_OFF); + msm8930_ext_spk_pamp = 0; + pr_debug("%s: slepping 4 ms after turning on external " + " Left Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static int msm8930_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Left Pos", 17)) + msm8960_ext_spk_power_amp_on(SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Left Neg", 17)) + msm8960_ext_spk_power_amp_on(SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } else { + if (!strncmp(w->name, "Ext Spk Left Pos", 17)) + msm8960_ext_spk_power_amp_off(SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Left Neg", 17)) + msm8960_ext_spk_power_amp_off(SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm8930_enable_codec_ext_clk( + struct snd_soc_codec *codec, int enable, + bool dapm) +{ + pr_debug("%s: enable = %d\n", __func__, enable); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users != 1) + return 0; + + if (codec_clk) { + clk_set_rate(codec_clk, SITAR_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + sitar_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Sitar MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + } else { + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) + return 0; + clk_users--; + if (!clk_users) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + sitar_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } + return 0; +} + +static int msm8930_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return msm8930_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return msm8930_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget msm8930_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm8930_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Left Pos", msm8930_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Left Neg", msm8930_spkramp_event), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + {"MIC BIAS1 Internal1", NULL, "MCLK"}, + {"MIC BIAS2 Internal1", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Left Pos", NULL, "LINEOUT1"}, + {"Ext Spk Left Neg", NULL, "LINEOUT2"}, + + /* Headset Mic */ + {"AMIC2", NULL, "MIC BIAS2 Internal1"}, + {"MIC BIAS2 Internal1", NULL, "Headset Mic"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "ANCLeft Headset Mic"}, + + {"AMIC3", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "ANCRight Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC1 Input on Sitar codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Back top MIC on Fluid. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC2 Input on Sitar codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Sitar codec. + */ + {"DMIC3", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Sitar codec. + */ + {"DMIC4", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic4"}, + + +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm8930_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm8930_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm8930_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_slim_0_rx_ch = %d\n", __func__, + msm8930_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm8930_slim_0_rx_ch - 1; + return 0; +} + +static int msm8930_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8930_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8930_slim_0_rx_ch = %d\n", __func__, + msm8930_slim_0_rx_ch); + return 1; +} + +static int msm8930_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_slim_0_tx_ch = %d\n", __func__, + msm8930_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm8930_slim_0_tx_ch - 1; + return 0; +} + +static int msm8930_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8930_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8930_slim_0_tx_ch = %d\n", __func__, + msm8930_slim_0_tx_ch); + return 1; +} + +static int msm8930_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_btsco_rate = %d", __func__, msm8930_btsco_rate); + ucontrol->value.integer.value[0] = msm8930_btsco_rate; + return 0; +} + +static int msm8930_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8930_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm8930_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm8930_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm8930_btsco_rate = %d\n", __func__, msm8930_btsco_rate); + return 0; +} + +static const char *pmic_spk_gain_text[] = { + "NEG_6_DB", "NEG_4_DB", "NEG_2_DB", "ZERO_DB", "POS_2_DB", "POS_4_DB", + "POS_6_DB", "POS_8_DB", "POS_10_DB", "POS_12_DB", "POS_14_DB", + "POS_16_DB", "POS_18_DB", "POS_20_DB", "POS_22_DB", "POS_24_DB" +}; + +static const struct soc_enum msm8960_pmic_spk_gain_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(pmic_spk_gain_text), + pmic_spk_gain_text), +}; + +static int msm8930_pmic_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8930_pmic_spk_gain = %d\n", __func__, + msm8930_pmic_spk_gain); + ucontrol->value.integer.value[0] = msm8930_pmic_spk_gain; + return 0; +} + +static int msm8930_pmic_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + msm8930_pmic_spk_gain = ucontrol->value.integer.value[0]; + ret = pm8xxx_spk_gain(msm8930_pmic_spk_gain); + pr_debug("%s: msm8930_pmic_spk_gain = %d" + " ucontrol->value.integer.value[0] = %d\n", __func__, + msm8930_pmic_spk_gain, + (int) ucontrol->value.integer.value[0]); + return ret; +} + +static const struct snd_kcontrol_new sitar_msm8930_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8930_enum[0], msm8930_get_spk, + msm8930_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm8930_enum[1], + msm8930_slim_0_rx_ch_get, msm8930_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm8930_enum[2], + msm8930_slim_0_tx_ch_get, msm8930_slim_0_tx_ch_put), + SOC_ENUM_EXT("PMIC SPK Gain", msm8960_pmic_spk_gain_enum[0], + msm8930_pmic_gain_get, msm8930_pmic_gain_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm8930_btsco_enum[0], + msm8930_btsco_rate_get, msm8930_btsco_rate_put), +}; + +static void *def_sitar_mbhc_cal(void) +{ + void *sitar_cal; + struct sitar_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + sitar_cal = kzalloc(SITAR_MBHC_CAL_SIZE(SITAR_MBHC_DEF_BUTTONS, + SITAR_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!sitar_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((SITAR_MBHC_CAL_GENERAL_PTR(sitar_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_PLUG_DET_PTR(sitar_cal)->X) = (Y)) + S(mic_current, SITAR_PID_MIC_5_UA); + S(hph_current, SITAR_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_PLUG_TYPE_PTR(sitar_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 1650); +#undef S +#define S(X, Y) ((SITAR_MBHC_CAL_BTN_DET_PTR(sitar_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, SITAR_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(sitar_cal); + btn_low = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_V_BTN_LOW); + btn_high = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 10; + btn_low[1] = 11; + btn_high[1] = 38; + btn_low[2] = 39; + btn_high[2] = 64; + btn_low[3] = 65; + btn_high[3] = 91; + btn_low[4] = 92; + btn_high[4] = 115; + btn_low[5] = 116; + btn_high[5] = 141; + btn_low[6] = 142; + btn_high[6] = 163; + btn_low[7] = 164; + btn_high[7] = 250; + n_ready = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_N_READY); + n_ready[0] = 48; + n_ready[1] = 38; + n_cic = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = sitar_mbhc_cal_btn_det_mp(btn_cfg, SITAR_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return sitar_cal; +} + +static int msm8930_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + pr_debug("%s: ch=%d\n", __func__, + msm8930_slim_0_rx_ch); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm8930_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm8930_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + msm8930_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + msm8930_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + + } +end: + return ret; +} + +static int msm8930_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + pr_debug("%s()\n", __func__); + + snd_soc_dapm_new_controls(dapm, msm8930_dapm_widgets, + ARRAY_SIZE(msm8930_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Left Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + SITAR_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + mbhc_cfg.gpio = 37; + mbhc_cfg.gpio_irq = gpio_to_irq(mbhc_cfg.gpio); + sitar_hs_detect(codec, &mbhc_cfg); + + /* Initialize default PMIC speaker gain */ + pm8xxx_spk_gain(DEFAULT_PMIC_SPK_GAIN); + + return 0; +} + +static int msm8930_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8930_slim_0_rx_ch; + + return 0; +} + +static int msm8930_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8930_slim_0_tx_ch; + + return 0; +} + +static int msm8930_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + + return 0; +} + +static int msm8930_hdmi_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int msm8930_btsco_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = msm8930_btsco_rate; + channels->min = channels->max = msm8930_btsco_ch; + + return 0; +} + +static int msm8930_auxpcm_be_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} + +static int msm8930_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int msm8930_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} + +static int msm8930_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); + return 0; +} + +static int msm8930_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm8930_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; + +} + +static void msm8930_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm8930_aux_pcm_free_gpios(); +} + +static void msm8930_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s(): substream = %s stream = %d\n", __func__, + substream->name, substream->stream); +} + +static struct snd_soc_ops msm8930_be_ops = { + .startup = msm8930_startup, + .hw_params = msm8930_hw_params, + .shutdown = msm8930_shutdown, +}; + +static struct snd_soc_ops msm8930_auxpcm_be_ops = { + .startup = msm8930_auxpcm_startup, + .shutdown = msm8930_auxpcm_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8930_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8930 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8930 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8930 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + /* .be_id = do not care */ + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + /* .be_id = do not care */ + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8930 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "sitar_codec", + .codec_dai_name = "sitar_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm8930_audrx_init, + .be_hw_params_fixup = msm8930_slim_0_rx_be_hw_params_fixup, + .ops = &msm8930_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "sitar_codec", + .codec_dai_name = "sitar_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm8930_slim_0_tx_be_hw_params_fixup, + .ops = &msm8930_be_ops, + }, + /* Backend BT/FM DAI Links */ + { + .name = LPASS_BE_INT_BT_SCO_RX, + .stream_name = "Internal BT-SCO Playback", + .cpu_dai_name = "msm-dai-q6.12288", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX, + .be_hw_params_fixup = msm8930_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_BT_SCO_TX, + .stream_name = "Internal BT-SCO Capture", + .cpu_dai_name = "msm-dai-q6.12289", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX, + .be_hw_params_fixup = msm8930_btsco_be_hw_params_fixup, + }, + { + .name = LPASS_BE_INT_FM_RX, + .stream_name = "Internal FM Playback", + .cpu_dai_name = "msm-dai-q6.12292", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_RX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_FM_TX, + .stream_name = "Internal FM Capture", + .cpu_dai_name = "msm-dai-q6.12293", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_TX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_HDMI_RX, + .be_hw_params_fixup = msm8930_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_RX, + .be_hw_params_fixup = msm8930_auxpcm_be_params_fixup, + .ops = &msm8930_auxpcm_be_ops, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_TX, + .be_hw_params_fixup = msm8930_auxpcm_be_params_fixup, + }, + /* Incall Music BACK END DAI Link */ + { + .name = LPASS_BE_VOICE_PLAYBACK_TX, + .stream_name = "Voice Farend Playback", + .cpu_dai_name = "msm-dai-q6.32773", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + }, + /* Incall Record Uplink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_TX, + .stream_name = "Voice Uplink Capture", + .cpu_dai_name = "msm-dai-q6.32772", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INCALL_RECORD_TX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + }, + /* Incall Record Downlink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_RX, + .stream_name = "Voice Downlink Capture", + .cpu_dai_name = "msm-dai-q6.32771", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INCALL_RECORD_RX, + .be_hw_params_fixup = msm8930_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, +}; + +struct snd_soc_card snd_soc_card_msm8930 = { + .name = "msm8930-sitar-snd-card", + .dai_link = msm8930_dai, + .num_links = ARRAY_SIZE(msm8930_dai), + .controls = sitar_msm8930_controls, + .num_controls = ARRAY_SIZE(sitar_msm8930_controls), +}; + +static struct platform_device *msm8930_snd_device; + +static int msm8930_configure_headset_mic_gpios(void) +{ + int ret; + ret = gpio_request(80, "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio 80\n", __func__); + return ret; + } + ret = gpio_direction_output(80, 0); + if (ret) { + pr_err("%s: Unable to set direction\n", __func__); + gpio_free(80); + } + msm8930_headset_gpios_configured = 0; + return 0; +} +static void msm8930_free_headset_mic_gpios(void) +{ + if (msm8930_headset_gpios_configured) + gpio_free(80); +} + +static int __init msm8930_audio_init(void) +{ + int ret; + + if (!cpu_is_msm8930()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + mbhc_cfg.calibration = def_sitar_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm8930_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm8930_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm8930_snd_device, &snd_soc_card_msm8930); + ret = platform_device_add(msm8930_snd_device); + if (ret) { + platform_device_put(msm8930_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm8930_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm8930_headset_gpios_configured = 0; + } else + msm8930_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm8930_audio_init); + +static void __exit msm8930_audio_exit(void) +{ + if (!cpu_is_msm8930()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm8930_free_headset_mic_gpios(); + platform_device_unregister(msm8930_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm8930_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8930"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c new file mode 100644 index 000000000000..a102ec3f0561 --- /dev/null +++ b/sound/soc/msm/msm8960.c @@ -0,0 +1,1740 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include "../codecs/wcd9310.h" + +/* 8960 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM8960_SPK_ON 1 +#define MSM8960_SPK_OFF 0 + +#define msm8960_SLIM_0_RX_MAX_CHANNELS 2 +#define msm8960_SLIM_0_TX_MAX_CHANNELS 4 + +#define SAMPLE_RATE_8KHZ 8000 +#define SAMPLE_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 +#define TOP_SPK_AMP 0x10 + +#define GPIO_AUX_PCM_DOUT 63 +#define GPIO_AUX_PCM_DIN 64 +#define GPIO_AUX_PCM_SYNC 65 +#define GPIO_AUX_PCM_CLK 66 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +#define JACK_DETECT_GPIO 38 +#define JACK_DETECT_INT PM8921_GPIO_IRQ(PM8921_IRQ_BASE, JACK_DETECT_GPIO) +#define JACK_US_EURO_SEL_GPIO 35 + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm8960_spk_control; +static int msm8960_ext_bottom_spk_pamp; +static int msm8960_ext_top_spk_pamp; +static int msm8960_slim_0_rx_ch = 1; +static int msm8960_slim_0_tx_ch = 1; + +static int msm8960_btsco_rate = SAMPLE_RATE_8KHZ; +static int msm8960_btsco_ch = 1; + +static int msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + +static struct clk *codec_clk; +static int clk_users; + +static int msm8960_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static bool hs_detect_use_gpio; +module_param(hs_detect_use_gpio, bool, 0444); +MODULE_PARM_DESC(hs_detect_use_gpio, "Use GPIO for headset detection"); + +static bool hs_detect_use_firmware; +module_param(hs_detect_use_firmware, bool, 0444); +MODULE_PARM_DESC(hs_detect_use_firmware, "Use firmware for headset detection"); + +static int msm8960_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); +static bool msm8960_swap_gnd_mic(struct snd_soc_codec *codec); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm8960_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, + .gpio_irq = 0, + .gpio_level_insert = 1, + .swap_gnd_mic = NULL, +}; + +static u32 us_euro_sel_gpio = PM8921_GPIO_PM_TO_SYS(JACK_US_EURO_SEL_GPIO); + +static struct mutex cdc_mclk_mutex; + +static void msm8960_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + . + function = PM_GPIO_FUNC_NORMAL, + }; + + if (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm8960_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8960_ext_bottom_spk_pamp |= spk; + + if ((msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm8960_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm8960_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG | TOP_SPK_AMP)) { + + pr_debug("%s: top_spk_amp_state = 0x%x spk_event = 0x%x\n", + __func__, msm8960_ext_top_spk_pamp, spk); + + if (((msm8960_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) || + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm8960_ext_top_spk_pamp |= spk; + + if (((msm8960_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) || + (msm8960_ext_top_spk_pamp & TOP_SPK_AMP)) { + + msm8960_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm8960_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm8960_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG | TOP_SPK_AMP)) { + + pr_debug("%s: top_spk_amp_state = 0x%x spk_event = 0x%x\n", + __func__, msm8960_ext_top_spk_pamp, spk); + + if (!msm8960_ext_top_spk_pamp) + return; + + if ((spk & TOP_SPK_AMP_POS) || (spk & TOP_SPK_AMP_NEG)) { + + msm8960_ext_top_spk_pamp &= (~(TOP_SPK_AMP_POS | + TOP_SPK_AMP_NEG)); + } else if (spk & TOP_SPK_AMP) { + msm8960_ext_top_spk_pamp &= ~TOP_SPK_AMP; + } + + if (msm8960_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm8960_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after ext Top Spek Ampl is off\n", + __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm8960_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + mutex_lock(&dapm->codec->mutex); + + pr_debug("%s: msm8960_spk_control = %d", __func__, msm8960_spk_control); + if (msm8960_spk_control == MSM8960_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); + mutex_unlock(&dapm->codec->mutex); +} + +static int msm8960_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_spk_control = %d", __func__, msm8960_spk_control); + ucontrol->value.integer.value[0] = msm8960_spk_control; + return 0; +} +static int msm8960_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8960_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm8960_spk_control = ucontrol->value.integer.value[0]; + msm8960_ext_control(codec); + return 1; +} +static int msm8960_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm8960_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm8960_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm8960_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm8960_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top", 12)) + msm8960_ext_spk_power_amp_on(TOP_SPK_AMP); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm8960_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm8960_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm8960_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm8960_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top", 12)) + msm8960_ext_spk_power_amp_off(TOP_SPK_AMP); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm8960_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + int r = 0; + pr_debug("%s: enable = %d\n", __func__, enable); + + mutex_lock(&cdc_mclk_mutex); + if (enable) { + clk_users++; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 1) { + if (codec_clk) { + clk_set_rate(codec_clk, TABLA_EXT_CLK_RATE); + clk_prepare_enable(codec_clk); + tabla_mclk_enable(codec, 1, dapm); + } else { + pr_err("%s: Error setting Tabla MCLK\n", + __func__); + clk_users--; + r = -EINVAL; + } + } + } else { + if (clk_users > 0) { + clk_users--; + pr_debug("%s: clk_users = %d\n", __func__, clk_users); + if (clk_users == 0) { + pr_debug("%s: disabling MCLK. clk_users = %d\n", + __func__, clk_users); + tabla_mclk_enable(codec, 0, dapm); + clk_disable_unprepare(codec_clk); + } + } else { + pr_err("%s: Error releasing Tabla MCLK\n", __func__); + r = -EINVAL; + } + } + mutex_unlock(&cdc_mclk_mutex); + return r; +} + +static bool msm8960_swap_gnd_mic(struct snd_soc_codec *codec) +{ + int value = gpio_get_value_cansleep(us_euro_sel_gpio); + pr_debug("%s: US EURO select switch %d to %d\n", __func__, value, + !value); + gpio_set_value_cansleep(us_euro_sel_gpio, !value); + return true; +} + +static int msm8960_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + pr_debug("%s: event = %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return msm8960_enable_codec_ext_clk(w->codec, 1, true); + case SND_SOC_DAPM_POST_PMD: + return msm8960_enable_codec_ext_clk(w->codec, 0, true); + } + return 0; +} + +static const struct snd_soc_dapm_widget msm8960_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm8960_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm8960_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm8960_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm8960_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm8960_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top", msm8960_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + {"Ext Spk Top", NULL, "LINEOUT5"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm8960_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm8960_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static const char *auxpcm_rate_text[] = {"rate_8000", "rate_16000"}; +static const struct soc_enum msm8960_auxpcm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, auxpcm_rate_text), +}; + +static int msm8960_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_slim_0_rx_ch = %d\n", __func__, + msm8960_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_rx_ch - 1; + return 0; +} + +static int msm8960_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8960_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8960_slim_0_rx_ch = %d\n", __func__, + msm8960_slim_0_rx_ch); + return 1; +} + +static int msm8960_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_slim_0_tx_ch = %d\n", __func__, + msm8960_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_tx_ch - 1; + return 0; +} + +static int msm8960_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8960_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm8960_slim_0_tx_ch = %d\n", __func__, + msm8960_slim_0_tx_ch); + return 1; +} + +static int msm8960_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_btsco_rate = %d", __func__, msm8960_btsco_rate); + ucontrol->value.integer.value[0] = msm8960_btsco_rate; + return 0; +} + +static int msm8960_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8960_btsco_rate = SAMPLE_RATE_8KHZ; + break; + case 1: + msm8960_btsco_rate = SAMPLE_RATE_16KHZ; + break; + default: + msm8960_btsco_rate = SAMPLE_RATE_8KHZ; + break; + } + pr_debug("%s: msm8960_btsco_rate = %d\n", __func__, msm8960_btsco_rate); + return 0; +} + +static int msm8960_auxpcm_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_auxpcm_rate = %d", __func__, + msm8960_auxpcm_rate); + ucontrol->value.integer.value[0] = msm8960_auxpcm_rate; + return 0; +} + +static int msm8960_auxpcm_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + break; + case 1: + msm8960_auxpcm_rate = SAMPLE_RATE_16KHZ; + break; + default: + msm8960_auxpcm_rate = SAMPLE_RATE_8KHZ; + break; + } + pr_debug("%s: msm8960_auxpcm_rate = %d" + "ucontrol->value.integer.value[0] = %d\n", __func__, + msm8960_auxpcm_rate, + (int)ucontrol->value.integer.value[0]); + return 0; +} + +static const struct snd_kcontrol_new tabla_msm8960_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8960_enum[0], msm8960_get_spk, + msm8960_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm8960_enum[1], + msm8960_slim_0_rx_ch_get, msm8960_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm8960_enum[2], + msm8960_slim_0_tx_ch_get, msm8960_slim_0_tx_ch_put), + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm8960_btsco_enum[0], + msm8960_btsco_rate_get, msm8960_btsco_rate_put), + SOC_ENUM_EXT("AUX PCM SampleRate", msm8960_auxpcm_enum[0], + msm8960_auxpcm_rate_get, msm8960_auxpcm_rate_put), +}; + +static void *def_tabla_mbhc_cal(void) +{ + void *tabla_cal; + struct tabla_mbhc_btn_detect_cfg *btn_cfg; + u16 *btn_low, *btn_high; + u8 *n_ready, *n_cic, *gain; + + tabla_cal = kzalloc(TABLA_MBHC_CAL_SIZE(TABLA_MBHC_DEF_BUTTONS, + TABLA_MBHC_DEF_RLOADS), + GFP_KERNEL); + if (!tabla_cal) { + pr_err("%s: out of memory\n", __func__); + return NULL; + } + +#define S(X, Y) ((TABLA_MBHC_CAL_GENERAL_PTR(tabla_cal)->X) = (Y)) + S(t_ldoh, 100); + S(t_bg_fast_settle, 100); + S(t_shutdown_plug_rem, 255); + S(mbhc_nsa, 4); + S(mbhc_navg, 4); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_DET_PTR(tabla_cal)->X) = (Y)) + S(mic_current, TABLA_PID_MIC_5_UA); + S(hph_current, TABLA_PID_MIC_5_UA); + S(t_mic_pid, 100); + S(t_ins_complete, 250); + S(t_ins_retry, 200); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y)) + S(v_no_mic, 30); + S(v_hs_max, 2400); +#undef S +#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y)) + S(c[0], 62); + S(c[1], 124); + S(nc, 1); + S(n_meas, 3); + S(mbhc_nsc, 11); + S(n_btn_meas, 1); + S(n_btn_con, 2); + S(num_btn, TABLA_MBHC_DEF_BUTTONS); + S(v_btn_press_delta_sta, 100); + S(v_btn_press_delta_cic, 50); +#undef S + btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal); + btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW); + btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH); + btn_low[0] = -50; + btn_high[0] = 20; + btn_low[1] = 21; + btn_high[1] = 62; + btn_low[2] = 63; + btn_high[2] = 104; + btn_low[3] = 105; + btn_high[3] = 143; + btn_low[4] = 144; + btn_high[4] = 181; + btn_low[5] = 182; + btn_high[5] = 218; + btn_low[6] = 219; + btn_high[6] = 254; + btn_low[7] = 255; + btn_high[7] = 330; + n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY); + n_ready[0] = 80; + n_ready[1] = 68; + n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC); + n_cic[0] = 60; + n_cic[1] = 47; + gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN); + gain[0] = 11; + gain[1] = 9; + + return tabla_cal; +} + +static int msm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + pr_debug("%s: rx_0_ch=%d\n", __func__, msm8960_slim_0_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + msm8960_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + msm8960_slim_0_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + + pr_debug("%s: %s tx_dai_id = %d num_ch = %d\n", __func__, + codec_dai->name, codec_dai->id, msm8960_slim_0_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(cpu_dai, + msm8960_slim_0_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + msm8960_slim_0_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } +end: + return ret; +} + +static int msm8960_slimbus_2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + unsigned int rx_ch_cnt = 0, tx_ch_cnt = 0; + unsigned int num_tx_ch = 0; + unsigned int num_rx_ch = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + num_rx_ch = params_channels(params); + + pr_debug("%s: %s rx_dai_id = %d num_ch = %d\n", __func__, + codec_dai->name, codec_dai->id, num_rx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, 0, + num_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, 0, 0, + num_rx_ch, rx_ch); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } else { + num_tx_ch = params_channels(params); + + pr_debug("%s: %s tx_dai_id = %d num_ch = %d\n", __func__, + codec_dai->name, codec_dai->id, num_tx_ch); + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt , rx_ch); + if (ret < 0) { + pr_err("%s: failed to get codec chan map\n", __func__); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, + num_tx_ch, tx_ch, 0 , 0); + if (ret < 0) { + pr_err("%s: failed to set cpu chan map\n", __func__); + goto end; + } + ret = snd_soc_dai_set_channel_map(codec_dai, + num_tx_ch, tx_ch, 0, 0); + if (ret < 0) { + pr_err("%s: failed to set codec channel map\n", + __func__); + goto end; + } + } +end: + return ret; +} + +static int msm8960_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct pm_gpio jack_gpio_cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + }; + + pr_debug("%s(), dev_name%s\n", __func__, dev_name(cpu_dai->dev)); + + if (machine_is_msm8960_liquid()) { + top_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(19)); + bottom_spk_pamp_gpio = (PM8921_GPIO_PM_TO_SYS(18)); + } + + snd_soc_dapm_new_controls(dapm, msm8960_dapm_widgets, + ARRAY_SIZE(msm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, common_audio_map, + ARRAY_SIZE(common_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + (SND_JACK_HEADSET | SND_JACK_OC_HPHL | + SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED), + &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + err = snd_soc_jack_new(codec, "Button Jack", + TABLA_JACK_BUTTON_MASK, &button_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + + codec_clk = clk_get(cpu_dai->dev, "osr_clk"); + + if (machine_is_msm8960_cdp()) + mbhc_cfg.swap_gnd_mic = msm8960_swap_gnd_mic; + + if (hs_detect_use_gpio) { + mbhc_cfg.gpio = PM8921_GPIO_PM_TO_SYS(JACK_DETECT_GPIO); + mbhc_cfg.gpio_irq = JACK_DETECT_INT; + } + + if (mbhc_cfg.gpio) { + err = pm8xxx_gpio_config(mbhc_cfg.gpio, &jack_gpio_cfg); + if (err) { + pr_err("%s: pm8xxx_gpio_config JACK_DETECT failed %d\n", + __func__, err); + return err; + } + } + + mbhc_cfg.read_fw_bin = hs_detect_use_firmware; + + err = tabla_hs_detect(codec, &mbhc_cfg); + + return err; +} + +static int msm8960_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8960_slim_0_rx_ch; + + return 0; +} + +static int msm8960_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8960_slim_0_tx_ch; + + return 0; +} + +static int msm8960_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + + return 0; +} + +static int msm8960_hdmi_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s channels->min %u channels->max %u ()\n", __func__, + channels->min, channels->max); + + rate->min = rate->max = 48000; + + return 0; +} + +static int msm8960_btsco_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = msm8960_btsco_rate; + channels->min = channels->max = msm8960_btsco_ch; + + return 0; +} +static int msm8960_auxpcm_be_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = msm8960_auxpcm_rate; + /* PCM only supports mono output */ + channels->min = channels->max = 1; + + return 0; +} +static int msm8960_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} + +static int msm8960_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} +static int msm8960_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + pr_debug("%s(): dai_link_str_name = %s cpu_dai = %s codec_dai = %s\n", + __func__, rtd->dai_link->stream_name, + rtd->dai_link->cpu_dai_name, rtd->dai_link->codec_dai_name); + return 0; +} + +static int msm8960_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm8960_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return 0; +} + +static void msm8960_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm8960_aux_pcm_free_gpios(); +} + +static void msm8960_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + pr_debug("%s(): dai_link str_name = %s cpu_dai = %s codec_dai = %s\n", + __func__, rtd->dai_link->stream_name, + rtd->dai_link->cpu_dai_name, rtd->dai_link->codec_dai_name); +} + +static struct snd_soc_ops msm8960_be_ops = { + .startup = msm8960_startup, + .hw_params = msm8960_hw_params, + .shutdown = msm8960_shutdown, +}; + +static struct snd_soc_ops msm8960_auxpcm_be_ops = { + .startup = msm8960_auxpcm_startup, + .shutdown = msm8960_auxpcm_shutdown, +}; + +static struct snd_soc_ops msm8960_slimbus_2_be_ops = { + .startup = msm8960_startup, + .hw_params = msm8960_slimbus_2_hw_params, + .shutdown = msm8960_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8960_dai_common[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-multi-ch-pcm-dsp", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "MSM AFE-PCM RX", + .stream_name = "AFE-PROXY RX", + .cpu_dai_name = "msm-dai-q6.241", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = "MSM AFE-PCM TX", + .stream_name = "AFE-PROXY TX", + .cpu_dai_name = "msm-dai-q6.240", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .platform_name = "msm-pcm-afe", + .ignore_suspend = 1, + }, + { + .name = "MSM8960 Compr", + .stream_name = "COMPR", + .cpu_dai_name = "MultiMedia4", + .platform_name = "msm-compr-dsp", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .name = "AUXPCM Hostless", + .stream_name = "AUXPCM Hostless", + .cpu_dai_name = "AUXPCM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* HDMI Hostless */ + { + .name = "HDMI_RX_HOSTLESS", + .stream_name = "HDMI_RX_HOSTLESS", + .cpu_dai_name = "HDMI_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + { + .name = "VoLTE", + .stream_name = "VoLTE", + .cpu_dai_name = "VoLTE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .be_id = MSM_FRONTEND_DAI_VOLTE, + }, + /* Backend BT/FM DAI Links */ + { + .name = LPASS_BE_INT_BT_SCO_RX, + .stream_name = "Internal BT-SCO Playback", + .cpu_dai_name = "msm-dai-q6.12288", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX, + .be_hw_params_fixup = msm8960_btsco_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_BT_SCO_TX, + .stream_name = "Internal BT-SCO Capture", + .cpu_dai_name = "msm-dai-q6.12289", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX, + .be_hw_params_fixup = msm8960_btsco_be_hw_params_fixup, + }, + { + .name = LPASS_BE_INT_FM_RX, + .stream_name = "Internal FM Playback", + .cpu_dai_name = "msm-dai-q6.12292", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_RX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_INT_FM_TX, + .stream_name = "Internal FM Capture", + .cpu_dai_name = "msm-dai-q6.12293", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6-hdmi.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_HDMI_RX, + .be_hw_params_fixup = msm8960_hdmi_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + /* Backend AFE DAI Links */ + { + .name = LPASS_BE_AFE_PCM_RX, + .stream_name = "AFE Playback", + .cpu_dai_name = "msm-dai-q6.224", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_RX, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_AFE_PCM_TX, + .stream_name = "AFE Capture", + .cpu_dai_name = "msm-dai-q6.225", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AFE_PCM_TX, + }, + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.2", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_RX, + .be_hw_params_fixup = msm8960_auxpcm_be_params_fixup, + .ops = &msm8960_auxpcm_be_ops, + .ignore_pmdown_time = 1, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.3", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_TX, + .be_hw_params_fixup = msm8960_auxpcm_be_params_fixup, + }, + /* Incall Music BACK END DAI Link */ + { + .name = LPASS_BE_VOICE_PLAYBACK_TX, + .stream_name = "Voice Farend Playback", + .cpu_dai_name = "msm-dai-q6.32773", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* Incall Record Uplink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_TX, + .stream_name = "Voice Uplink Capture", + .cpu_dai_name = "msm-dai-q6.32772", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INCALL_RECORD_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* Incall Record Downlink BACK END DAI Link */ + { + .name = LPASS_BE_INCALL_RECORD_RX, + .stream_name = "Voice Downlink Capture", + .cpu_dai_name = "msm-dai-q6.32771", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INCALL_RECORD_RX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, +}; + +static struct snd_soc_dai_link msm8960_dai_delta_tabla1x[] = { + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla1x_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm8960_audrx_init, + .be_hw_params_fixup = msm8960_slim_0_rx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla1x_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm8960_slim_0_tx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + }, + /* Ultrasound TX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Capture", + .stream_name = "SLIMBUS_2 Hostless Capture", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla1x_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_slimbus_2_be_ops, + }, + /* Ultrasound RX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Playback", + .stream_name = "SLIMBUS_2 Hostless Playback", + .cpu_dai_name = "msm-dai-q6.16388", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla1x_codec", + .codec_dai_name = "tabla_rx3", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_slimbus_2_be_ops, + }, +}; + + +static struct snd_soc_dai_link msm8960_dai_delta_tabla2x[] = { + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm8960_audrx_init, + .be_hw_params_fixup = msm8960_slim_0_rx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + .ignore_pmdown_time = 1, /* this dainlink has playback support */ + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm8960_slim_0_tx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + }, + /* Ultrasound TX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Capture", + .stream_name = "SLIMBUS_2 Hostless Capture", + .cpu_dai_name = "msm-dai-q6.16389", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx2", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_slimbus_2_be_ops, + }, + /* Ultrasound RX Back End DAI Link */ + { + .name = "SLIMBUS_2 Hostless Playback", + .stream_name = "SLIMBUS_2 Hostless Playback", + .cpu_dai_name = "msm-dai-q6.16388", + .platform_name = "msm-pcm-hostless", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx3", + .ignore_suspend = 1, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + .ops = &msm8960_slimbus_2_be_ops, + }, +}; + +static struct snd_soc_dai_link msm8960_tabla1x_dai[ + ARRAY_SIZE(msm8960_dai_common) + + ARRAY_SIZE(msm8960_dai_delta_tabla1x)]; + + +static struct snd_soc_dai_link msm8960_dai[ + ARRAY_SIZE(msm8960_dai_common) + + ARRAY_SIZE(msm8960_dai_delta_tabla2x)]; + +static struct snd_soc_card snd_soc_tabla1x_card_msm8960 = { + .name = "msm8960-tabla1x-snd-card", + .dai_link = msm8960_tabla1x_dai, + .num_links = ARRAY_SIZE(msm8960_tabla1x_dai), + .controls = tabla_msm8960_controls, + .num_controls = ARRAY_SIZE(tabla_msm8960_controls), +}; + +static struct snd_soc_card snd_soc_card_msm8960 = { + .name = "msm8960-snd-card", + .dai_link = msm8960_dai, + .num_links = ARRAY_SIZE(msm8960_dai), + .controls = tabla_msm8960_controls, + .num_controls = ARRAY_SIZE(tabla_msm8960_controls), +}; + +static struct platform_device *msm8960_snd_device; +static struct platform_device *msm8960_snd_tabla1x_device; + +static int msm8960_configure_headset_mic_gpios(void) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(us_euro_sel_gpio, "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + us_euro_sel_gpio); + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(us_euro_sel_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + us_euro_sel_gpio); + else + gpio_direction_output(us_euro_sel_gpio, 0); + + return 0; +} +static void msm8960_free_headset_mic_gpios(void) +{ + if (msm8960_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(us_euro_sel_gpio); + } +} + +static int __init msm8960_audio_init(void) +{ + int ret; + + if (!cpu_is_msm8960()) { + pr_debug("%s: Not the right machine type\n", __func__); + return -ENODEV ; + } + + mbhc_cfg.calibration = def_tabla_mbhc_cal(); + if (!mbhc_cfg.calibration) { + pr_err("Calibration data allocation failed\n"); + return -ENOMEM; + } + + msm8960_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm8960_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + memcpy(msm8960_dai, msm8960_dai_common, sizeof(msm8960_dai_common)); + memcpy(msm8960_dai + ARRAY_SIZE(msm8960_dai_common), + msm8960_dai_delta_tabla2x, sizeof(msm8960_dai_delta_tabla2x)); + + platform_set_drvdata(msm8960_snd_device, &snd_soc_card_msm8960); + ret = platform_device_add(msm8960_snd_device); + if (ret) { + platform_device_put(msm8960_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + msm8960_snd_tabla1x_device = platform_device_alloc("soc-audio", 1); + if (!msm8960_snd_tabla1x_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + memcpy(msm8960_tabla1x_dai, msm8960_dai_common, + sizeof(msm8960_dai_common)); + memcpy(msm8960_tabla1x_dai + ARRAY_SIZE(msm8960_dai_common), + msm8960_dai_delta_tabla1x, sizeof(msm8960_dai_delta_tabla1x)); + + platform_set_drvdata(msm8960_snd_tabla1x_device, + &snd_soc_tabla1x_card_msm8960); + ret = platform_device_add(msm8960_snd_tabla1x_device); + if (ret) { + platform_device_put(msm8960_snd_tabla1x_device); + kfree(mbhc_cfg.calibration); + return ret; + } + + if (msm8960_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm8960_headset_gpios_configured = 0; + } else + msm8960_headset_gpios_configured = 1; + + mutex_init(&cdc_mclk_mutex); + return ret; + +} +module_init(msm8960_audio_init); + +static void __exit msm8960_audio_exit(void) +{ + if (!cpu_is_msm8960()) { + pr_debug("%s: Not the right machine type\n", __func__); + return ; + } + msm8960_free_headset_mic_gpios(); + platform_device_unregister(msm8960_snd_device); + platform_device_unregister(msm8960_snd_tabla1x_device); + kfree(mbhc_cfg.calibration); + mutex_destroy(&cdc_mclk_mutex); +} +module_exit(msm8960_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8960"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8974.c b/sound/soc/msm/msm8974.c new file mode 100644 index 000000000000..f0a9d14ebf99 --- /dev/null +++ b/sound/soc/msm/msm8974.c @@ -0,0 +1,752 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/wcd9310.h" + +/* 8974 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM8974_SPK_ON 1 +#define MSM8974_SPK_OFF 0 + +#define MSM_SLIM_0_RX_MAX_CHANNELS 2 +#define MSM_SLIM_0_TX_MAX_CHANNELS 4 + +#define BTSCO_RATE_8KHZ 8000 +#define BTSCO_RATE_16KHZ 16000 + +#define BOTTOM_SPK_AMP_POS 0x1 +#define BOTTOM_SPK_AMP_NEG 0x2 +#define TOP_SPK_AMP_POS 0x4 +#define TOP_SPK_AMP_NEG 0x8 + +#define GPIO_AUX_PCM_DOUT 43 +#define GPIO_AUX_PCM_DIN 44 +#define GPIO_AUX_PCM_SYNC 45 +#define GPIO_AUX_PCM_CLK 46 + +#define TABLA_EXT_CLK_RATE 12288000 + +#define TABLA_MBHC_DEF_BUTTONS 8 +#define TABLA_MBHC_DEF_RLOADS 5 + +/* Shared channel numbers for Slimbus ports that connect APQ to MDM. */ +enum { + SLIM_1_RX_1 = 145, /* BT-SCO and USB TX */ + SLIM_1_TX_1 = 146, /* BT-SCO and USB RX */ + SLIM_2_RX_1 = 147, /* HDMI RX */ + SLIM_3_RX_1 = 148, /* In-call recording RX */ + SLIM_3_RX_2 = 149, /* In-call recording RX */ + SLIM_4_TX_1 = 150, /* In-call musid delivery TX */ +}; + +static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18); +static u32 bottom_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(19); +static int msm_spk_control; +static int msm_ext_bottom_spk_pamp; +static int msm_ext_top_spk_pamp; +static int msm_slim_0_rx_ch = 1; +static int msm_slim_0_tx_ch = 1; + +static int msm_btsco_rate = BTSCO_RATE_8KHZ; +static int msm_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; +static struct snd_soc_jack button_jack; + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm); + +static struct tabla_mbhc_config mbhc_cfg = { + .headset_jack = &hs_jack, + .button_jack = &button_jack, + .read_fw_bin = false, + .calibration = NULL, + .micbias = TABLA_MICBIAS2, + .mclk_cb_fn = msm_enable_codec_ext_clk, + .mclk_rate = TABLA_EXT_CLK_RATE, + .gpio = 0, /* MBHC GPIO is not configured */ + .gpio_irq = 0, + .gpio_level_insert = 1, +}; + +static void msm_enable_ext_spk_amp_gpio(u32 spk_amp_gpio) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + . + function = PM_GPIO_FUNC_NORMAL, + }; + + if (spk_amp_gpio == bottom_spk_pamp_gpio) { + + ret = gpio_request(bottom_spk_pamp_gpio, "BOTTOM_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting BOTTOM SPK AMP GPIO %u\n", + __func__, bottom_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(bottom_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Bottom Spk Ampl" + " gpio %u\n", __func__, bottom_spk_pamp_gpio); + else { + pr_debug("%s: enable Bottom spkr amp gpio\n", __func__); + gpio_direction_output(bottom_spk_pamp_gpio, 1); + } + + } else if (spk_amp_gpio == top_spk_pamp_gpio) { + + ret = gpio_request(top_spk_pamp_gpio, "TOP_SPK_AMP"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + top_spk_pamp_gpio); + return; + } + ret = pm8xxx_gpio_config(top_spk_pamp_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure Top Spk Ampl" + " gpio %u\n", __func__, top_spk_pamp_gpio); + else { + pr_debug("%s: enable Top spkr amp gpio\n", __func__); + gpio_direction_output(top_spk_pamp_gpio, 1); + } + } else { + pr_err("%s: ERROR : Invalid External Speaker Ampl GPIO." + " gpio = %u\n", __func__, spk_amp_gpio); + return; + } +} + +static void msm_ext_spk_power_amp_on(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + pr_debug("%s() External Bottom Speaker Ampl already " + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_bottom_spk_pamp |= spk; + + if ((msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_POS) && + (msm_ext_bottom_spk_pamp & BOTTOM_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(bottom_spk_pamp_gpio); + pr_debug("%s: slepping 4 ms after turning on external " + " Bottom Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + pr_debug("%s() External Top Speaker Ampl already" + "turned on. spk = 0x%08x\n", __func__, spk); + return; + } + + msm_ext_top_spk_pamp |= spk; + + if ((msm_ext_top_spk_pamp & TOP_SPK_AMP_POS) && + (msm_ext_top_spk_pamp & TOP_SPK_AMP_NEG)) { + + msm_enable_ext_spk_amp_gpio(top_spk_pamp_gpio); + pr_debug("%s: sleeping 4 ms after turning on " + " external Top Speaker Ampl\n", __func__); + usleep_range(4000, 4000); + } + } else { + + pr_err("%s: ERROR : Invalid External Speaker Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_spk_power_amp_off(u32 spk) +{ + if (spk & (BOTTOM_SPK_AMP_POS | BOTTOM_SPK_AMP_NEG)) { + + if (!msm_ext_bottom_spk_pamp) + return; + + gpio_direction_output(bottom_spk_pamp_gpio, 0); + gpio_free(bottom_spk_pamp_gpio); + msm_ext_bottom_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Bottom" + " Speaker Ampl\n", __func__); + + usleep_range(4000, 4000); + + } else if (spk & (TOP_SPK_AMP_POS | TOP_SPK_AMP_NEG)) { + + if (!msm_ext_top_spk_pamp) + return; + + gpio_direction_output(top_spk_pamp_gpio, 0); + gpio_free(top_spk_pamp_gpio); + msm_ext_top_spk_pamp = 0; + + pr_debug("%s: sleeping 4 ms after turning off external Top" + " Spkaker Ampl\n", __func__); + + usleep_range(4000, 4000); + } else { + + pr_err("%s: ERROR : Invalid Ext Spk Ampl. spk = 0x%08x\n", + __func__, spk); + return; + } +} + +static void msm_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + if (msm_spk_control == MSM8974_SPK_ON) { + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_enable_pin(dapm, "Ext Spk Top Neg"); + } else { + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Bottom Neg"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Pos"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk Top Neg"); + } + + snd_soc_dapm_sync(dapm); +} + +static int msm_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_spk_control = %d", __func__, msm_spk_control); + ucontrol->value.integer.value[0] = msm_spk_control; + return 0; +} +static int msm_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm_spk_control = ucontrol->value.integer.value[0]; + msm_ext_control(codec); + return 1; +} +static int msm_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_on(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_on(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + + } else { + if (!strncmp(w->name, "Ext Spk Bottom Pos", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Bottom Neg", 18)) + msm_ext_spk_power_amp_off(BOTTOM_SPK_AMP_NEG); + else if (!strncmp(w->name, "Ext Spk Top Pos", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_POS); + else if (!strncmp(w->name, "Ext Spk Top Neg", 15)) + msm_ext_spk_power_amp_off(TOP_SPK_AMP_NEG); + else { + pr_err("%s() Invalid Speaker Widget = %s\n", + __func__, w->name); + return -EINVAL; + } + } + return 0; +} + +static int msm_enable_codec_ext_clk(struct snd_soc_codec *codec, int enable, + bool dapm) +{ + return 0; +} + +static int msm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} + +static const struct snd_soc_dapm_widget msm_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, + msm_mclk_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Ext Spk Bottom Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Bottom Neg", msm_spkramp_event), + + SND_SOC_DAPM_SPK("Ext Spk Top Pos", msm_spkramp_event), + SND_SOC_DAPM_SPK("Ext Spk Top Neg", msm_spkramp_event), + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("ANCRight Headset Mic", NULL), + SND_SOC_DAPM_MIC("ANCLeft Headset Mic", NULL), + + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + SND_SOC_DAPM_MIC("Digital Mic3", NULL), + SND_SOC_DAPM_MIC("Digital Mic4", NULL), + SND_SOC_DAPM_MIC("Digital Mic5", NULL), + SND_SOC_DAPM_MIC("Digital Mic6", NULL), + +}; + +static const struct snd_soc_dapm_route common_audio_map[] = { + + {"RX_BIAS", NULL, "MCLK"}, + {"LDO_H", NULL, "MCLK"}, + + /* Speaker path */ + {"Ext Spk Bottom Pos", NULL, "LINEOUT1"}, + {"Ext Spk Bottom Neg", NULL, "LINEOUT3"}, + + {"Ext Spk Top Pos", NULL, "LINEOUT2"}, + {"Ext Spk Top Neg", NULL, "LINEOUT4"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal1"}, + {"MIC BIAS1 Internal1", NULL, "Handset Mic"}, + + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, + + /** + * AMIC3 and AMIC4 inputs are connected to ANC microphones + * These mics are biased differently on CDP and FLUID + * routing entries below are based on bias arrangement + * on FLUID. + */ + {"AMIC3", NULL, "MIC BIAS3 Internal1"}, + {"MIC BIAS3 Internal1", NULL, "ANCRight Headset Mic"}, + + {"AMIC4", NULL, "MIC BIAS1 Internal2"}, + {"MIC BIAS1 Internal2", NULL, "ANCLeft Headset Mic"}, + + {"HEADPHONE", NULL, "LDO_H"}, + + /** + * The digital Mic routes are setup considering + * fluid as default device. + */ + + /** + * Digital Mic1. Front Bottom left Digital Mic on Fluid and MTP. + * Digital Mic GM5 on CDP mainboard. + * Conncted to DMIC2 Input on Tabla codec. + */ + {"DMIC2", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + + /** + * Digital Mic2. Front Bottom right Digital Mic on Fluid and MTP. + * Digital Mic GM6 on CDP mainboard. + * Conncted to DMIC1 Input on Tabla codec. + */ + {"DMIC1", NULL, "MIC BIAS1 External"}, + {"MIC BIAS1 External", NULL, "Digital Mic2"}, + + /** + * Digital Mic3. Back Bottom Digital Mic on Fluid. + * Digital Mic GM1 on CDP mainboard. + * Conncted to DMIC4 Input on Tabla codec. + */ + {"DMIC4", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic3"}, + + /** + * Digital Mic4. Back top Digital Mic on Fluid. + * Digital Mic GM2 on CDP mainboard. + * Conncted to DMIC3 Input on Tabla codec. + */ + {"DMIC3", NULL, "MIC BIAS3 External"}, + {"MIC BIAS3 External", NULL, "Digital Mic4"}, + + /** + * Digital Mic5. Front top Digital Mic on Fluid. + * Digital Mic GM3 on CDP mainboard. + * Conncted to DMIC5 Input on Tabla codec. + */ + {"DMIC5", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic5"}, + + /* Tabla digital Mic6 - back bottom digital Mic on Liquid and + * bottom mic on CDP. FLUID/MTP do not have dmic6 installed. + */ + {"DMIC6", NULL, "MIC BIAS4 External"}, + {"MIC BIAS4 External", NULL, "Digital Mic6"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *slim0_rx_ch_text[] = {"One", "Two"}; +static const char *slim0_tx_ch_text[] = {"One", "Two", "Three", "Four"}; + +static const struct soc_enum msm_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, slim0_rx_ch_text), + SOC_ENUM_SINGLE_EXT(4, slim0_tx_ch_text), +}; + +static const char *btsco_rate_text[] = {"8000", "16000"}; +static const struct soc_enum msm_btsco_enum[] = { + SOC_ENUM_SINGLE_EXT(2, btsco_rate_text), +}; + +static int msm_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_rx_ch - 1; + return 0; +} + +static int msm_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_rx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_rx_ch = %d\n", __func__, + msm_slim_0_rx_ch); + return 1; +} + +static int msm_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm_slim_0_tx_ch - 1; + return 0; +} + +static int msm_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm_slim_0_tx_ch = ucontrol->value.integer.value[0] + 1; + + pr_debug("%s: msm_slim_0_tx_ch = %d\n", __func__, + msm_slim_0_tx_ch); + return 1; +} + +static int msm_btsco_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm_btsco_rate = %d", __func__, + msm_btsco_rate); + ucontrol->value.integer.value[0] = msm_btsco_rate; + return 0; +} + +static int msm_btsco_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + switch (ucontrol->value.integer.value[0]) { + case 0: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + case 1: + msm_btsco_rate = BTSCO_RATE_16KHZ; + break; + default: + msm_btsco_rate = BTSCO_RATE_8KHZ; + break; + } + pr_debug("%s: msm_btsco_rate = %d\n", __func__, + msm_btsco_rate); + return 0; +} + +static const struct snd_kcontrol_new tabla_msm_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm_enum[0], msm_get_spk, + msm_set_spk), + SOC_ENUM_EXT("SLIM_0_RX Channels", msm_enum[1], + msm_slim_0_rx_ch_get, msm_slim_0_rx_ch_put), + SOC_ENUM_EXT("SLIM_0_TX Channels", msm_enum[2], + msm_slim_0_tx_ch_get, msm_slim_0_tx_ch_put), +}; + +static const struct snd_kcontrol_new int_btsco_rate_mixer_controls[] = { + SOC_ENUM_EXT("Internal BTSCO SampleRate", msm_btsco_enum[0], + msm_btsco_rate_get, msm_btsco_rate_put), +}; + +static struct snd_soc_dsp_link lpa_fe_media = { + .playback = true, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; +static struct snd_soc_dsp_link fe_media = { + .playback = true, + .capture = true, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; +static int msm_auxpcm_be_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* PCM only supports mono output with 8khz sample rate */ + rate->min = rate->max = 8000; + channels->min = channels->max = 1; + + return 0; +} +static int msm_aux_pcm_get_gpios(void) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DOUT", + __func__, GPIO_AUX_PCM_DOUT); + goto fail_dout; + } + + ret = gpio_request(GPIO_AUX_PCM_DIN, "AUX PCM DIN"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM DIN", + __func__, GPIO_AUX_PCM_DIN); + goto fail_din; + } + + ret = gpio_request(GPIO_AUX_PCM_SYNC, "AUX PCM SYNC"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM SYNC", + __func__, GPIO_AUX_PCM_SYNC); + goto fail_sync; + } + ret = gpio_request(GPIO_AUX_PCM_CLK, "AUX PCM CLK"); + if (ret < 0) { + pr_err("%s: Failed to request gpio(%d): AUX PCM CLK", + __func__, GPIO_AUX_PCM_CLK); + goto fail_clk; + } + + return 0; + +fail_clk: + gpio_free(GPIO_AUX_PCM_SYNC); +fail_sync: + gpio_free(GPIO_AUX_PCM_DIN); +fail_din: + gpio_free(GPIO_AUX_PCM_DOUT); +fail_dout: + + return ret; +} +static int msm_aux_pcm_free_gpios(void) +{ + gpio_free(GPIO_AUX_PCM_DIN); + gpio_free(GPIO_AUX_PCM_DOUT); + gpio_free(GPIO_AUX_PCM_SYNC); + gpio_free(GPIO_AUX_PCM_CLK); + + return 0; +} + +static int msm_auxpcm_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + ret = msm_aux_pcm_get_gpios(); + if (ret < 0) { + pr_err("%s: Aux PCM GPIO request failed\n", __func__); + return -EINVAL; + } + return ret; +} + +static void msm_auxpcm_shutdown(struct snd_pcm_substream *substream) +{ + + pr_debug("%s(): substream = %s\n", __func__, substream->name); + msm_aux_pcm_free_gpios(); +} +static struct snd_soc_ops msm_auxpcm_be_ops = { + .startup = msm_auxpcm_startup, + .shutdown = msm_auxpcm_shutdown, +}; +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8974 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8974 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .dsp_link = &lpa_fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + + /* AUX PCM Backend DAI Links */ + { + .name = LPASS_BE_AUXPCM_RX, + .stream_name = "AUX PCM Playback", + .cpu_dai_name = "msm-dai-q6.4106", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_RX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + .ops = &msm_auxpcm_be_ops, + }, + { + .name = LPASS_BE_AUXPCM_TX, + .stream_name = "AUX PCM Capture", + .cpu_dai_name = "msm-dai-q6.4107", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_AUXPCM_TX, + .be_hw_params_fixup = msm_auxpcm_be_params_fixup, + }, + +}; + +struct snd_soc_card snd_soc_card_msm = { + .name = "msm8974-taiko-snd-card", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static struct platform_device *msm_snd_device; + +static void msm_free_headset_mic_gpios(void) +{ + if (msm_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm_audio_init(void) +{ + int ret = 0; + if (!machine_is_copper_sim()) { + pr_err("%s: Not the right machine type\n", __func__); + return -ENODEV; + } + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + kfree(mbhc_cfg.calibration); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + kfree(mbhc_cfg.calibration); + return ret; + } + return ret; + +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + if (!machine_is_copper_sim()) { + pr_err("%s: Not the right machine type\n", __func__); + return ; + } + msm_free_headset_mic_gpios(); + platform_device_unregister(msm_snd_device); + kfree(mbhc_cfg.calibration); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC msm"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-dai.c b/sound/soc/msm/msm8x60-dai.c new file mode 100644 index 000000000000..eb5b8f97843e --- /dev/null +++ b/sound/soc/msm/msm8x60-dai.c @@ -0,0 +1,148 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.c. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8x60-pcm.h" + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rate_min = 8000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static int asoc_msm_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int asoc_msm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_msm_codec_driver = { + .probe = asoc_msm_codec_probe, + .remove = asoc_msm_codec_remove, + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_msm_cpu_driver = { + .probe = asoc_msm_cpu_probe, + .remove = asoc_msm_cpu_remove, + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_msm_codec_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_codec_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_msm_cpu_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_cpu_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-pcm.c b/sound/soc/msm/msm8x60-pcm.c new file mode 100644 index 000000000000..e19365e8a87a --- /dev/null +++ b/sound/soc/msm/msm8x60-pcm.c @@ -0,0 +1,806 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm8x60-pcm.h" + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 960 * 10, + .period_bytes_min = 960 * 5, + .period_bytes_max = 960 * 5, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +uint32_t in_frame_info[8][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void alsa_out_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + int ret = 0; + struct msm_audio *prtd = (struct msm_audio *) private_data; + int dev_rate = 48000; + pr_debug("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + pr_debug("AUDDEV_EVT_DEV_RDY\n"); + prtd->copp_id = evt_payload->routing_id; + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, prtd->copp_id); + if (prtd->copp_id == PCM_RX) + dev_rate = 8000; + + ret = msm_snddev_set_dec(prtd->session_id, prtd->copp_id, 1, + dev_rate, 1); + break; + case AUDDEV_EVT_DEV_RLS: + pr_debug("AUDDEV_EVT_DEV_RLS\n"); + prtd->copp_id = evt_payload->routing_id; + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, prtd->copp_id); + if (prtd->copp_id == PCM_RX) + dev_rate = 8000; + + ret = msm_snddev_set_dec(prtd->session_id, prtd->copp_id, 0, + dev_rate, 1); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + pr_debug("AUDDEV_EVT_STREAM_VOL_CHG\n"); + break; + default: + pr_debug("Unknown Event\n"); + break; + } +} + +static void alsa_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + int ret = 0; + struct msm_audio *prtd = (struct msm_audio *) private_data; + int dev_rate = 48000; + pr_debug("evt_id = 0x%8x\n", evt_id); + + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + prtd->copp_id = evt_payload->routing_id; + if (prtd->copp_id == PCM_TX) + dev_rate = 8000; + + ret = msm_snddev_set_enc(prtd->session_id, prtd->copp_id, 1, + dev_rate, 1); + break; + case AUDDEV_EVT_DEV_RLS: + prtd->copp_id = evt_payload->routing_id; + if (prtd->copp_id == PCM_TX) + dev_rate = 8000; + + ret = msm_snddev_set_enc(prtd->session_id, prtd->copp_id, 0, + dev_rate, 1); + break; + default: + pr_debug("Unknown Event\n"); + break; + } +} + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + if (!prtd->mmap_flag + && !atomic_read(&prtd->out_needed)) + break; + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) + break; + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + int dev_rate = 48000; + int i = 0; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_debug("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + atomic_set(&prtd->in_count, 0); + for (i = 0; i < MAX_COPP; i++) { + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, i); + if (session_route.playback_session[substream->number][i] + != DEVICE_IGNORE) { + pr_err("Device active\n"); + if (i == PCM_RX) + dev_rate = 8000; + msm_snddev_set_dec(prtd->session_id, + i, 1, dev_rate, runtime->channels); + } + } + prtd->enabled = 1; + prtd->cmd_ack = 0; + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + int dev_rate = 48000; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read_nolock(prtd->audio_client); + prtd->periods = runtime->periods; + for (i = 0; i < MAX_COPP; i++) { + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, + session_route.capture_session[prtd->session_id][i]); + if (session_route.capture_session[prtd->session_id][i] + != DEVICE_IGNORE) { + if (i == PCM_RX) + dev_rate = 8000; + msm_snddev_set_enc(prtd->session_id, i, 1, dev_rate, 1); + } + } + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("SNDRV_PCM_TRIGGER_START\n"); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_debug("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* The session id returned by q6asm_open_read above is random and + * hence we cannot use the session id to route from user space. + * This results in need of a hardcoded session id for both playback + * and capture sessions. we can use the subdevice id to identify + * the session and use that for routing. Hence using + * substream->number as the session id for routing purpose. However + * DSP understands the session based on the allocated session id, + * hence using the variable prtd->session_id for all dsp commands. + */ + + prtd->session_id = prtd->audio_client->session; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->cmd_ack = 1; + prtd->device_events = AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_STREAM_VOL_CHG | + AUDDEV_EVT_DEV_RLS; + prtd->source = msm_snddev_route_dec(prtd->session_id); + pr_debug("Register device event listener for" + "SNDRV_PCM_STREAM_PLAYBACK session %d\n", + substream->number); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_DEC, substream->number, + alsa_out_listener, (void *) prtd); + if (ret) + pr_debug("failed to register device event listener\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prtd->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + prtd->source = msm_snddev_route_enc(prtd->session_id); + pr_debug("Register device event listener for" + "SNDRV_PCM_STREAM_CAPTURE session %d\n", + substream->number); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_ENC, substream->number, + alsa_in_listener, (void *) prtd); + if (ret) + pr_debug("failed to register device event listener\n"); + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_debug("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write_nolock(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + pr_debug("%s\n", __func__); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, + substream->number); + pr_debug("%s\n", __func__); + msm_clear_session_id(prtd->session_id); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read_nolock(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, + substream->number); + msm_clear_session_id(prtd->session_id); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + pr_debug("%s: pcm_irq_pos = %d\n", __func__, prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 2); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-pcm.h b/sound/soc/msm/msm8x60-pcm.h new file mode 100644 index 000000000000..31f0e6318fa3 --- /dev/null +++ b/sound/soc/msm/msm8x60-pcm.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H +#include +#include + +#define MAX_PLAYBACK_SESSIONS 2 +#define MAX_CAPTURE_SESSIONS 1 +#define MAX_COPP 12 + +extern int copy_count; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio { + struct snd_pcm_substream *substream; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + uint16_t source; /* Encoding source bit mask */ + + struct audio_client *audio_client; + + uint16_t session_id; + int copp_id; + + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t dsp_cnt; + + uint32_t device_events; /* device events interested in */ + int abort; /* set when error, like sample rate mismatch */ + + int enabled; + int close_ack; + int cmd_ack; + atomic_t start; + atomic_t out_count; + atomic_t in_count; + atomic_t out_needed; + int periods; + int mmap_flag; +}; + +struct pcm_session { + unsigned short playback_session[MAX_PLAYBACK_SESSIONS][MAX_COPP]; + unsigned short capture_session[MAX_CAPTURE_SESSIONS][MAX_COPP]; +}; + +/* platform data */ +extern struct snd_soc_platform_driver msm_soc_platform; +extern struct pcm_session session_route; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm8x60.c b/sound/soc/msm/msm8x60.c new file mode 100644 index 000000000000..fcc3f32f5483 --- /dev/null +++ b/sound/soc/msm/msm8x60.c @@ -0,0 +1,1231 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOOPBACK_ENABLE 0x1 +#define LOOPBACK_DISABLE 0x0 + +#include "msm8x60-pcm.h" + +static struct platform_device *msm_audio_snd_device; +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); +struct pcm_session session_route; +EXPORT_SYMBOL(session_route); +static struct snd_kcontrol_new snd_msm_controls[]; + +char snddev_name[AUDIO_DEV_CTL_MAX_DEV][44]; +#define MSM_MAX_VOLUME 0x2000 +#define MSM_VOLUME_STEP ((MSM_MAX_VOLUME+17)/100) /* 17 added to avoid + more deviation */ +static int device_index; /* Count of Device controls */ +static int simple_control; /* Count of simple controls*/ +static int src_dev; +static int dst_dev; +static int loopback_status; + +static int msm_scontrol_count_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + return 0; +} + +static int msm_scontrol_count_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = simple_control; + return 0; +} + +static int msm_v_call_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* start, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_call_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + return 0; +} + +static int msm_v_call_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int start = ucontrol->value.integer.value[0]; + u32 session_id = ucontrol->value.integer.value[1]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + if (start) + broadcast_event(AUDDEV_EVT_START_VOICE, DEVICE_IGNORE, + session_id); + else + broadcast_event(AUDDEV_EVT_END_VOICE, DEVICE_IGNORE, + session_id); + return 0; +} + +static int msm_v_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* dir, mute, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 0; + return 0; +} + +static int msm_v_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + u32 session_id = ucontrol->value.integer.value[2]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + return msm_set_voice_mute(dir, mute, session_id); +} + +static int msm_v_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* dir, volume, session_id */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 0; + return 0; +} + +static int msm_v_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + u32 session_id = ucontrol->value.integer.value[2]; + + if ((session_id != 0) && + ((session_id < SESSION_ID_BASE) || + (session_id >= SESSION_ID_BASE + MAX_VOC_SESSIONS))) { + pr_err("%s: Invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + return msm_set_voice_vol(dir, volume, session_id); +} + +static int msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 16383; + return 0; +} +static int msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + int session_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + int factor = ucontrol->value.integer.value[2]; + u64 session_mask = 0; + + if (factor > 10000) + return -EINVAL; + + if ((volume < 0) || (volume/factor > 100)) + return -EINVAL; + + volume = (MSM_VOLUME_STEP * volume); + + /* Convert back to original decimal point by removing the 10-base factor + * and discard the fractional portion + */ + + volume = volume/factor; + + if (volume > MSM_MAX_VOLUME) + volume = MSM_MAX_VOLUME; + + /* Only Decoder volume control supported */ + session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + msm_vol_ctl.volume = volume; + pr_debug("%s:session_id %d, volume %d", __func__, session_id, volume); + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, DEVICE_IGNORE, + session_mask); + + return ret; +} + +static int msm_voice_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_voice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + uint32_t rx_dev_id; + uint32_t tx_dev_id; + struct msm_snddev_info *rx_dev_info; + struct msm_snddev_info *tx_dev_info; + int set = ucontrol->value.integer.value[2]; + u64 session_mask; + + if (!set) + return -EPERM; + /* Rx Device Routing */ + rx_dev_id = ucontrol->value.integer.value[0]; + rx_dev_info = audio_dev_ctrl_find_dev(rx_dev_id); + + if (IS_ERR(rx_dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(rx_dev_info); + return rc; + } + + if (!(rx_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("%s:First Dev is supposed to be RX\n", __func__); + return -EFAULT; + } + + pr_debug("%s:route cfg %d STREAM_VOICE_RX type\n", + __func__, rx_dev_id); + + msm_set_voc_route(rx_dev_info, AUDIO_ROUTE_STREAM_VOICE_RX, + rx_dev_id); + + session_mask = ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_VOC-1)); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, rx_dev_id, session_mask); + + + /* Tx Device Routing */ + tx_dev_id = ucontrol->value.integer.value[1]; + tx_dev_info = audio_dev_ctrl_find_dev(tx_dev_id); + + if (IS_ERR(tx_dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(tx_dev_info); + return rc; + } + + if (!(tx_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("%s:Second Dev is supposed to be Tx\n", __func__); + return -EFAULT; + } + + pr_debug("%s:route cfg %d %d type\n", + __func__, tx_dev_id, AUDIO_ROUTE_STREAM_VOICE_TX); + + msm_set_voc_route(tx_dev_info, AUDIO_ROUTE_STREAM_VOICE_TX, + tx_dev_id); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, tx_dev_id, session_mask); + + if (rx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, rx_dev_id, session_mask); + + if (tx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, tx_dev_id, session_mask); + + return rc; +} + +static int msm_voice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + struct msm_snddev_info *dst_dev_info; + struct msm_snddev_info *src_dev_info; + int tx_freq = 0; + int rx_freq = 0; + u32 set_freq = 0; + + set = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + pr_info("%s:device %s set %d\n", __func__, dev_info->name, set); + + if (set) { + if (!dev_info->opened) { + set_freq = dev_info->sample_rate; + if (!msm_device_is_voice(route_cfg.dev_id)) { + msm_get_voc_freq(&tx_freq, &rx_freq); + if (dev_info->capability & SNDDEV_CAP_TX) + set_freq = tx_freq; + + if (set_freq == 0) + set_freq = dev_info->sample_rate; + } else + set_freq = dev_info->sample_rate; + + + pr_err("%s:device freq =%d\n", __func__, set_freq); + rc = dev_info->dev_ops.set_freq(dev_info, set_freq); + if (rc < 0) { + pr_err("%s:device freq failed!\n", __func__); + return rc; + } + dev_info->set_sample_rate = rc; + rc = 0; + rc = dev_info->dev_ops.open(dev_info); + if (rc < 0) { + pr_err("%s:Enabling %s failed\n", + __func__, dev_info->name); + return rc; + } + dev_info->opened = 1; + broadcast_event(AUDDEV_EVT_DEV_RDY, route_cfg.dev_id, + SESSION_IGNORE); + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if ((dst_dev_info->opened) && + (src_dev_info->opened)) { + pr_debug("%d: Enable afe_loopback\n", + __LINE__); + afe_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 1; + } + } + } + } else { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + pr_err("%s:Snd device failed close!\n", + __func__); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + SESSION_IGNORE); + } + if (loopback_status == 1) { + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + pr_debug("%d: Disable afe_loopback\n", + __LINE__); + afe_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 0; + } + } + } + + } + return rc; +} + +static int msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + + ucontrol->value.integer.value[0] = dev_info->copp_id; + ucontrol->value.integer.value[1] = dev_info->capability; + + return 0; +} + +static int msm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int enc_freq = 0; + int requested_freq = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + int session_id = ucontrol->value.integer.value[0]; + int set = ucontrol->value.integer.value[2]; + u64 session_mask = 0; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + + if (ucontrol->id.numid == 2) + route_cfg.stream_type = AUDIO_ROUTE_STREAM_PLAYBACK; + else + route_cfg.stream_type = AUDIO_ROUTE_STREAM_REC; + + pr_debug("%s:route cfg %d %d type for popp %d\n", + __func__, route_cfg.dev_id, route_cfg.stream_type, session_id); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + if (route_cfg.stream_type == AUDIO_ROUTE_STREAM_PLAYBACK) { + rc = msm_snddev_set_dec(session_id, dev_info->copp_id, set, + dev_info->sample_rate, dev_info->channel_mode); + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } + } else { + + rc = msm_snddev_set_enc(session_id, dev_info->copp_id, set, + dev_info->sample_rate, dev_info->channel_mode); + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + enc_freq = msm_snddev_get_enc_freq(session_id); + requested_freq = enc_freq; + if (enc_freq > 0) { + rc = msm_snddev_request_freq(&enc_freq, + session_id, + SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + pr_debug("%s:sample rate configured %d\ + sample rate requested %d \n", + __func__, enc_freq, requested_freq); + if ((rc <= 0) || (enc_freq != requested_freq)) { + pr_debug("%s:msm_snddev_withdraw_freq\n", + __func__); + rc = msm_snddev_withdraw_freq + (session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + broadcast_event(AUDDEV_EVT_FREQ_CHG, + route_cfg.dev_id, + SESSION_IGNORE); + } + } + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } + } + + if (rc < 0) { + pr_err("%s:device could not be assigned!\n", __func__); + return -EFAULT; + } + + return rc; +} + +static int msm_device_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_device_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + ucontrol->value.integer.value[0] = dev_info->dev_volume; + + return 0; +} + +static int msm_device_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + pr_debug("%s:dev_id = %d, volume = %d\n", __func__, dev_id, volume); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + pr_err("%s: audio_dev_ctrl_find_dev failed. %ld \n", + __func__, PTR_ERR(dev_info)); + return rc; + } + + pr_debug("%s:dev_name = %s dev_id = %d, volume = %d\n", + __func__, dev_info->name, dev_id, volume); + + if (dev_info->dev_ops.set_device_volume) + rc = dev_info->dev_ops.set_device_volume(dev_info, volume); + else { + pr_info("%s : device %s does not support device volume " + "control.", __func__, dev_info->name); + return -EPERM; + } + + return rc; +} + +static int msm_reset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0; + return 0; +} + +static int msm_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_err("%s:Resetting all devices\n", __func__); + return msm_reset_all_device(); +} + +static int msm_anc_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int msm_anc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_anc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int enable = ucontrol->value.integer.value[1]; + + pr_debug("%s: dev_id = %d, enable = %d\n", __func__, dev_id, enable); + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + pr_err("%s: audio_dev_ctrl_find_dev failed. %ld\n", + __func__, PTR_ERR(dev_info)); + return rc; + } + + if (dev_info->dev_ops.enable_anc) { + rc = dev_info->dev_ops.enable_anc(dev_info, enable); + } else { + pr_info("%s : device %s does not support anc control.", + __func__, dev_info->name); + return -EPERM; + } + + return rc; +} + +static int pcm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + /* + * First parameter is session id ~ subdevice number + * Second parameter is device id. + */ + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int pcm_route_get_rx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int pcm_route_get_tx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int pcm_route_put_rx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int session_id = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + u64 session_mask = 0; + + /* + * session id is incremented by one and stored as session id 0 + * is being used by dsp currently. whereas user space would use + * subdevice number as session id. + */ + session_id = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + set = ucontrol->value.integer.value[2]; + + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("pass invalid dev_id %d\n", route_cfg.dev_id); + return PTR_ERR(dev_info); + } + if (!(dev_info->capability & SNDDEV_CAP_RX)) + return -EINVAL; + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + if (!set) { + session_route.playback_session[session_id][dev_info->copp_id] + = DEVICE_IGNORE; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + return 0; + } + pr_debug("%s:Routing playback session %d to %s\n", + __func__, (session_id), + dev_info->name); + session_route.playback_session[session_id][dev_info->copp_id] = + dev_info->copp_id; + if (dev_info->opened) { + dev_info->sessions = dev_info->sessions | session_mask; + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } else { + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } + return 0; +} + +static int pcm_route_put_tx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int session_id = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + u64 session_mask = 0; + + session_id = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + set = ucontrol->value.integer.value[2]; + + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("pass invalid dev_id %d\n", route_cfg.dev_id); + return PTR_ERR(dev_info); + } + pr_debug("%s:Routing capture session %d to %s\n", __func__, + session_id, + dev_info->name); + if (!(dev_info->capability & SNDDEV_CAP_TX)) + return -EINVAL; + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + session_route.capture_session[session_id][dev_info->copp_id] + = DEVICE_IGNORE; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + return 0; + } + + session_route.capture_session[session_id][dev_info->copp_id] = + dev_info->copp_id; + if (dev_info->opened) { + dev_info->sessions = dev_info->sessions | session_mask; + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } else { + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } + return 0; +} + +static int msm_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_snddev_info *src_dev_info = NULL; /* TX device */ + struct msm_snddev_info *dst_dev_info = NULL; /* RX device */ + int dst_dev_id = ucontrol->value.integer.value[0]; + int src_dev_id = ucontrol->value.integer.value[1]; + int set = ucontrol->value.integer.value[2]; + + pr_debug("%s: dst=%d :src=%d set=%d\n", __func__, + dst_dev_id, src_dev_id, set); + + dst_dev_info = audio_dev_ctrl_find_dev(dst_dev_id); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + if (!(dst_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("Destination device %d is not RX device\n", + dst_dev_id); + return -EFAULT; + } + + src_dev_info = audio_dev_ctrl_find_dev(src_dev_id); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if (!(src_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("Source device %d is not TX device\n", src_dev_id); + return -EFAULT; + } + + if (set) { + pr_debug("%s:%d:Enabling AFE_Loopback\n", __func__, __LINE__); + src_dev = src_dev_id; + dst_dev = dst_dev_id; + loopback_status = 1; + if ((dst_dev_info->opened) && (src_dev_info->opened)) + rc = afe_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } else { + pr_debug("%s:%d:Disabling AFE_Loopback\n", __func__, __LINE__); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + loopback_status = 0; + rc = afe_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } + return rc; +} +static int msm_device_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int msm_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dev_id = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + struct msm_snddev_info *dev_info; + int rc = 0; + u16 gain = 0x2000; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + pr_err("%s: audio_dev_ctrl_find_dev failed. %ld\n", + __func__, PTR_ERR(dev_info)); + return rc; + } + if (!(dev_info->capability & SNDDEV_CAP_TX)) { + rc = -EINVAL; + return rc; + } + if (mute) + gain = 0; + + pr_debug("%s:dev_name = %s dev_id = %d, gain = %hX\n", + __func__, dev_info->name, dev_id, gain); + rc = afe_apply_gain(dev_info->copp_id, gain); + if (rc < 0) { + pr_err("%s : device %s not able to set device gain " + "control.", __func__, dev_info->name); + return rc; + } + pr_debug("Muting/Unmuting device id %d(%s)\n", dev_id, dev_info->name); + + return rc; +} + +static int msm_voc_session_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SESSION_ID_BASE + MAX_VOC_SESSIONS; + return 0; +} + +static int msm_voice_session_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + voice_get_session_id("Voice session"); + return 0; +} + +static int msm_voip_session_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = voice_get_session_id("VoIP session"); + return 0; +} + +static struct snd_kcontrol_new snd_dev_controls[AUDIO_DEV_CTL_MAX_DEV]; + +static int snd_dev_ctl_index(int idx) +{ + struct msm_snddev_info *dev_info; + + dev_info = audio_dev_ctrl_find_dev(idx); + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + return PTR_ERR(dev_info); + } + if (sizeof(dev_info->name) <= 44) + sprintf(&snddev_name[idx][0] , "%s", dev_info->name); + + snd_dev_controls[idx].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_dev_controls[idx].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_dev_controls[idx].name = &snddev_name[idx][0]; + snd_dev_controls[idx].index = idx; + snd_dev_controls[idx].info = msm_device_info; + snd_dev_controls[idx].get = msm_device_get; + snd_dev_controls[idx].put = msm_device_put; + snd_dev_controls[idx].private_value = 0; + return 0; + +} + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +/* If new controls are to be added which would be constant across the + * different targets, please add to the structure + * snd_msm_controls. Please do not add any controls to the structure + * snd_msm_secondary_controls defined below unless they are msm8x60 + * specific. + */ + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT("Count", msm_scontrol_count_info, msm_scontrol_count_get, \ + NULL, 0), + MSM_EXT("Stream", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Record", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Voice", msm_voice_info, msm_voice_get, \ + msm_voice_put, 0), + MSM_EXT("Volume", msm_volume_info, msm_volume_get, \ + msm_volume_put, 0), + MSM_EXT("VoiceVolume", msm_v_volume_info, msm_v_volume_get, \ + msm_v_volume_put, 0), + MSM_EXT("VoiceMute", msm_v_mute_info, msm_v_mute_get, \ + msm_v_mute_put, 0), + MSM_EXT("Voice Call", msm_v_call_info, msm_v_call_get, \ + msm_v_call_put, 0), + MSM_EXT("Device_Volume", msm_device_volume_info, + msm_device_volume_get, msm_device_volume_put, 0), + MSM_EXT("Reset", msm_reset_info, + msm_reset_get, msm_reset_put, 0), + MSM_EXT("ANC", msm_anc_info, msm_anc_get, msm_anc_put, 0), + MSM_EXT("Device_Mute", msm_device_mute_info, + msm_device_mute_get, msm_device_mute_put, 0), +}; + +static struct snd_kcontrol_new snd_msm_secondary_controls[] = { + MSM_EXT("PCM Playback Sink", + pcm_route_info, pcm_route_get_rx, pcm_route_put_rx, 0), + MSM_EXT("PCM Capture Source", + pcm_route_info, pcm_route_get_tx, pcm_route_put_tx, 0), + MSM_EXT("Sound Device Loopback", msm_loopback_info, + msm_loopback_get, msm_loopback_put, 0), + MSM_EXT("VoiceVolume Ext", + msm_v_volume_info, msm_v_volume_get, msm_v_volume_put, 0), + MSM_EXT("VoiceMute Ext", + msm_v_mute_info, msm_v_mute_get, msm_v_mute_put, 0), + MSM_EXT("Voice Call Ext", + msm_v_call_info, msm_v_call_get, msm_v_call_put, 0), + MSM_EXT("Voice session", + msm_voc_session_info, msm_voice_session_get, NULL, 0), + MSM_EXT("VoIP session", + msm_voc_session_info, msm_voip_session_get, NULL, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + int dev_cnt; + + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding ctl\n", __func__); + } + + for (idx = 0; idx < ARRAY_SIZE(snd_msm_secondary_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_secondary_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding secondary ctl\n", __func__); + } + dev_cnt = msm_snddev_devcount(); + + for (idx = 0; idx < dev_cnt; idx++) { + if (!snd_dev_ctl_index(idx)) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_dev_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding ctl\n", __func__); + } else + return 0; + } + simple_control = ARRAY_SIZE(snd_msm_controls) + + ARRAY_SIZE(snd_msm_secondary_controls); + device_index = simple_control + 1; + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + memset(&session_route, DEVICE_IGNORE, sizeof(struct pcm_session)); + + ret = msm_new_mixer(codec); + if (ret < 0) + pr_err("%s: ALSA MSM Mixer Fail\n", __func__); + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +#ifdef CONFIG_MSM_8x60_VOIP +{ + .name = "MSM Primary Voip", + .stream_name = "MVS", + .cpu_dai_name = "mvs-cpu-dai.0", + .platform_name = "msm-mvs-audio.0", + .codec_name = "mvs-codec-dai.0", + .codec_dai_name = "mvs-codec-dai", +}, +#endif +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm_audio_mvs.h b/sound/soc/msm/msm_audio_mvs.h new file mode 100644 index 000000000000..6ee457c154d4 --- /dev/null +++ b/sound/soc/msm/msm_audio_mvs.h @@ -0,0 +1,369 @@ +/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#ifndef __MSM_AUDIO_MVS_H +#define __MSM_AUDIO_MVS_H +#include +#include +#include +#include +#include +#include + + +#define AUDIO_GET_MVS_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 0), unsigned) +#define AUDIO_SET_MVS_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 1), unsigned) +#define AUDIO_SET_SCR_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 2), unsigned) +#define AUDIO_SET_DTX_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 3), unsigned) +/* MVS modes */ +#define MVS_MODE_LINEAR_PCM 9 + +#define MVS_PROG 0x30000014 +#define MVS_VERS 0x00030001 + +#define MVS_CLIENT_ID_VOIP 0x00000003 /* MVS_CLIENT_VOIP */ + +#define MVS_ACQUIRE_PROC 4 +#define MVS_ENABLE_PROC 5 +#define MVS_RELEASE_PROC 6 +#define MVS_SET_PCM_MODE_PROC 9 + +#define MVS_EVENT_CB_TYPE_PROC 1 +#define MVS_PACKET_UL_FN_TYPE_PROC 2 +#define MVS_PACKET_DL_FN_TYPE_PROC 3 + +#define MVS_CB_FUNC_ID 0xAAAABBBB +#define MVS_UL_CB_FUNC_ID 0xBBBBCCCC +#define MVS_DL_CB_FUNC_ID 0xCCCCDDDD + +/* MVS frame modes */ + +#define MVS_FRAME_MODE_PCM_UL 13 +#define MVS_FRAME_MODE_PCM_DL 14 + +/* MVS context */ +#define MVS_PKT_CONTEXT_ISR 0x00000001 + +/* Max voc packet size */ +#define MVS_MAX_VOC_PKT_SIZE 320 + +#define VOIP_MAX_Q_LEN 20 +#define MVS_MAX_Q_LEN 8 +#define RPC_TYPE_REQUEST 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_STATUS_FAILURE 0 +#define RPC_STATUS_SUCCESS 1 +#define RPC_STATUS_REJECT 1 + + +#define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) +#define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) +#define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) + + +enum audio_mvs_state_type { AUDIO_MVS_CLOSED, AUDIO_MVS_OPENED, + AUDIO_MVS_PREPARING, AUDIO_MVS_ACQUIRE, AUDIO_MVS_ENABLED, + AUDIO_MVS_CLOSING +}; + +enum audio_mvs_event_type { AUDIO_MVS_COMMAND, AUDIO_MVS_MODE, + AUDIO_MVS_NOTIFY +}; + +enum audio_mvs_cmd_status_type { AUDIO_MVS_CMD_FAILURE, AUDIO_MVS_CMD_BUSY, + AUDIO_MVS_CMD_SUCCESS +}; + +enum audio_mvs_mode_status_type { AUDIO_MVS_MODE_NOT_AVAIL, + AUDIO_MVS_MODE_INIT, AUDIO_MVS_MODE_READY +}; + +enum audio_mvs_pkt_status_type { AUDIO_MVS_PKT_NORMAL, AUDIO_MVS_PKT_FAST, + AUDIO_MVS_PKT_SLOW +}; + +struct rpc_audio_mvs_acquire_args { + uint32_t client_id; + uint32_t cb_func_id; +}; + +struct audio_mvs_acquire_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_acquire_args acquire_args; +}; + +struct rpc_audio_mvs_enable_args { + uint32_t client_id; + uint32_t mode; + uint32_t ul_cb_func_id; + uint32_t dl_cb_func_id; + uint32_t context; +}; + +struct audio_mvs_enable_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_enable_args enable_args; +}; + +struct audio_mvs_release_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t client_id; +}; + +struct audio_mvs_set_pcm_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t pcm_mode; +}; + +struct audio_mvs_set_pcmwb_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t pcmwb_mode; +}; + +struct audio_mvs_buffer { + uint8_t *voc_pkt; + uint32_t len; +}; + +union audio_mvs_event_data { + struct mvs_ev_command_type { + uint32_t event; + uint32_t client_id; + uint32_t cmd_status; + } mvs_ev_command_type; + + struct mvs_ev_mode_type { + uint32_t event; + uint32_t client_id; + uint32_t mode_status; + uint32_t mode; + } mvs_ev_mode_type; + + struct mvs_ev_notify_type { + uint32_t event; + uint32_t client_id; + uint32_t buf_dir; + uint32_t max_frames; + } mvs_ev_notify_type; +}; + +struct audio_mvs_cb_func_args { + uint32_t cb_func_id; + uint32_t valid_ptr; + uint32_t event; + union audio_mvs_event_data event_data; +}; + +struct audio_mvs_frame_info_hdr { + uint32_t frame_mode; + uint32_t mvs_mode; + uint32_t buf_free_cnt; +}; + +struct audio_mvs_ul_cb_func_args { + uint32_t cb_func_id; + uint32_t pkt_len; + uint32_t voc_pkt[MVS_MAX_VOC_PKT_SIZE / 4]; + + uint32_t valid_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_ignore; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + + uint32_t pkt_len_ignore; +}; + +struct audio_mvs_ul_reply { + struct rpc_reply_hdr reply_hdr; + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +struct audio_mvs_dl_cb_func_args { + uint32_t cb_func_id; + uint32_t valid_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_ignore; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + +}; + +struct audio_mvs_dl_reply { + struct rpc_reply_hdr reply_hdr; + uint32_t voc_pkt[MVS_MAX_VOC_PKT_SIZE / 4]; + uint32_t valid_frame_info_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_again; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +struct audio_mvs_info_type { + enum audio_mvs_state_type state; + uint32_t frame_mode; + uint32_t mvs_mode; + uint32_t buf_free_cnt; + uint32_t pcm_frame; + uint32_t pcm_mode; + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + int dl_play; + struct msm_rpc_endpoint *rpc_endpt; + uint32_t rpc_prog; + uint32_t rpc_ver; + uint32_t rpc_status; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ + + uint32_t samp_rate; + uint32_t channel_mode; + + uint8_t *mem_chunk; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct audio_mvs_buffer in[MVS_MAX_Q_LEN]; + uint32_t in_read; + uint32_t in_write; + + struct audio_mvs_buffer out[MVS_MAX_Q_LEN]; + uint32_t out_read; + uint32_t out_write; + + struct task_struct *task; + + wait_queue_head_t wait; + wait_queue_head_t prepare_wait; + wait_queue_head_t out_wait; + wait_queue_head_t in_wait; + + + struct mutex lock; + struct mutex prepare_lock; + struct mutex in_lock; + struct mutex out_lock; + + struct wake_lock suspend_lock; + struct pm_qos_request pm_qos_req; + struct timer_list timer; + unsigned long expiry; + int ack_dl_count; + int ack_ul_count; + int prepare_ack; + int playback_start; + int capture_start; + unsigned long expiry_delta; + int mvs_enable; + int playback_enable; + int capture_enable; + int instance; + +}; + +struct audio_voip_info_type { + enum audio_mvs_state_type state; + enum audio_mvs_state_type playback_state; + enum audio_mvs_state_type capture_state; + + unsigned int pcm_playback_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct audio_mvs_buffer in[VOIP_MAX_Q_LEN]; + uint32_t in_read; + uint32_t in_write; + + struct audio_mvs_buffer out[VOIP_MAX_Q_LEN]; + uint32_t out_read; + uint32_t out_write; + + wait_queue_head_t out_wait; + wait_queue_head_t in_wait; + + struct mutex lock; + struct mutex prepare_lock; + + struct wake_lock suspend_lock; + struct pm_qos_request pm_qos_req; + int playback_start; + int capture_start; + int instance; +}; + +enum msm_audio_pcm_frame_type { + MVS_AMR_SPEECH_GOOD, /* Good speech frame */ + MVS_AMR_SPEECH_DEGRADED, /* Speech degraded */ + MVS_AMR_ONSET, /* onset */ + MVS_AMR_SPEECH_BAD, /* Corrupt speech frame (bad CRC) */ + MVS_AMR_SID_FIRST, /* First silence descriptor */ + MVS_AMR_SID_UPDATE, /* Comfort noise frame */ + MVS_AMR_SID_BAD, /* Corrupt SID frame (bad CRC) */ + MVS_AMR_NO_DATA, /* Nothing to transmit */ + MVS_AMR_SPEECH_LOST, /* downlink speech lost */ +}; + +enum msm_audio_dtx_mode_type { MVS_DTX_OFF, MVS_DTX_ON +}; + +struct msm_audio_mvs_config { + uint32_t mvs_mode; + uint32_t bit_rate; +}; + +extern struct snd_soc_dai_driver msm_mvs_dais[2]; +extern struct snd_soc_codec_device soc_codec_dev_msm_mvs; +extern struct snd_soc_platform_driver msm_mvs_soc_platform; +extern struct snd_soc_platform_driver msm_voip_soc_platform; +#endif /* __MSM_AUDIO_MVS_H */ diff --git a/sound/soc/msm/mvs-dai.c b/sound/soc/msm/mvs-dai.c new file mode 100644 index 000000000000..bbe390a634ec --- /dev/null +++ b/sound/soc/msm/mvs-dai.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2010, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + +static struct snd_soc_dai_driver msm_mvs_codec_dais[] = { +{ + .name = "mvs-codec-dai", + .playback = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_mvs_cpu_dais[] = { +{ + .name = "mvs-cpu-dai", + .playback = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static int asoc_mvs_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_mvs_codec_dais, ARRAY_SIZE(msm_mvs_codec_dais)); +} + +static int asoc_mvs_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static int asoc_mvs_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_mvs_cpu_dais); +} + +static int asoc_mvs_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_mvs_codec_driver = { + .probe = asoc_mvs_codec_probe, + .remove = asoc_mvs_codec_remove, + .driver = { + .name = "mvs-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_mvs_cpu_driver = { + .probe = asoc_mvs_cpu_probe, + .remove = asoc_mvs_cpu_remove, + .driver = { + .name = "mvs-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init mvs_codec_dai_init(void) +{ + return platform_driver_register(&asoc_mvs_codec_driver); +} + +static void __exit mvs_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_mvs_codec_driver); +} + +static int __init mvs_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_mvs_cpu_driver); +} + +static void __exit mvs_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_mvs_cpu_driver); +} + +module_init(mvs_codec_dai_init); +module_exit(mvs_codec_dai_exit); +module_init(mvs_cpu_dai_init); +module_exit(mvs_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6/Makefile b/sound/soc/msm/qdsp6/Makefile new file mode 100644 index 000000000000..f45bd3362b93 --- /dev/null +++ b/sound/soc/msm/qdsp6/Makefile @@ -0,0 +1,2 @@ +obj-y := q6asm.o q6adm.o q6afe.o +obj-$(CONFIG_SND_SOC_VOICE) += q6voice.o \ No newline at end of file diff --git a/sound/soc/msm/qdsp6/q6adm.c b/sound/soc/msm/qdsp6/q6adm.c new file mode 100644 index 000000000000..16f60777c30e --- /dev/null +++ b/sound/soc/msm/qdsp6/q6adm.c @@ -0,0 +1,1088 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define TIMEOUT_MS 1000 +#define AUDIO_RX 0x0 +#define AUDIO_TX 0x1 + +#define ASM_MAX_SESSION 0x8 /* To do: define in a header */ +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF + +struct adm_ctl { + void *apr; + atomic_t copp_id[AFE_MAX_PORTS]; + atomic_t copp_cnt[AFE_MAX_PORTS]; + atomic_t copp_stat[AFE_MAX_PORTS]; + wait_queue_head_t wait; +}; + +static struct acdb_cal_block mem_addr_audproc[MAX_AUDPROC_TYPES]; +static struct acdb_cal_block mem_addr_audvol[MAX_AUDPROC_TYPES]; + +static struct adm_ctl this_adm; + +int srs_trumedia_open(int port_id, int srs_tech_id, void *srs_params) +{ + struct asm_pp_params_command *open = NULL; + int ret = 0, sz = 0; + int index; + + pr_debug("SRS - %s", __func__); + switch (srs_tech_id) { + case SRS_ID_GLOBAL: { + struct srs_trumedia_params_GLOBAL *glb_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_GLOBAL); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_GLOBAL) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS; + open->params.param_size = + sizeof(struct srs_trumedia_params_GLOBAL); + glb_params = (struct srs_trumedia_params_GLOBAL *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(glb_params, srs_params, + sizeof(struct srs_trumedia_params_GLOBAL)); + pr_debug("SRS - %s: Global params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x\n", + __func__, (int)glb_params->v1, + (int)glb_params->v2, (int)glb_params->v3, + (int)glb_params->v4, (int)glb_params->v5, + (int)glb_params->v6, (int)glb_params->v7, + (int)glb_params->v8); + break; + } + case SRS_ID_WOWHD: { + struct srs_trumedia_params_WOWHD *whd_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_WOWHD); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_WOWHD) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_WOWHD; + open->params.param_size = + sizeof(struct srs_trumedia_params_WOWHD); + whd_params = (struct srs_trumedia_params_WOWHD *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(whd_params, srs_params, + sizeof(struct srs_trumedia_params_WOWHD)); + pr_debug("SRS - %s: WOWHD params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x, 9 = %x," + " 10 = %x, 11 = %x\n", __func__, (int)whd_params->v1, + (int)whd_params->v2, (int)whd_params->v3, + (int)whd_params->v4, (int)whd_params->v5, + (int)whd_params->v6, (int)whd_params->v7, + (int)whd_params->v8, (int)whd_params->v9, + (int)whd_params->v10, (int)whd_params->v11); + break; + } + case SRS_ID_CSHP: { + struct srs_trumedia_params_CSHP *chp_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_CSHP); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_CSHP) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_CSHP; + open->params.param_size = + sizeof(struct srs_trumedia_params_CSHP); + chp_params = (struct srs_trumedia_params_CSHP *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(chp_params, srs_params, + sizeof(struct srs_trumedia_params_CSHP)); + pr_debug("SRS - %s: CSHP params - 1 = %x, 2 = %x, 3 = %x," + " 4 = %x, 5 = %x, 6 = %x, 7 = %x, 8 = %x," + " 9 = %x\n", __func__, (int)chp_params->v1, + (int)chp_params->v2, (int)chp_params->v3, + (int)chp_params->v4, (int)chp_params->v5, + (int)chp_params->v6, (int)chp_params->v7, + (int)chp_params->v8, (int)chp_params->v9); + break; + } + case SRS_ID_HPF: { + struct srs_trumedia_params_HPF *hpf_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_HPF); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_HPF) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_HPF; + open->params.param_size = + sizeof(struct srs_trumedia_params_HPF); + hpf_params = (struct srs_trumedia_params_HPF *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(hpf_params, srs_params, + sizeof(struct srs_trumedia_params_HPF)); + pr_debug("SRS - %s: HPF params - 1 = %x\n", __func__, + (int)hpf_params->v1); + break; + } + case SRS_ID_PEQ: { + struct srs_trumedia_params_PEQ *peq_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_PEQ); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_PEQ) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_PEQ; + open->params.param_size = + sizeof(struct srs_trumedia_params_PEQ); + peq_params = (struct srs_trumedia_params_PEQ *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(peq_params, srs_params, + sizeof(struct srs_trumedia_params_PEQ)); + pr_debug("SRS - %s: PEQ params - 1 = %x 2 = %x, 3 = %x," + " 4 = %x\n", __func__, (int)peq_params->v1, + (int)peq_params->v2, (int)peq_params->v3, + (int)peq_params->v4); + break; + } + case SRS_ID_HL: { + struct srs_trumedia_params_HL *hl_params = NULL; + sz = sizeof(struct asm_pp_params_command) + + sizeof(struct srs_trumedia_params_HL); + open = kzalloc(sz, GFP_KERNEL); + open->payload_size = sizeof(struct srs_trumedia_params_HL) + + sizeof(struct asm_pp_param_data_hdr); + open->params.param_id = SRS_TRUMEDIA_PARAMS_HL; + open->params.param_size = sizeof(struct srs_trumedia_params_HL); + hl_params = (struct srs_trumedia_params_HL *)((u8 *)open + + sizeof(struct asm_pp_params_command)); + memcpy(hl_params, srs_params, + sizeof(struct srs_trumedia_params_HL)); + pr_debug("SRS - %s: HL params - 1 = %x, 2 = %x, 3 = %x, 4 = %x," + " 5 = %x, 6 = %x, 7 = %x\n", __func__, + (int)hl_params->v1, (int)hl_params->v2, + (int)hl_params->v3, (int)hl_params->v4, + (int)hl_params->v5, (int)hl_params->v6, + (int)hl_params->v7); + break; + } + default: + goto fail_cmd; + } + + open->payload = NULL; + open->params.module_id = SRS_TRUMEDIA_MODULE_ID; + open->params.reserved = 0; + open->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + open->hdr.pkt_size = sz; + open->hdr.src_svc = APR_SVC_ADM; + open->hdr.src_domain = APR_DOMAIN_APPS; + open->hdr.src_port = port_id; + open->hdr.dest_svc = APR_SVC_ADM; + open->hdr.dest_domain = APR_DOMAIN_ADSP; + index = afe_get_port_index(port_id); + open->hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + open->hdr.token = port_id; + open->hdr.opcode = ADM_CMD_SET_PARAMS; + pr_debug("SRS - %s: Command was sent now check Q6 - port id = %d," + " size %d, module id %x, param id %x.\n", __func__, + open->hdr.dest_port, open->payload_size, + open->params.module_id, open->params.param_id); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)open); + if (ret < 0) { + pr_err("SRS - %s: ADM enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, 1, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("SRS - %s: ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + +fail_cmd: + kfree(open); + return ret; +} + +static int32_t adm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *payload; + int i, index; + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("adm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, + this_adm.apr); + if (this_adm.apr) { + apr_reset(this_adm.apr); + for (i = 0; i < AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], + RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + } + this_adm.apr = NULL; + } + return 0; + } + + pr_debug("%s: code = 0x%x %x %x size = %d\n", __func__, + data->opcode, payload[0], payload[1], + data->payload_size); + + if (data->payload_size) { + index = afe_get_port_index(data->token); + pr_debug("%s: Port ID %d, index %d\n", __func__, + data->token, index); + if (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, data->token); + return 0; + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + pr_debug("APR_BASIC_RSP_RESULT id %x\n", payload[0]); + switch (payload[0]) { + case ADM_CMD_SET_PARAMS: + if (rtac_make_adm_callback(payload, + data->payload_size)) + break; + case ADM_CMD_COPP_CLOSE: + case ADM_CMD_MEMORY_MAP: + case ADM_CMD_MEMORY_UNMAP: + case ADM_CMD_MEMORY_MAP_REGIONS: + case ADM_CMD_MEMORY_UNMAP_REGIONS: + case ADM_CMD_MATRIX_MAP_ROUTINGS: + case ADM_CMD_CONNECT_AFE_PORT: + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait); + break; + default: + pr_err("%s: Unknown Cmd: 0x%x\n", __func__, + payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ADM_CMDRSP_COPP_OPEN: + case ADM_CMDRSP_MULTI_CHANNEL_COPP_OPEN: { + struct adm_copp_open_respond *open = data->payload; + if (open->copp_id == INVALID_COPP_ID) { + pr_err("%s: invalid coppid rxed %d\n", + __func__, open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait); + break; + } + atomic_set(&this_adm.copp_id[index], open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + pr_debug("%s: coppid rxed=%d\n", __func__, + open->copp_id); + wake_up(&this_adm.wait); + } + break; + case ADM_CMDRSP_GET_PARAMS: + pr_debug("%s: ADM_CMDRSP_GET_PARAMS\n", __func__); + rtac_make_adm_callback(payload, + data->payload_size); + break; + default: + pr_err("%s: Unknown cmd:0x%x\n", __func__, + data->opcode); + break; + } + } + return 0; +} + +static int send_adm_cal_block(int port_id, struct acdb_cal_block *aud_cal) +{ + s32 result = 0; + struct adm_set_params_command adm_params; + int index = afe_get_port_index(port_id); + if (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d portid %d\n", + __func__, index, port_id); + return 0; + } + + pr_debug("%s: Port id %d, index %d\n", __func__, port_id, index); + + if (!aud_cal || aud_cal->cal_size == 0) { + pr_debug("%s: No ADM cal to send for port_id = %d!\n", + __func__, port_id); + result = -EINVAL; + goto done; + } + + adm_params.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + adm_params.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(adm_params)); + adm_params.hdr.src_svc = APR_SVC_ADM; + adm_params.hdr.src_domain = APR_DOMAIN_APPS; + adm_params.hdr.src_port = port_id; + adm_params.hdr.dest_svc = APR_SVC_ADM; + adm_params.hdr.dest_domain = APR_DOMAIN_ADSP; + adm_params.hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + adm_params.hdr.token = port_id; + adm_params.hdr.opcode = ADM_CMD_SET_PARAMS; + adm_params.payload = aud_cal->cal_paddr; + adm_params.payload_size = aud_cal->cal_size; + + atomic_set(&this_adm.copp_stat[index], 0); + pr_debug("%s: Sending SET_PARAMS payload = 0x%x, size = %d\n", + __func__, adm_params.payload, adm_params.payload_size); + result = apr_send_pkt(this_adm.apr, (uint32_t *)&adm_params); + if (result < 0) { + pr_err("%s: Set params failed port = %d payload = 0x%x\n", + __func__, port_id, aud_cal->cal_paddr); + result = -EINVAL; + goto done; + } + /* Wait for the callback */ + result = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!result) { + pr_err("%s: Set params timed out port = %d, payload = 0x%x\n", + __func__, port_id, aud_cal->cal_paddr); + result = -EINVAL; + goto done; + } + + result = 0; +done: + return result; +} + +static void send_adm_cal(int port_id, int path) +{ + int result = 0; + s32 acdb_path; + struct acdb_cal_block aud_cal; + + pr_debug("%s\n", __func__); + + /* Maps audio_dev_ctrl path definition to ACDB definition */ + acdb_path = path - 1; + + pr_debug("%s: Sending audproc cal\n", __func__); + get_audproc_cal(acdb_path, &aud_cal); + + /* map & cache buffers used */ + if (((mem_addr_audproc[acdb_path].cal_paddr != aud_cal.cal_paddr) && + (aud_cal.cal_size > 0)) || + (aud_cal.cal_size > mem_addr_audproc[acdb_path].cal_size)) { + + if (mem_addr_audproc[acdb_path].cal_paddr != 0) + adm_memory_unmap_regions( + &mem_addr_audproc[acdb_path].cal_paddr, + &mem_addr_audproc[acdb_path].cal_size, 1); + + result = adm_memory_map_regions(&aud_cal.cal_paddr, 0, + &aud_cal.cal_size, 1); + if (result < 0) + pr_err("ADM audproc mmap did not work! path = %d, " + "addr = 0x%x, size = %d\n", acdb_path, + aud_cal.cal_paddr, aud_cal.cal_size); + else + mem_addr_audproc[acdb_path] = aud_cal; + } + + if (!send_adm_cal_block(port_id, &aud_cal)) + pr_debug("%s: Audproc cal sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + else + pr_debug("%s: Audproc cal not sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + + pr_debug("%s: Sending audvol cal\n", __func__); + get_audvol_cal(acdb_path, &aud_cal); + + /* map & cache buffers used */ + if (((mem_addr_audvol[acdb_path].cal_paddr != aud_cal.cal_paddr) && + (aud_cal.cal_size > 0)) || + (aud_cal.cal_size > mem_addr_audvol[acdb_path].cal_size)) { + if (mem_addr_audvol[acdb_path].cal_paddr != 0) + adm_memory_unmap_regions( + &mem_addr_audvol[acdb_path].cal_paddr, + &mem_addr_audvol[acdb_path].cal_size, 1); + + result = adm_memory_map_regions(&aud_cal.cal_paddr, 0, + &aud_cal.cal_size, 1); + if (result < 0) + pr_err("ADM audvol mmap did not work! path = %d, " + "addr = 0x%x, size = %d\n", acdb_path, + aud_cal.cal_paddr, aud_cal.cal_size); + else + mem_addr_audvol[acdb_path] = aud_cal; + } + + if (!send_adm_cal_block(port_id, &aud_cal)) + pr_debug("%s: Audvol cal sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); + else + pr_debug("%s: Audvol cal not sent for port id: %d, path %d\n", + __func__, port_id, acdb_path); +} + +int adm_connect_afe_port(int mode, int session_id, int port_id) +{ + struct adm_cmd_connect_afe_port cmd; + int ret = 0; + int index; + + pr_debug("%s: port %d session id:%d mode:%d\n", __func__, + port_id, session_id, mode); + + port_id = afe_convert_virtual_to_portid(port_id); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + index = afe_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.src_svc = APR_SVC_ADM; + cmd.hdr.src_domain = APR_DOMAIN_APPS; + cmd.hdr.src_port = port_id; + cmd.hdr.dest_svc = APR_SVC_ADM; + cmd.hdr.dest_domain = APR_DOMAIN_ADSP; + cmd.hdr.dest_port = port_id; + cmd.hdr.token = port_id; + cmd.hdr.opcode = ADM_CMD_CONNECT_AFE_PORT; + + cmd.mode = mode; + cmd.session_id = session_id; + cmd.afe_port_id = port_id; + + atomic_set(&this_adm.copp_stat[index], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&cmd); + if (ret < 0) { + pr_err("%s:ADM enable for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM connect AFE failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + +int adm_open(int port_id, int path, int rate, int channel_mode, int topology) +{ + struct adm_copp_open_command open; + int ret = 0; + int index; + + pr_debug("%s: port %d path:%d rate:%d mode:%d\n", __func__, + port_id, path, rate, channel_mode); + + port_id = afe_convert_virtual_to_portid(port_id); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = afe_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + + + /* Create a COPP if port id are not enabled */ + if (atomic_read(&this_adm.copp_cnt[index]) == 0) { + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + open.hdr.pkt_size = sizeof(open); + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = port_id; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = port_id; + open.hdr.token = port_id; + open.hdr.opcode = ADM_CMD_COPP_OPEN; + + open.mode = path; + open.endpoint_id1 = port_id; + open.endpoint_id2 = 0xFFFF; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + if (open.topology_id == 0) + open.topology_id = topology; + + open.channel_config = channel_mode & 0x00FF; + open.rate = rate; + + pr_debug("%s: channel_config=%d port_id=%d rate=%d" + "topology_id=0x%X\n", __func__, open.channel_config,\ + open.endpoint_id1, open.rate,\ + open.topology_id); + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&open); + if (ret < 0) { + pr_err("%s:ADM enable for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + + +int adm_multi_ch_copp_open(int port_id, int path, int rate, int channel_mode, + int topology) +{ + struct adm_multi_ch_copp_open_command open; + int ret = 0; + int index; + + pr_debug("%s: port %d path:%d rate:%d channel :%d\n", __func__, + port_id, path, rate, channel_mode); + + port_id = afe_convert_virtual_to_portid(port_id); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = afe_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + + /* Create a COPP if port id are not enabled */ + if (atomic_read(&this_adm.copp_cnt[index]) == 0) { + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + + open.hdr.pkt_size = + sizeof(struct adm_multi_ch_copp_open_command); + open.hdr.opcode = ADM_CMD_MULTI_CHANNEL_COPP_OPEN; + memset(open.dev_channel_mapping, 0, 8); + + if (channel_mode == 1) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FC; + } else if (channel_mode == 2) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channel_mode == 4) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_RB; + open.dev_channel_mapping[3] = PCM_CHANNEL_LB; + } else if (channel_mode == 6) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_LFE; + open.dev_channel_mapping[3] = PCM_CHANNEL_FC; + open.dev_channel_mapping[4] = PCM_CHANNEL_LB; + open.dev_channel_mapping[5] = PCM_CHANNEL_RB; + } else { + pr_err("%s invalid num_chan %d\n", __func__, + channel_mode); + return -EINVAL; + } + + + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = port_id; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = port_id; + open.hdr.token = port_id; + + open.mode = path; + open.endpoint_id1 = port_id; + open.endpoint_id2 = 0xFFFF; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + if (open.topology_id == 0) + open.topology_id = topology; + + open.channel_config = channel_mode & 0x00FF; + open.rate = rate; + + pr_debug("%s: channel_config=%d port_id=%d rate=%d" + " topology_id=0x%X\n", __func__, open.channel_config, + open.endpoint_id1, open.rate, + open.topology_id); + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&open); + if (ret < 0) { + pr_err("%s:ADM enable for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id) +{ + struct adm_routings_command route; + int ret = 0, i = 0; + /* Assumes port_ids have already been validated during adm_open */ + int index = afe_get_port_index(copp_id); + if (index < 0 || index >= AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, copp_id); + return 0; + } + + pr_debug("%s: session 0x%x path:%d num_copps:%d port_id[0]:%d\n", + __func__, session_id, path, num_copps, port_id[0]); + + route.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + route.hdr.pkt_size = sizeof(route); + route.hdr.src_svc = 0; + route.hdr.src_domain = APR_DOMAIN_APPS; + route.hdr.src_port = copp_id; + route.hdr.dest_svc = APR_SVC_ADM; + route.hdr.dest_domain = APR_DOMAIN_ADSP; + route.hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + route.hdr.token = copp_id; + route.hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS; + route.num_sessions = 1; + route.session[0].id = session_id; + route.session[0].num_copps = num_copps; + + for (i = 0; i < num_copps; i++) { + int tmp; + port_id[i] = afe_convert_virtual_to_portid(port_id[i]); + + tmp = afe_get_port_index(port_id[i]); + + pr_debug("%s: port_id[%d]: %d, index: %d\n", __func__, i, + port_id[i], tmp); + + if (tmp >= 0 && tmp < AFE_MAX_PORTS) + route.session[0].copp_id[i] = + atomic_read(&this_adm.copp_id[tmp]); + } + if (num_copps % 2) + route.session[0].copp_id[i] = 0; + + switch (path) { + case 0x1: + route.path = AUDIO_RX; + break; + case 0x2: + case 0x3: + route.path = AUDIO_TX; + break; + default: + pr_err("%s: Wrong path set[%d]\n", __func__, path); + break; + } + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&route); + if (ret < 0) { + pr_err("%s: ADM routing for port %d failed\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + + for (i = 0; i < num_copps; i++) + send_adm_cal(port_id[i], path); + + for (i = 0; i < num_copps; i++) + rtac_add_adm_device(port_id[i], atomic_read(&this_adm.copp_id + [afe_get_port_index(port_id[i])]), + path, session_id); + return 0; + +fail_cmd: + + return ret; +} + +int adm_memory_map_regions(uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct adm_cmd_memory_map_regions *mmap_regions = NULL; + struct adm_memory_map_regions *mregions = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + + pr_debug("%s\n", __func__); + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + + cmd_size = sizeof(struct adm_cmd_memory_map_regions) + + sizeof(struct adm_memory_map_regions) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + mmap_regions = (struct adm_cmd_memory_map_regions *)mmap_region_cmd; + mmap_regions->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mmap_regions->hdr.pkt_size = cmd_size; + mmap_regions->hdr.src_port = 0; + mmap_regions->hdr.dest_port = 0; + mmap_regions->hdr.token = 0; + mmap_regions->hdr.opcode = ADM_CMD_MEMORY_MAP_REGIONS; + mmap_regions->mempool_id = mempool_id & 0x00ff; + mmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("%s: map_regions->nregions = %d\n", __func__, + mmap_regions->nregions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct adm_cmd_memory_map_regions)); + mregions = (struct adm_memory_map_regions *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->phys = buf_add[i]; + mregions->buf_size = bufsz[i]; + ++mregions; + } + + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + mmap_regions->hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_map\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + kfree(mmap_region_cmd); + return ret; +} + +int adm_memory_unmap_regions(uint32_t *buf_add, uint32_t *bufsz, + uint32_t bufcnt) +{ + struct adm_cmd_memory_unmap_regions *unmap_regions = NULL; + struct adm_memory_unmap_regions *mregions = NULL; + void *unmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + + pr_debug("%s\n", __func__); + + if (this_adm.apr == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + + cmd_size = sizeof(struct adm_cmd_memory_unmap_regions) + + sizeof(struct adm_memory_unmap_regions) * bufcnt; + + unmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!unmap_region_cmd) { + pr_err("%s: allocate unmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + unmap_regions = (struct adm_cmd_memory_unmap_regions *) + unmap_region_cmd; + unmap_regions->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + unmap_regions->hdr.pkt_size = cmd_size; + unmap_regions->hdr.src_port = 0; + unmap_regions->hdr.dest_port = 0; + unmap_regions->hdr.token = 0; + unmap_regions->hdr.opcode = ADM_CMD_MEMORY_UNMAP_REGIONS; + unmap_regions->nregions = bufcnt & 0x00ff; + unmap_regions->reserved = 0; + pr_debug("%s: unmap_regions->nregions = %d\n", __func__, + unmap_regions->nregions); + payload = ((u8 *) unmap_region_cmd + + sizeof(struct adm_cmd_memory_unmap_regions)); + mregions = (struct adm_memory_unmap_regions *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->phys = buf_add[i]; + ++mregions; + } + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) unmap_region_cmd); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + unmap_regions->hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_unmap\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + kfree(unmap_region_cmd); + return ret; +} + +int adm_get_copp_id(int port_index) +{ + pr_debug("%s\n", __func__); + + if (port_index < 0) { + pr_err("%s: invalid port_id = %d\n", __func__, port_index); + return -EINVAL; + } + + return atomic_read(&this_adm.copp_id[port_index]); +} + +int adm_close(int port_id) +{ + struct apr_hdr close; + + int ret = 0; + int index = 0; + + port_id = afe_convert_virtual_to_portid(port_id); + + index = afe_get_port_index(port_id); + if (afe_validate_port(port_id) < 0) + return -EINVAL; + + pr_debug("%s port_id=%d index %d\n", __func__, port_id, index); + + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + pr_err("%s: copp count for port[%d]is 0\n", __func__, port_id); + + goto fail_cmd; + } + atomic_dec(&this_adm.copp_cnt[index]); + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + + close.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + close.pkt_size = sizeof(close); + close.src_svc = APR_SVC_ADM; + close.src_domain = APR_DOMAIN_APPS; + close.src_port = port_id; + close.dest_svc = APR_SVC_ADM; + close.dest_domain = APR_DOMAIN_ADSP; + close.dest_port = atomic_read(&this_adm.copp_id[index]); + close.token = port_id; + close.opcode = ADM_CMD_COPP_CLOSE; + + atomic_set(&this_adm.copp_id[index], RESET_COPP_ID); + atomic_set(&this_adm.copp_stat[index], 0); + + + pr_debug("%s:coppid %d portid=%d index=%d coppcnt=%d\n", + __func__, + atomic_read(&this_adm.copp_id[index]), + port_id, index, + atomic_read(&this_adm.copp_cnt[index])); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&close); + if (ret < 0) { + pr_err("%s ADM close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + rtac_remove_adm_device(port_id); + } + +fail_cmd: + return ret; +} + +static int __init adm_init(void) +{ + int i = 0; + init_waitqueue_head(&this_adm.wait); + this_adm.apr = NULL; + + for (i = 0; i < AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + } + return 0; +} + +device_initcall(adm_init); diff --git a/sound/soc/msm/qdsp6/q6afe.c b/sound/soc/msm/qdsp6/q6afe.c new file mode 100644 index 000000000000..4f73e0f63b74 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6afe.c @@ -0,0 +1,1738 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct afe_ctl { + void *apr; + atomic_t state; + atomic_t status; + wait_queue_head_t wait; + struct task_struct *task; + void (*tx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void (*rx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void *tx_private_data; + void *rx_private_data; +}; + +static struct afe_ctl this_afe; + +static struct acdb_cal_block afe_cal_addr[MAX_AUDPROC_TYPES]; + +#define TIMEOUT_MS 1000 +#define Q6AFE_MAX_VOLUME 0x3FFF + +#define SIZEOF_CFG_CMD(y) \ + (sizeof(struct apr_hdr) + sizeof(u16) + (sizeof(struct y))) + +static int32_t afe_callback(struct apr_client_data *data, void *priv) +{ + if (data->opcode == RESET_EVENTS) { + pr_debug("q6afe: reset event = %d %d apr[%p]\n", + data->reset_event, data->reset_proc, this_afe.apr); + if (this_afe.apr) { + apr_reset(this_afe.apr); + atomic_set(&this_afe.state, 0); + this_afe.apr = NULL; + } + /* send info to user */ + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + send_sig(SIGUSR1, this_afe.task, 0); + return 0; + } + if (data->payload_size) { + uint32_t *payload; + uint16_t port_id = 0; + payload = data->payload; + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x\n", + __func__, data->opcode, + payload[0], payload[1]); + /* payload[1] contains the error status for response */ + if (payload[1] != 0) { + atomic_set(&this_afe.status, -1); + pr_err("%s: cmd = 0x%x returned error = 0x%x\n", + __func__, payload[0], payload[1]); + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case AFE_PORT_AUDIO_IF_CONFIG: + case AFE_PORT_CMD_I2S_CONFIG: + case AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG: + case AFE_PORT_AUDIO_SLIM_SCH_CONFIG: + case AFE_PORT_CMD_STOP: + case AFE_PORT_CMD_START: + case AFE_PORT_CMD_LOOPBACK: + case AFE_PORT_CMD_SIDETONE_CTL: + case AFE_PORT_CMD_SET_PARAM: + case AFE_PSEUDOPORT_CMD_START: + case AFE_PSEUDOPORT_CMD_STOP: + case AFE_PORT_CMD_APPLY_GAIN: + case AFE_SERVICE_CMD_MEMORY_MAP: + case AFE_SERVICE_CMD_MEMORY_UNMAP: + case AFE_SERVICE_CMD_UNREG_RTPORT: + atomic_set(&this_afe.state, 0); + wake_up(&this_afe.wait); + break; + case AFE_SERVICE_CMD_REG_RTPORT: + break; + case AFE_SERVICE_CMD_RTPORT_WR: + port_id = RT_PROXY_PORT_001_TX; + break; + case AFE_SERVICE_CMD_RTPORT_RD: + port_id = RT_PROXY_PORT_001_RX; + break; + default: + pr_err("Unknown cmd 0x%x\n", + payload[0]); + break; + } + } else if (data->opcode == AFE_EVENT_RT_PROXY_PORT_STATUS) { + port_id = (uint16_t)(0x0000FFFF & payload[0]); + } + pr_debug("%s:port_id = %x\n", __func__, port_id); + switch (port_id) { + case RT_PROXY_PORT_001_TX: { + if (this_afe.tx_cb) { + this_afe.tx_cb(data->opcode, data->token, + data->payload, + this_afe.tx_private_data); + } + break; + } + case RT_PROXY_PORT_001_RX: { + if (this_afe.rx_cb) { + this_afe.rx_cb(data->opcode, data->token, + data->payload, + this_afe.rx_private_data); + } + break; + } + default: + break; + } + } + return 0; +} + +int afe_get_port_type(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PCM_RX: + case SECONDARY_PCM_RX: + case SECONDARY_I2S_RX: + case MI2S_RX: + case HDMI_RX: + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_2_RX: + case SLIMBUS_3_RX: + case INT_BT_SCO_RX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case VOICE_PLAYBACK_TX: + case RT_PROXY_PORT_001_RX: + case SLIMBUS_4_RX: + ret = MSM_AFE_PORT_TYPE_RX; + break; + + case PRIMARY_I2S_TX: + case PCM_TX: + case SECONDARY_PCM_TX: + case SECONDARY_I2S_TX: + case MI2S_TX: + case DIGI_MIC_TX: + case VOICE_RECORD_TX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case SLIMBUS_2_TX: + case SLIMBUS_3_TX: + case INT_FM_TX: + case VOICE_RECORD_RX: + case INT_BT_SCO_TX: + case RT_PROXY_PORT_001_TX: + case SLIMBUS_4_TX: + ret = MSM_AFE_PORT_TYPE_TX; + break; + + default: + pr_err("%s: invalid port id %d\n", __func__, port_id); + ret = -EINVAL; + } + + return ret; +} + +int afe_validate_port(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case PCM_RX: + case PCM_TX: + case SECONDARY_PCM_RX: + case SECONDARY_PCM_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + case HDMI_RX: + case RSVD_2: + case RSVD_3: + case DIGI_MIC_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + case VOICE_PLAYBACK_TX: + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case INT_FM_TX: + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + { + ret = 0; + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} + +int afe_convert_virtual_to_portid(u16 port_id) +{ + int ret; + + /* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ + if (afe_validate_port(port_id) < 0) { + if (port_id == RT_PROXY_DAI_001_RX || + port_id == RT_PROXY_DAI_001_TX || + port_id == RT_PROXY_DAI_002_RX || + port_id == RT_PROXY_DAI_002_TX) + ret = VIRTUAL_ID_TO_PORTID(port_id); + else + ret = -EINVAL; + } else + ret = port_id; + + return ret; +} + +int afe_get_port_index(u16 port_id) +{ + switch (port_id) { + case PRIMARY_I2S_RX: return IDX_PRIMARY_I2S_RX; + case PRIMARY_I2S_TX: return IDX_PRIMARY_I2S_TX; + case PCM_RX: return IDX_PCM_RX; + case PCM_TX: return IDX_PCM_TX; + case SECONDARY_PCM_RX: return IDX_SECONDARY_PCM_RX; + case SECONDARY_PCM_TX: return IDX_SECONDARY_PCM_TX; + case SECONDARY_I2S_RX: return IDX_SECONDARY_I2S_RX; + case SECONDARY_I2S_TX: return IDX_SECONDARY_I2S_TX; + case MI2S_RX: return IDX_MI2S_RX; + case MI2S_TX: return IDX_MI2S_TX; + case HDMI_RX: return IDX_HDMI_RX; + case RSVD_2: return IDX_RSVD_2; + case RSVD_3: return IDX_RSVD_3; + case DIGI_MIC_TX: return IDX_DIGI_MIC_TX; + case VOICE_RECORD_RX: return IDX_VOICE_RECORD_RX; + case VOICE_RECORD_TX: return IDX_VOICE_RECORD_TX; + case VOICE_PLAYBACK_TX: return IDX_VOICE_PLAYBACK_TX; + case SLIMBUS_0_RX: return IDX_SLIMBUS_0_RX; + case SLIMBUS_0_TX: return IDX_SLIMBUS_0_TX; + case SLIMBUS_1_RX: return IDX_SLIMBUS_1_RX; + case SLIMBUS_1_TX: return IDX_SLIMBUS_1_TX; + case SLIMBUS_2_RX: return IDX_SLIMBUS_2_RX; + case SLIMBUS_2_TX: return IDX_SLIMBUS_2_TX; + case SLIMBUS_3_RX: return IDX_SLIMBUS_3_RX; + case SLIMBUS_3_TX: return IDX_SLIMBUS_3_TX; + case INT_BT_SCO_RX: return IDX_INT_BT_SCO_RX; + case INT_BT_SCO_TX: return IDX_INT_BT_SCO_TX; + case INT_BT_A2DP_RX: return IDX_INT_BT_A2DP_RX; + case INT_FM_RX: return IDX_INT_FM_RX; + case INT_FM_TX: return IDX_INT_FM_TX; + case RT_PROXY_PORT_001_RX: return IDX_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return IDX_RT_PROXY_PORT_001_TX; + case SLIMBUS_4_RX: return IDX_SLIMBUS_4_RX; + case SLIMBUS_4_TX: return IDX_SLIMBUS_4_TX; + + default: return -EINVAL; + } +} + +int afe_sizeof_cfg_cmd(u16 port_id) +{ + int ret_size; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_mi2s_cfg); + break; + case HDMI_RX: + ret_size = SIZEOF_CFG_CMD(afe_port_hdmi_multi_ch_cfg); + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_slimbus_sch_cfg); + break; + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_rtproxy_cfg); + break; + case PCM_RX: + case PCM_TX: + case SECONDARY_PCM_RX: + case SECONDARY_PCM_TX: + default: + ret_size = SIZEOF_CFG_CMD(afe_port_pcm_cfg); + break; + } + return ret_size; +} + +int afe_q6_interface_prepare(void) +{ + int ret = 0; + + pr_debug("%s:", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + } + } + return ret; +} + +static void afe_send_cal_block(int32_t path, u16 port_id) +{ + int result = 0; + struct acdb_cal_block cal_block; + struct afe_port_cmd_set_param_no_payload afe_cal; + pr_debug("%s: path %d\n", __func__, path); + + get_afe_cal(path, &cal_block); + if (cal_block.cal_size <= 0) { + pr_debug("%s: No AFE cal to send!\n", __func__); + goto done; + } + + if ((afe_cal_addr[path].cal_paddr != cal_block.cal_paddr) || + (cal_block.cal_size > afe_cal_addr[path].cal_size)) { + if (afe_cal_addr[path].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[path].cal_paddr); + + afe_cmd_memory_map_nowait(cal_block.cal_paddr, + cal_block.cal_size); + afe_cal_addr[path].cal_paddr = cal_block.cal_paddr; + afe_cal_addr[path].cal_size = cal_block.cal_size; + } + + afe_cal.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afe_cal.hdr.pkt_size = sizeof(afe_cal); + afe_cal.hdr.src_port = 0; + afe_cal.hdr.dest_port = 0; + afe_cal.hdr.token = 0; + afe_cal.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + afe_cal.port_id = port_id; + afe_cal.payload_size = cal_block.cal_size; + afe_cal.payload_address = cal_block.cal_paddr; + + pr_debug("%s: AFE cal sent for device port = %d, path = %d, " + "cal size = %d, cal addr = 0x%x\n", __func__, + port_id, path, cal_block.cal_size, cal_block.cal_paddr); + + result = apr_send_pkt(this_afe.apr, (uint32_t *) &afe_cal); + if (result < 0) { + pr_err("%s: AFE cal for port %d failed\n", + __func__, port_id); + } + + pr_debug("%s: AFE cal sent for path %d device!\n", __func__, path); +done: + return; +} + +void afe_send_cal(u16 port_id) +{ + pr_debug("%s\n", __func__); + + if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_TX) + afe_send_cal_block(TX_CAL, port_id); + else if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_RX) + afe_send_cal_block(RX_CAL, port_id); +} + +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate) /* This function is no blocking */ +{ + struct afe_port_start_command start; + struct afe_audioif_config_command config; + int ret; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + pr_debug("%s: %d %d\n", __func__, port_id, rate); + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + ret = -ENODEV; + return ret; + } + + if (port_id == HDMI_RX) { + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = 0; + config.hdr.opcode = AFE_PORT_MULTI_CHAN_HDMI_AUDIO_IF_CONFIG; + } else { + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = 0; + switch (port_id) { + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + config.hdr.opcode = AFE_PORT_AUDIO_SLIM_SCH_CONFIG; + break; + case MI2S_TX: + case MI2S_RX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + /* AFE_PORT_CMD_I2S_CONFIG command is not supported + * in the LPASS EL 1.0. So we have to distiguish + * which AFE command, AFE_PORT_CMD_I2S_CONFIG or + * AFE_PORT_AUDIO_IF_CONFIG to use. If the format + * is L-PCM, the AFE_PORT_AUDIO_IF_CONFIG is used + * to make the backward compatible. + */ + pr_debug("%s: afe_config->mi2s.format = %d\n", __func__, + afe_config->mi2s.format); + if (afe_config->mi2s.format == MSM_AFE_I2S_FORMAT_LPCM) + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + else + config.hdr.opcode = AFE_PORT_CMD_I2S_CONFIG; + break; + default: + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + break; + } + } + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + config.port_id = port_id; + config.port = *afe_config; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + /* send AFE cal */ + afe_send_cal(port_id); + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PORT_CMD_START; + start.port_id = port_id; + start.gain = 0x2000; + start.sample_rate = rate; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + + if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + if (this_afe.task != current) + this_afe.task = current; + + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + return 0; + +fail_cmd: + return ret; +} + +int afe_open(u16 port_id, union afe_port_config *afe_config, int rate) +{ + struct afe_port_start_command start; + struct afe_audioif_config_command config; + int ret = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + + pr_debug("%s: %d %d\n", __func__, port_id, rate); + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = 0; + switch (port_id) { + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + config.hdr.opcode = AFE_PORT_AUDIO_SLIM_SCH_CONFIG; + break; + case MI2S_TX: + case MI2S_RX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + /* AFE_PORT_CMD_I2S_CONFIG command is not supported + * in the LPASS EL 1.0. So we have to distiguish + * which AFE command, AFE_PORT_CMD_I2S_CONFIG or + * AFE_PORT_AUDIO_IF_CONFIG to use. If the format + * is L-PCM, the AFE_PORT_AUDIO_IF_CONFIG is used + * to make the backward compatible. + */ + pr_debug("%s: afe_config->mi2s.format = %d\n", __func__, + afe_config->mi2s.format); + if (afe_config->mi2s.format == MSM_AFE_I2S_FORMAT_LPCM) + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + else + config.hdr.opcode = AFE_PORT_CMD_I2S_CONFIG; + break; + default: + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + break; + } + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + config.port_id = port_id; + config.port = *afe_config; + + atomic_set(&this_afe.state, 1); + atomic_set(&this_afe.status, 0); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + if (atomic_read(&this_afe.status) != 0) { + pr_err("%s: config cmd failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PORT_CMD_START; + start.port_id = port_id; + start.gain = 0x2000; + start.sample_rate = rate; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + if (this_afe.task != current) + this_afe.task = current; + + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + return 0; +fail_cmd: + return ret; +} + +int afe_loopback(u16 enable, u16 dst_port, u16 src_port) +{ + struct afe_loopback_command lb_cmd; + int ret = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + if ((afe_get_port_type(dst_port) == MSM_AFE_PORT_TYPE_RX) && + (afe_get_port_type(src_port) == MSM_AFE_PORT_TYPE_RX)) + return afe_loopback_cfg(enable, dst_port, src_port, + LB_MODE_EC_REF_VOICE_AUDIO); + + lb_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + lb_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(lb_cmd) - APR_HDR_SIZE); + lb_cmd.hdr.src_port = 0; + lb_cmd.hdr.dest_port = 0; + lb_cmd.hdr.token = 0; + lb_cmd.hdr.opcode = AFE_PORT_CMD_LOOPBACK; + lb_cmd.tx_port_id = src_port; + lb_cmd.rx_port_id = dst_port; + lb_cmd.mode = 0xFFFF; + lb_cmd.enable = (enable ? 1 : 0); + atomic_set(&this_afe.state, 1); + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &lb_cmd); + if (ret < 0) { + pr_err("%s: AFE loopback failed\n", __func__); + ret = -EINVAL; + goto done; + } + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + } +done: + return ret; +} + +int afe_loopback_cfg(u16 enable, u16 dst_port, u16 src_port, u16 mode) +{ + struct afe_port_cmd_set_param lp_cfg; + int ret = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + pr_debug("%s: src_port %d, dst_port %d\n", + __func__, src_port, dst_port); + + lp_cfg.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + lp_cfg.hdr.pkt_size = sizeof(lp_cfg); + lp_cfg.hdr.src_port = 0; + lp_cfg.hdr.dest_port = 0; + lp_cfg.hdr.token = 0; + lp_cfg.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + + lp_cfg.port_id = src_port; + lp_cfg.payload_size = sizeof(struct afe_param_payload); + lp_cfg.payload_address = 0; + + lp_cfg.payload.module_id = AFE_MODULE_LOOPBACK; + lp_cfg.payload.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + lp_cfg.payload.param_size = sizeof(struct afe_param_loopback_cfg); + lp_cfg.payload.reserved = 0; + + lp_cfg.payload.param.loopback_cfg.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + lp_cfg.payload.param.loopback_cfg.dst_port_id = dst_port; + lp_cfg.payload.param.loopback_cfg.routing_mode = mode; + lp_cfg.payload.param.loopback_cfg.enable = enable; + lp_cfg.payload.param.loopback_cfg.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &lp_cfg); + if (ret < 0) { + pr_err("%s: AFE loopback config failed for src_port %d, dst_port %d\n", + __func__, src_port, dst_port); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_loopback_gain(u16 port_id, u16 volume) +{ + struct afe_port_cmd_set_param set_param; + int ret = 0; + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + /* RX ports numbers are even .TX ports numbers are odd. */ + if (port_id % 2 == 0) { + pr_err("%s: Failed : afe loopback gain only for TX ports." + " port_id %d\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + pr_debug("%s: %d %hX\n", __func__, port_id, volume); + + set_param.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + set_param.hdr.pkt_size = sizeof(set_param); + set_param.hdr.src_port = 0; + set_param.hdr.dest_port = 0; + set_param.hdr.token = 0; + set_param.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + + set_param.port_id = port_id; + set_param.payload_size = sizeof(struct afe_param_payload); + set_param.payload_address = 0; + + set_param.payload.module_id = AFE_MODULE_ID_PORT_INFO; + set_param.payload.param_id = AFE_PARAM_ID_LOOPBACK_GAIN; + set_param.payload.param_size = sizeof(struct afe_param_loopback_gain); + set_param.payload.reserved = 0; + + set_param.payload.param.loopback_gain.gain = volume; + set_param.payload.param.loopback_gain.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &set_param); + if (ret < 0) { + pr_err("%s: AFE param set failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_apply_gain(u16 port_id, u16 gain) +{ + struct afe_port_gain_command set_gain; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is not opened\n", __func__); + ret = -EPERM; + goto fail_cmd; + } + + if (afe_validate_port(port_id) < 0) { + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + /* RX ports numbers are even .TX ports numbers are odd. */ + if (port_id % 2 == 0) { + pr_err("%s: Failed : afe apply gain only for TX ports." + " port_id %d\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + pr_debug("%s: %d %hX\n", __func__, port_id, gain); + + set_gain.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + set_gain.hdr.pkt_size = sizeof(set_gain); + set_gain.hdr.src_port = 0; + set_gain.hdr.dest_port = 0; + set_gain.hdr.token = 0; + set_gain.hdr.opcode = AFE_PORT_CMD_APPLY_GAIN; + + set_gain.port_id = port_id; + set_gain.gain = gain; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &set_gain); + if (ret < 0) { + pr_err("%s: AFE Gain set failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_pseudo_port_start_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + return -ENODEV; + } + + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PSEUDOPORT_CMD_START; + start.port_id = port_id; + start.timing = 1; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed %d\n", + __func__, port_id, ret); + return -EINVAL; + } + return 0; +} + +int afe_start_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PSEUDOPORT_CMD_START; + start.port_id = port_id; + start.timing = 1; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed %d\n", + __func__, port_id, ret); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +int afe_pseudo_port_stop_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PSEUDOPORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + if (ret < 0) { + pr_err("%s: AFE close failed %d\n", __func__, ret); + return -EINVAL; + } + + return 0; + +} + +int afe_stop_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PSEUDOPORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + if (ret < 0) { + pr_err("%s: AFE close failed %d\n", __func__, ret); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + struct afe_cmd_memory_map mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_MAP; + mregion.phy_addr = dma_addr_p; + mregion.mem_sz = dma_buf_sz; + mregion.mem_id = 0; + mregion.rsvd = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + + return 0; +} + +int afe_cmd_memory_map_nowait(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + struct afe_cmd_memory_map mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_MAP; + mregion.phy_addr = dma_addr_p; + mregion.mem_sz = dma_buf_sz; + mregion.mem_id = 0; + mregion.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_cmd_memory_unmap(u32 dma_addr_p) +{ + int ret = 0; + struct afe_cmd_memory_unmap mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_UNMAP; + mregion.phy_addr = dma_addr_p; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_cmd_memory_unmap_nowait(u32 dma_addr_p) +{ + int ret = 0; + struct afe_cmd_memory_unmap mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_MEMORY_UNMAP; + mregion.phy_addr = dma_addr_p; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data) +{ + int ret = 0; + struct afe_cmd_reg_rtport rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = cb; + this_afe.tx_private_data = private_data; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = cb; + this_afe.rx_private_data = private_data; + } + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 1; + rtproxy.hdr.dest_port = 1; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_REG_RTPORT; + rtproxy.port_id = port_id; + rtproxy.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE reg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_unregister_get_events(u16 port_id) +{ + int ret = 0; + struct afe_cmd_unreg_rtport rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 0; + rtproxy.hdr.dest_port = 0; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_UNREG_RTPORT; + rtproxy.port_id = port_id; + rtproxy.rsvd = 0; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = NULL; + this_afe.tx_private_data = NULL; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = NULL; + this_afe.rx_private_data = NULL; + } + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE enable Unreg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_rt_proxy_port_write(u32 buf_addr_p, int bytes) +{ + int ret = 0; + struct afe_cmd_rtport_wr afecmd_wr; + + if (this_afe.apr == NULL) { + pr_err("%s:register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_wr.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_wr.hdr.pkt_size = sizeof(afecmd_wr); + afecmd_wr.hdr.src_port = 0; + afecmd_wr.hdr.dest_port = 0; + afecmd_wr.hdr.token = 0; + afecmd_wr.hdr.opcode = AFE_SERVICE_CMD_RTPORT_WR; + afecmd_wr.buf_addr = (uint32_t)buf_addr_p; + afecmd_wr.port_id = RT_PROXY_PORT_001_TX; + afecmd_wr.bytes_avail = bytes; + afecmd_wr.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_wr); + if (ret < 0) { + pr_err("%s: AFE rtproxy write to port 0x%x failed %d\n", + __func__, afecmd_wr.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; + +} + +int afe_rt_proxy_port_read(u32 buf_addr_p, int bytes) +{ + int ret = 0; + struct afe_cmd_rtport_rd afecmd_rd; + + if (this_afe.apr == NULL) { + pr_err("%s: register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_rd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_rd.hdr.pkt_size = sizeof(afecmd_rd); + afecmd_rd.hdr.src_port = 0; + afecmd_rd.hdr.dest_port = 0; + afecmd_rd.hdr.token = 0; + afecmd_rd.hdr.opcode = AFE_SERVICE_CMD_RTPORT_RD; + afecmd_rd.buf_addr = (uint32_t)buf_addr_p; + afecmd_rd.port_id = RT_PROXY_PORT_001_RX; + afecmd_rd.bytes_avail = bytes; + afecmd_rd.rsvd = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_rd); + if (ret < 0) { + pr_err("%s: AFE rtproxy read cmd to port 0x%x failed %d\n", + __func__, afecmd_rd.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_afelb; +static struct dentry *debugfs_afelb_gain; + +static int afe_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_info("debug intf %s\n", (char *) file->private_data); + return 0; +} + +static int afe_get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } else + return -EINVAL; + } + return 0; +} +#define AFE_LOOPBACK_ON (1) +#define AFE_LOOPBACK_OFF (0) +static ssize_t afe_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char lbuf[32]; + int rc; + unsigned long param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strcmp(lb_str, "afe_loopback")) { + rc = afe_get_parameters(lbuf, param, 3); + if (!rc) { + pr_info("%s %lu %lu %lu\n", lb_str, param[0], param[1], + param[2]); + + if ((param[0] != AFE_LOOPBACK_ON) && (param[0] != + AFE_LOOPBACK_OFF)) { + pr_err("%s: Error, parameter 0 incorrect\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + if ((afe_validate_port(param[1]) < 0) || + (afe_validate_port(param[2])) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + } + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback(param[0], param[1], param[2]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + + } else if (!strcmp(lb_str, "afe_loopback_gain")) { + rc = afe_get_parameters(lbuf, param, 2); + if (!rc) { + pr_info("%s %lu %lu\n", lb_str, param[0], param[1]); + + if (afe_validate_port(param[0]) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + + if (param[1] < 0 || param[1] > 100) { + pr_err("%s: Error, volume shoud be 0 to 100" + " percentage param = %lu\n", + __func__, param[1]); + rc = -EINVAL; + goto afe_error; + } + + param[1] = (Q6AFE_MAX_VOLUME * param[1]) / 100; + + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback_gain(param[0], param[1]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + } + +afe_error: + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations afe_debug_fops = { + .open = afe_debug_open, + .write = afe_debug_write +}; +#endif +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain) +{ + struct afe_port_sidetone_command cmd_sidetone; + int ret = 0; + + pr_info("%s: tx_port_id:%d rx_port_id:%d enable:%d gain:%d\n", __func__, + tx_port_id, rx_port_id, enable, gain); + cmd_sidetone.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cmd_sidetone.hdr.pkt_size = sizeof(cmd_sidetone); + cmd_sidetone.hdr.src_port = 0; + cmd_sidetone.hdr.dest_port = 0; + cmd_sidetone.hdr.token = 0; + cmd_sidetone.hdr.opcode = AFE_PORT_CMD_SIDETONE_CTL; + cmd_sidetone.tx_port_id = tx_port_id; + cmd_sidetone.rx_port_id = rx_port_id; + cmd_sidetone.gain = gain; + cmd_sidetone.enable = enable; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &cmd_sidetone); + if (ret < 0) { + pr_err("%s: AFE sidetone failed for tx_port:%d rx_port:%d\n", + __func__, tx_port_id, rx_port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_port_stop_nowait(int port_id) +{ + struct afe_port_stop_command stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + port_id = afe_convert_virtual_to_portid(port_id); + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (ret == -ENETRESET) { + pr_info("%s: Need to reset, calling APR deregister", __func__); + return apr_deregister(this_afe.apr); + } else if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + } + +fail_cmd: + return ret; + +} + +int afe_close(int port_id) +{ + struct afe_port_stop_command stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + port_id = afe_convert_virtual_to_portid(port_id); + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (ret == -ENETRESET) { + pr_info("%s: Need to reset, calling APR deregister", __func__); + return apr_deregister(this_afe.apr); + } + + if (ret < 0) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + return ret; +} + +static int __init afe_init(void) +{ + init_waitqueue_head(&this_afe.wait); + atomic_set(&this_afe.state, 0); + atomic_set(&this_afe.status, 0); + this_afe.apr = NULL; +#ifdef CONFIG_DEBUG_FS + debugfs_afelb = debugfs_create_file("afe_loopback", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback", + &afe_debug_fops); + + debugfs_afelb_gain = debugfs_create_file("afe_loopback_gain", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback_gain", + &afe_debug_fops); + + +#endif + return 0; +} + +static void __exit afe_exit(void) +{ + int i; +#ifdef CONFIG_DEBUG_FS + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_afelb_gain) + debugfs_remove(debugfs_afelb_gain); +#endif + for (i = 0; i < MAX_AUDPROC_TYPES; i++) { + if (afe_cal_addr[i].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[i].cal_paddr); + } +} + +device_initcall(afe_init); +__exitcall(afe_exit); diff --git a/sound/soc/msm/qdsp6/q6asm.c b/sound/soc/msm/qdsp6/q6asm.c new file mode 100644 index 000000000000..03b5bccbcb6a --- /dev/null +++ b/sound/soc/msm/qdsp6/q6asm.c @@ -0,0 +1,3533 @@ + +/* + * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + + +#define TRUE 0x01 +#define FALSE 0x00 +#define READDONE_IDX_STATUS 0 +#define READDONE_IDX_BUFFER 1 +#define READDONE_IDX_SIZE 2 +#define READDONE_IDX_OFFSET 3 +#define READDONE_IDX_MSW_TS 4 +#define READDONE_IDX_LSW_TS 5 +#define READDONE_IDX_FLAGS 6 +#define READDONE_IDX_NUMFRAMES 7 +#define READDONE_IDX_ID 8 +#ifdef CONFIG_DEBUG_FS +#define OUT_BUFFER_SIZE 56 +#define IN_BUFFER_SIZE 24 +#endif +static DEFINE_MUTEX(session_lock); + +/* session id: 0 reserved */ +static struct audio_client *session[SESSION_MAX+1]; +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv); +static int32_t q6asm_callback(struct apr_client_data *data, void *priv); +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); + +static void q6asm_reset_buf_state(struct audio_client *ac); + +#ifdef CONFIG_DEBUG_FS +static struct timeval out_cold_tv; +static struct timeval out_warm_tv; +static struct timeval out_cont_tv; +static struct timeval in_cont_tv; +static long out_enable_flag; +static long in_enable_flag; +static struct dentry *out_dentry; +static struct dentry *in_dentry; +static int in_cont_index; +/*This var is used to keep track of first write done for cold output latency */ +static int out_cold_index; +static char *out_buffer; +static char *in_buffer; +static int audio_output_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_output_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(out_buffer, OUT_BUFFER_SIZE, "%ld,%ld,%ld,%ld,%ld,%ld,",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec, out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec, out_cont_tv.tv_sec, out_cont_tv.tv_usec); + return simple_read_from_buffer(buf, OUT_BUFFER_SIZE, ppos, + out_buffer, OUT_BUFFER_SIZE); +} +static ssize_t audio_output_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + + out_cold_index = 0; + + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &out_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_output_latency_debug_fops = { + .open = audio_output_latency_dbgfs_open, + .read = audio_output_latency_dbgfs_read, + .write = audio_output_latency_dbgfs_write +}; + +static int audio_input_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_input_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(in_buffer, IN_BUFFER_SIZE, "%ld,%ld,",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + return simple_read_from_buffer(buf, IN_BUFFER_SIZE, ppos, + in_buffer, IN_BUFFER_SIZE); +} +static ssize_t audio_input_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &in_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_input_latency_debug_fops = { + .open = audio_input_latency_dbgfs_open, + .read = audio_input_latency_dbgfs_read, + .write = audio_input_latency_dbgfs_write +}; +#endif +struct asm_mmap { + atomic_t ref_cnt; + atomic_t cmd_state; + wait_queue_head_t cmd_wait; + void *apr; +}; + +static struct asm_mmap this_mmap; + +static int q6asm_session_alloc(struct audio_client *ac) +{ + int n; + mutex_lock(&session_lock); + for (n = 1; n <= SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void q6asm_session_free(struct audio_client *ac) +{ + pr_debug("%s: sessionid[%d]\n", __func__, ac->session); + rtac_remove_popp_from_adm_devices(ac->session); + mutex_lock(&session_lock); + session[ac->session] = 0; + mutex_unlock(&session_lock); + ac->session = 0; + return; +} + +int q6asm_audio_client_buf_free(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap_regions(ac, dir, + port->buf[0].size, + port->max_buf_cnt); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + while (cnt >= 0) { + if (port->buf[cnt].data) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_kernel(port->buf[cnt].client, + port->buf[cnt].handle); + ion_free(port->buf[cnt].client, + port->buf[cnt].handle); + ion_client_destroy(port->buf[cnt].client); +#else + pr_debug("%s:data[%p]phys[%p][%p] cnt[%d]" + "mem_buffer[%p]\n", + __func__, (void *)port->buf[cnt].data, + (void *)port->buf[cnt].phys, + (void *)&port->buf[cnt].phys, cnt, + (void *)port->buf[cnt].mem_buffer); + if (IS_ERR((void *)port->buf[cnt].mem_buffer)) + pr_err("%s:mem buffer invalid, error =" + "%ld\n", __func__, + PTR_ERR((void *)port->buf[cnt].mem_buffer)); + else { + if (iounmap( + port->buf[cnt].mem_buffer) < 0) + pr_err("%s: unmap buffer" + " failed\n", __func__); + } + free_contiguous_memory_by_paddr( + port->buf[cnt].phys); + +#endif + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + --(port->max_buf_cnt); + } + --cnt; + } + kfree(port->buf); + port->buf = NULL; + } + mutex_unlock(&ac->cmd_lock); + return 0; +} + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap(ac, port->buf[0].phys, dir); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + if (port->buf[0].data) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + ion_unmap_kernel(port->buf[0].client, port->buf[0].handle); + ion_free(port->buf[0].client, port->buf[0].handle); + ion_client_destroy(port->buf[0].client); + pr_debug("%s:data[%p]phys[%p][%p]" + ", client[%p] handle[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].client, + (void *)port->buf[0].handle); +#else + pr_debug("%s:data[%p]phys[%p][%p]" + "mem_buffer[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].mem_buffer); + if (IS_ERR((void *)port->buf[0].mem_buffer)) + pr_err("%s:mem buffer invalid, error =" + "%ld\n", __func__, + PTR_ERR((void *)port->buf[0].mem_buffer)); + else { + if (iounmap( + port->buf[0].mem_buffer) < 0) + pr_err("%s: unmap buffer" + " failed\n", __func__); + } + free_contiguous_memory_by_paddr(port->buf[0].phys); +#endif + } + + while (cnt >= 0) { + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + cnt--; + } + port->max_buf_cnt = 0; + kfree(port->buf); + port->buf = NULL; + mutex_unlock(&ac->cmd_lock); + return 0; +} + +void q6asm_audio_client_free(struct audio_client *ac) +{ + int loopcnt; + struct audio_port_data *port; + if (!ac || !ac->session) + return; + pr_debug("%s: Session id %d\n", __func__, ac->session); + if (ac->io_mode == SYNC_IO_MODE) { + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + if (!port->buf) + continue; + pr_debug("%s:loopcnt = %d\n", __func__, loopcnt); + q6asm_audio_client_buf_free(loopcnt, ac); + } + } + + apr_deregister(ac->apr); + q6asm_session_free(ac); + + pr_debug("%s: APR De-Register\n", __func__); + if (atomic_read(&this_mmap.ref_cnt) <= 0) { + pr_err("%s: APR Common Port Already Closed\n", __func__); + goto done; + } + + atomic_dec(&this_mmap.ref_cnt); + if (atomic_read(&this_mmap.ref_cnt) == 0) { + apr_deregister(this_mmap.apr); + pr_debug("%s:APR De-Register common port\n", __func__); + } +done: + kfree(ac); + return; +} + +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode) +{ + if (ac == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + if ((mode == ASYNC_IO_MODE) || (mode == SYNC_IO_MODE)) { + ac->io_mode = mode; + pr_debug("%s:Set Mode to %d\n", __func__, ac->io_mode); + return 0; + } else { + pr_err("%s:Not an valid IO Mode:%d\n", __func__, ac->io_mode); + return -EINVAL; + } +} + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv) +{ + struct audio_client *ac; + int n; + int lcnt = 0; + + ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL); + if (!ac) + return NULL; + n = q6asm_session_alloc(ac); + if (n <= 0) + goto fail_session; + ac->session = n; + ac->cb = cb; + ac->priv = priv; + ac->io_mode = SYNC_IO_MODE; + ac->apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_callback,\ + ((ac->session) << 8 | 0x0001),\ + ac); + + if (ac->apr == NULL) { + pr_err("%s Registration with APR failed\n", __func__); + goto fail; + } + rtac_set_asm_handle(n, ac->apr); + + pr_debug("%s Registering the common port with APR\n", __func__); + if (atomic_read(&this_mmap.ref_cnt) == 0) { + this_mmap.apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_mmapcallback,\ + 0x0FFFFFFFF, &this_mmap); + if (this_mmap.apr == NULL) { + pr_debug("%s Unable to register \ + APR ASM common port \n", __func__); + goto fail; + } + } + + atomic_inc(&this_mmap.ref_cnt); + init_waitqueue_head(&ac->cmd_wait); + init_waitqueue_head(&ac->time_wait); + atomic_set(&ac->time_flag, 1); + mutex_init(&ac->cmd_lock); + for (lcnt = 0; lcnt <= OUT; lcnt++) { + mutex_init(&ac->port[lcnt].lock); + spin_lock_init(&ac->port[lcnt].dsp_lock); + } + atomic_set(&ac->cmd_state, 0); + + pr_debug("%s: session[%d]\n", __func__, ac->session); + + return ac; +fail: + q6asm_audio_client_free(ac); + return NULL; +fail_session: + kfree(ac); + return NULL; +} + +struct audio_client *q6asm_get_audio_client(int session_id) +{ + if ((session_id <= 0) || (session_id > SESSION_MAX)) { + pr_err("%s: invalid session: %d\n", __func__, session_id); + goto err; + } + + if (!session[session_id]) { + pr_err("%s: session not active: %d\n", __func__, session_id); + goto err; + } + + return session[session_id]; +err: + return NULL; +} + +int q6asm_audio_client_buf_alloc(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int len; +#endif + + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->io_mode == SYNC_IO_MODE) { + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + + while (cnt < bufcnt) { + if (bufsz > 0) { + if (!buf[cnt].data) { +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + buf[cnt].client = msm_ion_client_create + (UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *) + buf[cnt].client)) { + pr_err("%s: ION create client" + " for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].handle = ion_alloc + (buf[cnt].client, bufsz, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) + buf[cnt].handle)) { + pr_err("%s: ION memory" + " allocation for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + rc = ion_phys(buf[cnt].client, + buf[cnt].handle, + (ion_phys_addr_t *) + &buf[cnt].phys, + (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical" + " for AUDIO failed, rc = %d\n", + __func__, rc); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[cnt].data = ion_map_kernel + (buf[cnt].client, buf[cnt].handle, + 0); + if (IS_ERR_OR_NULL((void *) + buf[cnt].data)) { + pr_err("%s: ION memory" + " mapping for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + memset((void *)buf[cnt].data, 0, bufsz); +#else + unsigned int flags = 0; + buf[cnt].phys = + allocate_contiguous_ebi_nomap(bufsz, + SZ_4K); + if (!buf[cnt].phys) { + pr_err("%s:Buf alloc failed " + " size=%d\n", __func__, + bufsz); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].mem_buffer = + ioremap(buf[cnt].phys, bufsz); + if (IS_ERR( + (void *)buf[cnt].mem_buffer)) { + pr_err("%s:map_buffer failed," + "error = %ld\n", + __func__, PTR_ERR((void *)buf[cnt].mem_buffer)); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].data = + buf[cnt].mem_buffer; + if (!buf[cnt].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } +#endif + buf[cnt].used = 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]\n", + __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys); + cnt++; + } + } + } + ac->port[dir].max_buf_cnt = cnt; + + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map_regions(ac, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + } + return 0; +fail: + q6asm_audio_client_buf_free(dir, ac); + return -EINVAL; +} + +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + int len; +#else + int flags = 0; +#endif + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", + __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + +#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION + buf[0].client = msm_ion_client_create(UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *)buf[0].client)) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[0].handle = ion_alloc(buf[0].client, bufsz * bufcnt, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) buf[0].handle)) { + pr_err("%s: ION memory allocation for AUDIO failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + rc = ion_phys(buf[0].client, buf[0].handle, + (ion_phys_addr_t *)&buf[0].phys, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].data = ion_map_kernel(buf[0].client, buf[0].handle, 0); + if (IS_ERR_OR_NULL((void *) buf[0].data)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + memset((void *)buf[0].data, 0, (bufsz * bufcnt)); +#else + buf[0].phys = allocate_contiguous_ebi_nomap(bufsz * bufcnt, + SZ_4K); + if (!buf[0].phys) { + pr_err("%s:Buf alloc failed " + " size=%d, bufcnt=%d\n", __func__, + bufsz, bufcnt); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].mem_buffer = ioremap(buf[0].phys, bufsz * bufcnt); + if (IS_ERR((void *)buf[cnt].mem_buffer)) { + pr_err("%s:map_buffer failed," + "error = %ld\n", + __func__, PTR_ERR((void *)buf[0].mem_buffer)); + + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[0].data = buf[0].mem_buffer; +#endif + if (!buf[0].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].used = dir ^ 1; + buf[0].size = bufsz; + buf[0].actual_size = bufsz; + cnt = 1; + while (cnt < bufcnt) { + if (bufsz > 0) { + buf[cnt].data = buf[0].data + (cnt * bufsz); + buf[cnt].phys = buf[0].phys + (cnt * bufsz); + if (!buf[cnt].data) { + pr_err("%s Buf alloc failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].used = dir ^ 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]\n", __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys); + } + cnt++; + } + ac->port[dir].max_buf_cnt = cnt; + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map(ac, buf[0].phys, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + return 0; +fail: + q6asm_audio_client_buf_free_contiguous(dir, ac); + return -EINVAL; +} + +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv) +{ + uint32_t token; + uint32_t *payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event is received: %d %d apr[%p]\n", + __func__, + data->reset_event, + data->reset_proc, + this_mmap.apr); + apr_reset(this_mmap.apr); + this_mmap.apr = NULL; + atomic_set(&this_mmap.cmd_state, 0); + return 0; + } + + pr_debug("%s:ptr0[0x%x]ptr1[0x%x]opcode[0x%x]" + "token[0x%x]payload_s[%d] src[%d] dest[%d]\n", __func__, + payload[0], payload[1], data->opcode, data->token, + data->payload_size, data->src_port, data->dest_port); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_SESSION_CMD_MEMORY_MAP: + case ASM_SESSION_CMD_MEMORY_UNMAP: + case ASM_SESSION_CMD_MEMORY_MAP_REGIONS: + case ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS: + pr_debug("%s:command[0x%x]success [0x%x]\n", + __func__, payload[0], payload[1]); + if (atomic_read(&this_mmap.cmd_state)) { + atomic_set(&this_mmap.cmd_state, 0); + wake_up(&this_mmap.cmd_wait); + } + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + } + return 0; +} + + +static int32_t q6asm_callback(struct apr_client_data *data, void *priv) +{ + int i = 0; + struct audio_client *ac = (struct audio_client *)priv; + uint32_t token; + unsigned long dsp_flags; + uint32_t *payload; + + + if ((ac == NULL) || (data == NULL)) { + pr_err("ac or priv NULL\n"); + return -EINVAL; + } + if (ac->session <= 0 || ac->session > 8) { + pr_err("%s:Session ID is invalid, session = %d\n", __func__, + ac->session); + return -EINVAL; + } + + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("q6asm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, ac->apr); + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + apr_reset(ac->apr); + return 0; + } + + pr_debug("%s: session[%d]opcode[0x%x] \ + token[0x%x]payload_s[%d] src[%d] dest[%d]\n", __func__, + ac->session, data->opcode, + data->token, data->payload_size, data->src_port, + data->dest_port); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_STREAM_CMD_SET_PP_PARAMS: + if (rtac_make_asm_callback(ac->session, payload, + data->payload_size)) + break; + case ASM_SESSION_CMD_PAUSE: + case ASM_DATA_CMD_EOS: + case ASM_STREAM_CMD_CLOSE: + case ASM_STREAM_CMD_FLUSH: + case ASM_SESSION_CMD_RUN: + case ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS: + case ASM_STREAM_CMD_FLUSH_READBUFS: + pr_debug("%s:Payload = [0x%x]\n", __func__, payload[0]); + if (token != ac->session) { + pr_err("%s:Invalid session[%d] rxed expected[%d]", + __func__, token, ac->session); + return -EINVAL; + } + case ASM_STREAM_CMD_OPEN_READ: + case ASM_STREAM_CMD_OPEN_WRITE: + case ASM_STREAM_CMD_OPEN_READWRITE: + case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + case ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED: + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ASM_DATA_EVENT_WRITE_DONE:{ + struct audio_port_data *port = &ac->port[IN]; + pr_debug("%s: Rxed opcode[0x%x] status[0x%x] token[%d]", + __func__, payload[0], payload[1], + data->token); + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", + __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (port->buf[data->token].phys != + payload[0]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[data->token].phys,\ + (void *)payload[0]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + return -EINVAL; + } + token = data->token; + port->buf[token].used = 1; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + /* For first Write done log the time and reset + out_cold_index*/ + if (out_cold_index != 1) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld \ + sec %ld microsec\n",\ + out_cold_tv.tv_sec,\ + out_cold_tv.tv_usec); + out_cold_index = 1; + } + pr_debug("out_enable_flag %ld",\ + out_enable_flag); + } +#endif + for (i = 0; i < port->max_buf_cnt; i++) + pr_debug("%d ", port->buf[i].used); + + } + break; + } + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + rtac_make_asm_callback(ac->session, payload, + data->payload_size); + break; + case ASM_DATA_EVENT_READ_DONE:{ + + struct audio_port_data *port = &ac->port[OUT]; +#ifdef CONFIG_DEBUG_FS + if (in_enable_flag) { + /* when in_cont_index == 7, DSP would be + * writing into the 8th 512 byte buffer and this + * timestamp is tapped here.Once done it then writes + * to 9th 512 byte buffer.These two buffers(8th, 9th) + * reach the test application in 5th iteration and that + * timestamp is tapped at user level. The difference + * of these two timestamps gives us the time between + * the time at which dsp started filling the sample + * required and when it reached the test application. + * Hence continuous input latency + */ + if (in_cont_index == 7) { + do_gettimeofday(&in_cont_tv); + pr_err("In_CONT:previous read buffer done \ + at %ld sec %ld microsec\n",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + } + } +#endif + pr_debug("%s:R-D: status=%d buff_add=%x act_size=%d offset=%d\n", + __func__, payload[READDONE_IDX_STATUS], + payload[READDONE_IDX_BUFFER], + payload[READDONE_IDX_SIZE], + payload[READDONE_IDX_OFFSET]); + pr_debug("%s:R-D:msw_ts=%d lsw_ts=%d flags=%d id=%d num=%d\n", + __func__, payload[READDONE_IDX_MSW_TS], + payload[READDONE_IDX_LSW_TS], + payload[READDONE_IDX_FLAGS], + payload[READDONE_IDX_ID], + payload[READDONE_IDX_NUMFRAMES]); +#ifdef CONFIG_DEBUG_FS + if (in_enable_flag) { + in_cont_index++; + } +#endif + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + token = data->token; + port->buf[token].used = 0; + if (port->buf[token].phys != + payload[READDONE_IDX_BUFFER]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[token].phys,\ + (void *)payload[READDONE_IDX_BUFFER]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + break; + } + port->buf[token].actual_size = + payload[READDONE_IDX_SIZE]; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + } + break; + } + case ASM_DATA_EVENT_EOS: + case ASM_DATA_CMDRSP_EOS: + pr_debug("%s:EOS ACK received: rxed opcode[0x%x]\n", + __func__, data->opcode); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("ASM_SESSION_EVENT_TX_OVERFLOW\n"); + break; + case ASM_SESSION_CMDRSP_GET_SESSION_TIME: + pr_debug("%s: ASM_SESSION_CMDRSP_GET_SESSION_TIME, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d\n", __func__, + payload[0], payload[1], payload[2]); + ac->time_stamp = (uint64_t)(((uint64_t)payload[1] << 32) | + payload[2]); + if (atomic_read(&ac->time_flag)) { + atomic_set(&ac->time_flag, 0); + wake_up(&ac->time_wait); + } + break; + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + case ASM_DATA_EVENT_ENC_SR_CM_NOTIFY: + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + payload[0], payload[1], payload[2], + payload[3]); + break; + } + if (ac->cb) + ac->cb(data->opcode, data->token, + data->payload, ac->priv); + + return 0; +} + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, uint32_t *size, + uint32_t *index) +{ + void *data; + unsigned char idx; + struct audio_port_data *port; + + if (!ac || ((dir != IN) && (dir != OUT))) + return NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->cpu_buf; + if (port->buf == NULL) { + pr_debug("%s:Buffer pointer null\n", __func__); + mutex_unlock(&port->lock); + return NULL; + } + /* dir 0: used = 0 means buf in use + dir 1: used = 1 means buf in use */ + if (port->buf[idx].used == dir) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_debug("%s:Next buf idx[0x%x] not available,\ + dir[%d]\n", __func__, idx, dir); + mutex_unlock(&port->lock); + return NULL; + } + *size = port->buf[idx].actual_size; + *index = port->cpu_buf; + data = port->buf[idx].data; + pr_debug("%s:session[%d]index[%d] data[%p]size[%d]\n", + __func__, + ac->session, + port->cpu_buf, + data, *size); + /* By default increase the cpu_buf cnt + user accesses this function,increase cpu + buf(to avoid another api)*/ + port->buf[idx].used = dir; + port->cpu_buf = ((port->cpu_buf + 1) & (port->max_buf_cnt - 1)); + mutex_unlock(&port->lock); + return data; + } + return NULL; +} + +void *q6asm_is_cpu_buf_avail_nolock(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *index) +{ + void *data; + unsigned char idx; + struct audio_port_data *port; + + if (!ac || ((dir != IN) && (dir != OUT))) + return NULL; + + port = &ac->port[dir]; + + idx = port->cpu_buf; + if (port->buf == NULL) { + pr_debug("%s:Buffer pointer null\n", __func__); + return NULL; + } + /* + * dir 0: used = 0 means buf in use + * dir 1: used = 1 means buf in use + */ + if (port->buf[idx].used == dir) { + /* + * To make it more robust, we could loop and get the + * next avail buf, its risky though + */ + pr_debug("%s:Next buf idx[0x%x] not available,\ + dir[%d]\n", __func__, idx, dir); + return NULL; + } + *size = port->buf[idx].actual_size; + *index = port->cpu_buf; + data = port->buf[idx].data; + pr_debug("%s:session[%d]index[%d] data[%p]size[%d]\n", + __func__, ac->session, port->cpu_buf, + data, *size); + /* + * By default increase the cpu_buf cnt + * user accesses this function,increase cpu + * buf(to avoid another api) + */ + port->buf[idx].used = dir; + port->cpu_buf = ((port->cpu_buf + 1) & (port->max_buf_cnt - 1)); + return data; +} + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac) +{ + int ret = -1; + struct audio_port_data *port; + uint32_t idx; + + if (!ac || (dir != OUT)) + return ret; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->dsp_buf; + + if (port->buf[idx].used == (dir ^ 1)) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_err("Next buf idx[0x%x] not available, dir[%d]\n", + idx, dir); + mutex_unlock(&port->lock); + return ret; + } + pr_debug("%s: session[%d]dsp_buf=%d cpu_buf=%d\n", __func__, + ac->session, port->dsp_buf, port->cpu_buf); + ret = ((port->dsp_buf != port->cpu_buf) ? 0 : -1); + mutex_unlock(&port->lock); + } + return ret; +} + +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("%s:session=%d pkt size=%d cmd_flg=%d\n", __func__, pkt_size, + cmd_flg, ac->session); + mutex_lock(&ac->cmd_lock); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + mutex_unlock(&ac->cmd_lock); + return; +} + +static void q6asm_add_mmaphdr(struct apr_hdr *hdr, uint32_t pkt_size, + uint32_t cmd_flg) +{ + pr_debug("%s:pkt size=%d cmd_flg=%d\n", __func__, pkt_size, cmd_flg); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr->src_port = 0; + hdr->dest_port = 0; + if (cmd_flg) { + hdr->token = 0; + atomic_set(&this_mmap.cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} + +int q6asm_open_read(struct audio_client *ac, + uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_read open; +#ifdef CONFIG_DEBUG_FS + in_cont_index = 0; +#endif + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s:session[%d]", __func__, ac->session); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READ; + /* Stream prio : High, provide meta info with encoded frames */ + open.src_endpoint = ASM_END_POINT_DEVICE_MATRIX; + + open.pre_proc_top = get_asm_topology(); + if (open.pre_proc_top == 0) + open.pre_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.uMode = STREAM_PRIORITY_HIGH; + open.format = LINEAR_PCM; + break; + case FORMAT_MULTI_CHANNEL_LINEAR_PCM: + open.uMode = STREAM_PRIORITY_HIGH; + open.format = MULTI_CHANNEL_PCM; + break; + case FORMAT_MPEG4_AAC: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = MPEG4_AAC; + break; + case FORMAT_V13K: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = V13K_FS; + break; + case FORMAT_EVRC: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = EVRC_FS; + break; + case FORMAT_AMRNB: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = AMRNB_FS; + break; + case FORMAT_AMRWB: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = AMRWB_FS; + break; + default: + pr_err("Invalid format[%d]\n", format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for OPEN_WRITE rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_write_compressed(struct audio_client *ac, uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_write_compressed open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s: session[%d] wr_format[0x%x]", __func__, ac->session, + format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_COMPRESSED; + + switch (format) { + case FORMAT_AC3: + open.format = AC3_DECODER; + break; + case FORMAT_EAC3: + open.format = EAC3_DECODER; + break; + case FORMAT_MP3: + open.format = MP3; + break; + case FORMAT_DTS: + open.format = DTS; + break; + case FORMAT_AAC: + open.format = MPEG4_AAC; + break; + case FORMAT_ATRAC: + open.format = ATRAC; + break; + case FORMAT_WMA_V10PRO: + open.format = WMA_V10PRO; + break; + case FORMAT_MAT: + open.format = MAT; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, format); + goto fail_cmd; + } + /*Below flag indicates the DSP that Compressed audio input + stream is not IEC 61937 or IEC 60958 packetizied*/ + open.flags = 0x00000000; + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("%s: open failed op[0x%x]rc[%d]\n", \ + __func__, open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for OPEN_WRITE rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_write(struct audio_client *ac, uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_write open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s: session[%d] wr_format[0x%x]", __func__, ac->session, + format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE; + open.uMode = STREAM_PRIORITY_HIGH; + /* source endpoint : matrix */ + open.sink_endpoint = ASM_END_POINT_DEVICE_MATRIX; + open.stream_handle = 0x00; + + open.post_proc_top = get_asm_topology(); + if (open.post_proc_top == 0) + open.post_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.format = LINEAR_PCM; + break; + case FORMAT_MULTI_CHANNEL_LINEAR_PCM: + open.format = MULTI_CHANNEL_PCM; + break; + case FORMAT_MPEG4_AAC: + open.format = MPEG4_AAC; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.format = MPEG4_MULTI_AAC; + break; + case FORMAT_WMA_V9: + open.format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.format = WMA_V10PRO; + break; + case FORMAT_MP3: + open.format = MP3; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("%s: open failed op[0x%x]rc[%d]\n", \ + __func__, open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for OPEN_WRITE rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_read_write open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d]", __func__, ac->session); + pr_debug("wr_format[0x%x]rd_format[0x%x]", + wr_format, rd_format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READWRITE; + + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_NORMAL; + /* source endpoint : matrix */ + open.post_proc_top = get_asm_topology(); + if (open.post_proc_top == 0) + open.post_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (wr_format) { + case FORMAT_LINEAR_PCM: + open.write_format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.write_format = MPEG4_AAC; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.write_format = MPEG4_MULTI_AAC; + break; + case FORMAT_WMA_V9: + open.write_format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.write_format = WMA_V10PRO; + break; + case FORMAT_AMRNB: + open.write_format = AMRNB_FS; + break; + case FORMAT_AMRWB: + open.write_format = AMRWB_FS; + break; + case FORMAT_V13K: + open.write_format = V13K_FS; + break; + case FORMAT_EVRC: + open.write_format = EVRC_FS; + break; + case FORMAT_EVRCB: + open.write_format = EVRCB_FS; + break; + case FORMAT_EVRCWB: + open.write_format = EVRCWB_FS; + break; + case FORMAT_MP3: + open.write_format = MP3; + break; + default: + pr_err("Invalid format[%d]\n", wr_format); + goto fail_cmd; + } + + switch (rd_format) { + case FORMAT_LINEAR_PCM: + open.read_format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.read_format = MPEG4_AAC; + break; + case FORMAT_V13K: + open.read_format = V13K_FS; + break; + case FORMAT_EVRC: + open.read_format = EVRC_FS; + break; + case FORMAT_AMRNB: + open.read_format = AMRNB_FS; + break; + case FORMAT_AMRWB: + open.read_format = AMRWB_FS; + break; + default: + pr_err("Invalid format[%d]\n", rd_format); + goto fail_cmd; + } + pr_debug("%s:rdformat[0x%x]wrformat[0x%x]\n", __func__, + open.read_format, open.write_format); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for OPEN_WRITE rc[%d]\n", rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_stream_cmd_run run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s session[%d]", __func__, ac->session); + q6asm_add_hdr(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN; + run.flags = flags; + run.msw_ts = msw_ts; + run.lsw_ts = lsw_ts; +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld sec %ld microsec\n",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec); + } +#endif + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("Commmand run failed[%d]", rc); + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for run success rc[%d]", rc); + goto fail_cmd; + } + + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_stream_cmd_run run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("session[%d]", ac->session); + q6asm_add_hdr_async(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN; + run.flags = flags; + run.msw_ts = msw_ts; + run.lsw_ts = lsw_ts; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("%s:Commmand run failed[%d]", __func__, rc); + return -EINVAL; + } + return 0; +} + + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, uint32_t mode, uint32_t format) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]SR[%d]ch[%d]bitrate[%d]mode[%d]" + "format[%d]", __func__, ac->session, frames_per_buf, + sample_rate, channels, bit_rate, mode, format); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = MPEG4_AAC; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_aac_read_cfg); + enc_cfg.enc_blk.cfg.aac.bitrate = bit_rate; + enc_cfg.enc_blk.cfg.aac.enc_mode = mode; + enc_cfg.enc_blk.cfg.aac.format = format; + enc_cfg.enc_blk.cfg.aac.ch_cfg = channels; + enc_cfg.enc_blk.cfg.aac.sample_rate = sample_rate; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + + int rc = 0; + + pr_debug("%s: Session %d, rate = %d, channels = %d\n", __func__, + ac->session, rate, channels); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + enc_cfg.enc_blk.frames_per_buf = 1; + enc_cfg.enc_blk.format_id = LINEAR_PCM; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_pcm_cfg); + enc_cfg.enc_blk.cfg.pcm.ch_cfg = channels; + enc_cfg.enc_blk.cfg.pcm.bits_per_sample = 16; + enc_cfg.enc_blk.cfg.pcm.sample_rate = rate; + enc_cfg.enc_blk.cfg.pcm.is_signed = 1; + enc_cfg.enc_blk.cfg.pcm.interleaved = 1; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd open failed\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", enc_cfg.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + + int rc = 0; + + pr_debug("%s: Session %d, rate = %d, channels = %d\n", __func__, + ac->session, rate, channels); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + enc_cfg.enc_blk.frames_per_buf = 1; + enc_cfg.enc_blk.format_id = MULTI_CHANNEL_PCM; + enc_cfg.enc_blk.cfg_size = + sizeof(struct asm_multi_channel_pcm_fmt_blk); + enc_cfg.enc_blk.cfg.mpcm.num_channels = channels; + enc_cfg.enc_blk.cfg.mpcm.bits_per_sample = 16; + enc_cfg.enc_blk.cfg.mpcm.sample_rate = rate; + enc_cfg.enc_blk.cfg.mpcm.is_signed = 1; + enc_cfg.enc_blk.cfg.mpcm.is_interleaved = 1; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[0] = PCM_CHANNEL_FL; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[1] = PCM_CHANNEL_FR; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[2] = PCM_CHANNEL_RB; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[3] = PCM_CHANNEL_LB; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[4] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[5] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[6] = 0; + enc_cfg.enc_blk.cfg.mpcm.channel_mapping[7] = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd open failed\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", enc_cfg.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps_enable) +{ + struct asm_stream_cmd_encdec_sbr sbrps; + + int rc = 0; + + pr_debug("%s: Session %d\n", __func__, ac->session); + + q6asm_add_hdr(ac, &sbrps.hdr, sizeof(sbrps), TRUE); + + sbrps.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + sbrps.param_id = ASM_ENABLE_SBR_PS; + sbrps.param_size = sizeof(struct asm_sbr_ps); + sbrps.sbr_ps.enable = sbr_ps_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &sbrps); + if (rc < 0) { + pr_err("Command opcode[0x%x]paramid[0x%x] failed\n", + ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_ENABLE_SBR_PS); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", sbrps.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right) +{ + struct asm_stream_cmd_encdec_dualmono dual_mono; + + int rc = 0; + + pr_debug("%s: Session %d, sce_left = %d, sce_right = %d\n", + __func__, ac->session, sce_left, sce_right); + + q6asm_add_hdr(ac, &dual_mono.hdr, sizeof(dual_mono), TRUE); + + dual_mono.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + dual_mono.param_id = ASM_CONFIGURE_DUAL_MONO; + dual_mono.param_size = sizeof(struct asm_dual_mono); + dual_mono.channel_map.sce_left = sce_left; + dual_mono.channel_map.sce_right = sce_right; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &dual_mono); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_CONFIGURE_DUAL_MONO); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout opcode[0x%x]\n", __func__, + dual_mono.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels) +{ + struct asm_stream_cmd_encdec_channelmap chan_map; + u8 *channel_mapping; + + int rc = 0; + + pr_debug("%s: Session %d, num_channels = %d\n", + __func__, ac->session, num_channels); + + q6asm_add_hdr(ac, &chan_map.hdr, sizeof(chan_map), TRUE); + + chan_map.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + chan_map.param_id = ASM_ENCDEC_DEC_CHAN_MAP; + chan_map.param_size = sizeof(struct asm_dec_chan_map); + chan_map.chan_map.num_channels = num_channels; + + channel_mapping = + chan_map.chan_map.channel_mapping; + + memset(channel_mapping, PCM_CHANNEL_NULL, MAX_CHAN_MAP_CHANNELS); + if (num_channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (num_channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (num_channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + num_channels); + rc = -EINVAL; + goto fail_cmd; + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) &chan_map); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_ENCDEC_DEC_CHAN_MAP); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout opcode[0x%x]\n", __func__, + chan_map.hdr.opcode); + rc = -ETIMEDOUT; + goto fail_cmd; + } + return 0; +fail_cmd: + return rc; +} + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x] \ + reduced_rate_level[0x%4x]rate_modulation_cmd[0x%4x]", __func__, + ac->session, frames_per_buf, min_rate, max_rate, + reduced_rate_level, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = V13K_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_qcelp13_read_cfg); + enc_cfg.enc_blk.cfg.qcelp13.min_rate = min_rate; + enc_cfg.enc_blk.cfg.qcelp13.max_rate = max_rate; + enc_cfg.enc_blk.cfg.qcelp13.reduced_rate_level = reduced_rate_level; + enc_cfg.enc_blk.cfg.qcelp13.rate_modulation_cmd = rate_modulation_cmd; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x] \ + rate_modulation_cmd[0x%4x]", __func__, ac->session, + frames_per_buf, min_rate, max_rate, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = EVRC_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_evrc_read_cfg); + enc_cfg.enc_blk.cfg.evrc.min_rate = min_rate; + enc_cfg.enc_blk.cfg.evrc.max_rate = max_rate; + enc_cfg.enc_blk.cfg.evrc.rate_modulation_cmd = rate_modulation_cmd; + enc_cfg.enc_blk.cfg.evrc.reserved = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]band_mode[0x%4x]dtx_enable[0x%4x]", + __func__, ac->session, frames_per_buf, band_mode, dtx_enable); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = AMRNB_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_amrnb_read_cfg); + enc_cfg.enc_blk.cfg.amrnb.mode = band_mode; + enc_cfg.enc_blk.cfg.amrnb.dtx_mode = dtx_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrwb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]band_mode[0x%4x]dtx_enable[0x%4x]", + __func__, ac->session, frames_per_buf, band_mode, dtx_enable); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = AMRWB_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_amrwb_read_cfg); + enc_cfg.enc_blk.cfg.amrwb.mode = band_mode; + enc_cfg.enc_blk.cfg.amrwb.dtx_mode = dtx_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, rate, + channels); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = LINEAR_PCM; + fmt.cfg_size = sizeof(struct asm_pcm_cfg); + fmt.write_cfg.pcm_cfg.ch_cfg = channels; + fmt.write_cfg.pcm_cfg.bits_per_sample = 16; + fmt.write_cfg.pcm_cfg.sample_rate = rate; + fmt.write_cfg.pcm_cfg.is_signed = 1; + fmt.write_cfg.pcm_cfg.interleaved = 1; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_media_format_update fmt; + u8 *channel_mapping; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, rate, + channels); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = MULTI_CHANNEL_PCM; + fmt.cfg_size = sizeof(struct asm_multi_channel_pcm_fmt_blk); + fmt.write_cfg.multi_ch_pcm_cfg.num_channels = channels; + fmt.write_cfg.multi_ch_pcm_cfg.bits_per_sample = 16; + fmt.write_cfg.multi_ch_pcm_cfg.sample_rate = rate; + fmt.write_cfg.multi_ch_pcm_cfg.is_signed = 1; + fmt.write_cfg.multi_ch_pcm_cfg.is_interleaved = 1; + channel_mapping = + fmt.write_cfg.multi_ch_pcm_cfg.channel_mapping; + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, + cfg->sample_rate, cfg->ch_cfg); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = MPEG4_AAC; + fmt.cfg_size = sizeof(struct asm_aac_cfg); + fmt.write_cfg.aac_cfg.format = cfg->format; + fmt.write_cfg.aac_cfg.aot = cfg->aot; + fmt.write_cfg.aac_cfg.ep_config = cfg->ep_config; + fmt.write_cfg.aac_cfg.section_data_resilience = + cfg->section_data_resilience; + fmt.write_cfg.aac_cfg.scalefactor_data_resilience = + cfg->scalefactor_data_resilience; + fmt.write_cfg.aac_cfg.spectral_data_resilience = + cfg->spectral_data_resilience; + fmt.write_cfg.aac_cfg.ch_cfg = cfg->ch_cfg; + fmt.write_cfg.aac_cfg.sample_rate = cfg->sample_rate; + pr_info("%s:format=%x cfg_size=%d aac-cfg=%x aot=%d ch=%d sr=%d\n", + __func__, fmt.format, fmt.cfg_size, + fmt.write_cfg.aac_cfg.format, + fmt.write_cfg.aac_cfg.aot, + fmt.write_cfg.aac_cfg.ch_cfg, + fmt.write_cfg.aac_cfg.sample_rate); + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + + +int q6asm_media_format_block_multi_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, + cfg->sample_rate, cfg->ch_cfg); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = MPEG4_MULTI_AAC; + fmt.cfg_size = sizeof(struct asm_aac_cfg); + fmt.write_cfg.aac_cfg.format = cfg->format; + fmt.write_cfg.aac_cfg.aot = cfg->aot; + fmt.write_cfg.aac_cfg.ep_config = cfg->ep_config; + fmt.write_cfg.aac_cfg.section_data_resilience = + cfg->section_data_resilience; + fmt.write_cfg.aac_cfg.scalefactor_data_resilience = + cfg->scalefactor_data_resilience; + fmt.write_cfg.aac_cfg.spectral_data_resilience = + cfg->spectral_data_resilience; + fmt.write_cfg.aac_cfg.ch_cfg = cfg->ch_cfg; + fmt.write_cfg.aac_cfg.sample_rate = cfg->sample_rate; + pr_info("%s:format=%x cfg_size=%d aac-cfg=%x aot=%d ch=%d sr=%d\n", + __func__, fmt.format, fmt.cfg_size, + fmt.write_cfg.aac_cfg.format, + fmt.write_cfg.aac_cfg.aot, + fmt.write_cfg.aac_cfg.ch_cfg, + fmt.write_cfg.aac_cfg.sample_rate); + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + + + +int q6asm_media_format_block(struct audio_client *ac, uint32_t format) +{ + + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d] format[0x%x]\n", __func__, + ac->session, format); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + switch (format) { + case FORMAT_V13K: + fmt.format = V13K_FS; + break; + case FORMAT_EVRC: + fmt.format = EVRC_FS; + break; + case FORMAT_AMRWB: + fmt.format = AMRWB_FS; + break; + case FORMAT_AMRNB: + fmt.format = AMRNB_FS; + break; + case FORMAT_MP3: + fmt.format = MP3; + break; + default: + pr_err("Invalid format[%d]\n", format); + goto fail_cmd; + } + fmt.cfg_size = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg) +{ + struct asm_stream_media_format_update fmt; + struct asm_wma_cfg *wma_cfg = (struct asm_wma_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d],\ + balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x]\n", + ac->session, wma_cfg->format_tag, wma_cfg->sample_rate, + wma_cfg->ch_cfg, wma_cfg->avg_bytes_per_sec, + wma_cfg->block_align, wma_cfg->valid_bits_per_sample, + wma_cfg->ch_mask, wma_cfg->encode_opt); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = WMA_V9; + fmt.cfg_size = sizeof(struct asm_wma_cfg); + fmt.write_cfg.wma_cfg.format_tag = wma_cfg->format_tag; + fmt.write_cfg.wma_cfg.ch_cfg = wma_cfg->ch_cfg; + fmt.write_cfg.wma_cfg.sample_rate = wma_cfg->sample_rate; + fmt.write_cfg.wma_cfg.avg_bytes_per_sec = wma_cfg->avg_bytes_per_sec; + fmt.write_cfg.wma_cfg.block_align = wma_cfg->block_align; + fmt.write_cfg.wma_cfg.valid_bits_per_sample = + wma_cfg->valid_bits_per_sample; + fmt.write_cfg.wma_cfg.ch_mask = wma_cfg->ch_mask; + fmt.write_cfg.wma_cfg.encode_opt = wma_cfg->encode_opt; + fmt.write_cfg.wma_cfg.adv_encode_opt = 0; + fmt.write_cfg.wma_cfg.adv_encode_opt2 = 0; + fmt.write_cfg.wma_cfg.drc_peak_ref = 0; + fmt.write_cfg.wma_cfg.drc_peak_target = 0; + fmt.write_cfg.wma_cfg.drc_ave_ref = 0; + fmt.write_cfg.wma_cfg.drc_ave_target = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg) +{ + struct asm_stream_media_format_update fmt; + struct asm_wmapro_cfg *wmapro_cfg = (struct asm_wmapro_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d]," + "balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x],\ + adv_enc_opt[0x%4x], adv_enc_opt2[0x%8x]\n", + ac->session, wmapro_cfg->format_tag, wmapro_cfg->sample_rate, + wmapro_cfg->ch_cfg, wmapro_cfg->avg_bytes_per_sec, + wmapro_cfg->block_align, wmapro_cfg->valid_bits_per_sample, + wmapro_cfg->ch_mask, wmapro_cfg->encode_opt, + wmapro_cfg->adv_encode_opt, wmapro_cfg->adv_encode_opt2); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = WMA_V10PRO; + fmt.cfg_size = sizeof(struct asm_wmapro_cfg); + fmt.write_cfg.wmapro_cfg.format_tag = wmapro_cfg->format_tag; + fmt.write_cfg.wmapro_cfg.ch_cfg = wmapro_cfg->ch_cfg; + fmt.write_cfg.wmapro_cfg.sample_rate = wmapro_cfg->sample_rate; + fmt.write_cfg.wmapro_cfg.avg_bytes_per_sec = + wmapro_cfg->avg_bytes_per_sec; + fmt.write_cfg.wmapro_cfg.block_align = wmapro_cfg->block_align; + fmt.write_cfg.wmapro_cfg.valid_bits_per_sample = + wmapro_cfg->valid_bits_per_sample; + fmt.write_cfg.wmapro_cfg.ch_mask = wmapro_cfg->ch_mask; + fmt.write_cfg.wmapro_cfg.encode_opt = wmapro_cfg->encode_opt; + fmt.write_cfg.wmapro_cfg.adv_encode_opt = wmapro_cfg->adv_encode_opt; + fmt.write_cfg.wmapro_cfg.adv_encode_opt2 = wmapro_cfg->adv_encode_opt2; + fmt.write_cfg.wmapro_cfg.drc_peak_ref = 0; + fmt.write_cfg.wmapro_cfg.drc_peak_target = 0; + fmt.write_cfg.wmapro_cfg.drc_ave_ref = 0; + fmt.write_cfg.wmapro_cfg.drc_ave_target = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_map mem_map; + int rc = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + mem_map.hdr.opcode = ASM_SESSION_CMD_MEMORY_MAP; + + mem_map.buf_add = buf_add; + mem_map.buf_size = bufsz * bufcnt; + mem_map.mempool_id = 0; /* EBI */ + mem_map.reserved = 0; + + q6asm_add_mmaphdr(&mem_map.hdr, + sizeof(struct asm_stream_cmd_memory_map), TRUE); + + pr_debug("buf add[%x] buf_add_parameter[%x]\n", + mem_map.buf_add, buf_add); + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) &mem_map); + if (rc < 0) { + pr_err("mem_map op[0x%x]rc[%d]\n", + mem_map.hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, int dir) +{ + struct asm_stream_cmd_memory_unmap mem_unmap; + int rc = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + q6asm_add_mmaphdr(&mem_unmap.hdr, + sizeof(struct asm_stream_cmd_memory_unmap), TRUE); + mem_unmap.hdr.opcode = ASM_SESSION_CMD_MEMORY_UNMAP; + mem_unmap.buf_add = buf_add; + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) &mem_unmap); + if (rc < 0) { + pr_err("mem_unmap op[0x%x]rc[%d]\n", + mem_unmap.hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_lrchannel_gain_params *lrgain = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_lrchannel_gain_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_lrchannel_gain_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = L_R_CHANNEL_GAIN_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_lrchannel_gain_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + lrgain = (struct asm_lrchannel_gain_params *)payload; + + lrgain->left_gain = left_gain; + lrgain->right_gain = right_gain; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_map_regions *mmap_regions = NULL; + struct asm_memory_map_regions *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct asm_stream_cmd_memory_map_regions) + + sizeof(struct asm_memory_map_regions) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (mmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + mmap_regions = (struct asm_stream_cmd_memory_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(&mmap_regions->hdr, cmd_size, TRUE); + mmap_regions->hdr.opcode = ASM_SESSION_CMD_MEMORY_MAP_REGIONS; + mmap_regions->mempool_id = 0; + mmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("map_regions->nregions = %d\n", mmap_regions->nregions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct asm_stream_cmd_memory_map_regions)); + mregions = (struct asm_memory_map_regions *)payload; + + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->phys = ab->phys; + mregions->buf_size = ab->size; + ++mregions; + } + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) mmap_region_cmd); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + mmap_regions->hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(mmap_region_cmd); + return rc; +} + +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_unmap_regions *unmap_regions = NULL; + struct asm_memory_unmap_regions *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *unmap_region_cmd = NULL; + void *payload = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct asm_stream_cmd_memory_unmap_regions) + + sizeof(struct asm_memory_unmap_regions) * bufcnt; + + unmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (unmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + unmap_regions = (struct asm_stream_cmd_memory_unmap_regions *) + unmap_region_cmd; + q6asm_add_mmaphdr(&unmap_regions->hdr, cmd_size, TRUE); + unmap_regions->hdr.opcode = ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS; + unmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("unmap_regions->nregions = %d\n", unmap_regions->nregions); + payload = ((u8 *) unmap_region_cmd + + sizeof(struct asm_stream_cmd_memory_unmap_regions)); + mregions = (struct asm_memory_unmap_regions *)payload; + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->phys = ab->phys; + ++mregions; + } + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) unmap_region_cmd); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + unmap_regions->hdr.opcode, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_unmap\n"); + goto fail_cmd; + } + rc = 0; + +fail_cmd: + kfree(unmap_region_cmd); + return rc; +} + +int q6asm_set_mute(struct audio_client *ac, int muteflag) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_mute_params *mute = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_mute_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_mute_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = MUTE_CONFIG_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_mute_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + mute = (struct asm_mute_params *)payload; + + mute->muteflag = muteflag; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Mute Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending mute command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_set_volume(struct audio_client *ac, int volume) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_master_gain_params *mgain = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_master_gain_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_master_gain_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = MASTER_GAIN_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_master_gain_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + mgain = (struct asm_master_gain_params *)payload; + + mgain->master_gain = volume; + mgain->padding = 0x00; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *pause_param) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_softpause_params *params = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_softpause_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_softpause_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = SOFT_PAUSE_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_softpause_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + params = (struct asm_softpause_params *)payload; + + params->enable = pause_param->enable; + params->period = pause_param->period; + params->step = pause_param->step; + params->rampingcurve = pause_param->rampingcurve; + pr_debug("%s: soft Pause Command: enable = %d, period = %d," + "step = %d, curve = %d\n", __func__, params->enable, + params->period, params->step, params->rampingcurve); + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command(soft_pause) failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command(soft_pause)" + "to apr\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *softvol_param) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_softvolume_params *params = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_softvolume_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_softvolume_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = SOFT_VOLUME_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_softvolume_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + params = (struct asm_softvolume_params *)payload; + + params->period = softvol_param->period; + params->step = softvol_param->step; + params->rampingcurve = softvol_param->rampingcurve; + pr_debug("%s: soft Volume:opcode = %d,payload_sz =%d,module_id =%d," + "param_id = %d, param_sz = %d\n", __func__, + cmd->hdr.opcode, cmd->payload_size, + cmd->params.module_id, cmd->params.param_id, + cmd->params.param_size); + pr_debug("%s: soft Volume Command: period = %d," + "step = %d, curve = %d\n", __func__, params->period, + params->step, params->rampingcurve); + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command(soft_volume) failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command(soft_volume)" + "to apr\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_equalizer(struct audio_client *ac, void *eq) +{ + void *eq_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_equalizer_params *equalizer = NULL; + struct msm_audio_eq_stream_config *eq_params = NULL; + int i = 0; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_equalizer_params); + eq_cmd = kzalloc(sz, GFP_KERNEL); + if (eq_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + goto fail_cmd; + } + eq_params = (struct msm_audio_eq_stream_config *) eq; + cmd = (struct asm_pp_params_command *)eq_cmd; + q6asm_add_hdr(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_equalizer_params); + cmd->params.module_id = EQUALIZER_MODULE_ID; + cmd->params.param_id = EQUALIZER_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_equalizer_params); + cmd->params.reserved = 0; + payload = (u8 *)(eq_cmd + sizeof(struct asm_pp_params_command)); + equalizer = (struct asm_equalizer_params *)payload; + + equalizer->enable = eq_params->enable; + equalizer->num_bands = eq_params->num_bands; + pr_debug("%s: enable:%d numbands:%d\n", __func__, eq_params->enable, + eq_params->num_bands); + for (i = 0; i < eq_params->num_bands; i++) { + equalizer->eq_bands[i].band_idx = + eq_params->eq_bands[i].band_idx; + equalizer->eq_bands[i].filter_type = + eq_params->eq_bands[i].filter_type; + equalizer->eq_bands[i].center_freq_hz = + eq_params->eq_bands[i].center_freq_hz; + equalizer->eq_bands[i].filter_gain = + eq_params->eq_bands[i].filter_gain; + equalizer->eq_bands[i].q_factor = + eq_params->eq_bands[i].q_factor; + pr_debug("%s: filter_type:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_type, i); + pr_debug("%s: center_freq_hz:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].center_freq_hz, i); + pr_debug("%s: filter_gain:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_gain, i); + pr_debug("%s: q_factor:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].q_factor, i); + } + rc = apr_send_pkt(ac->apr, (uint32_t *) eq_cmd); + if (rc < 0) { + pr_err("%s: Equalizer Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending equalizer command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(eq_cmd); + return rc; +} + +int q6asm_read(struct audio_client *ac) +{ + struct asm_stream_cmd_read read; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr(ac, &read.hdr, sizeof(read), FALSE); + + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = ab->phys; + read.buf_size = ab->size; + read.uid = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + mutex_unlock(&port->lock); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_add, + read.hdr.token, + read.uid); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_read_nolock(struct audio_client *ac) +{ + struct asm_stream_cmd_read read; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = ab->phys; + read.buf_size = ab->size; + read.uid = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_add, + read.hdr.token, + read.uid); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + + +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("session=%d pkt size=%d cmd_flg=%d\n", pkt_size, cmd_flg, + ac->session); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param) +{ + int rc = 0; + struct asm_stream_cmd_write write; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), FALSE); + + /* Pass physical address as token for AIO scheme */ + write.hdr.token = param->uid; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = param->paddr; + write.avail_bytes = param->len; + write.uid = param->uid; + write.msw_ts = param->msw_ts; + write.lsw_ts = param->lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (param->flags == 0xFF00) + write.uflags = (0x00000000 | (param->flags & 0x800000FF)); + else + write.uflags = (0x80000000 | param->flags); + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + write.buf_add, write.avail_bytes); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_debug("[%s] write op[0x%x]rc[%d]\n", __func__, + write.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param) +{ + int rc = 0; + struct asm_stream_cmd_read read; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + /* Pass physical address as token for AIO scheme */ + read.hdr.token = param->paddr; + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = param->paddr; + read.buf_size = param->len; + read.uid = param->uid; + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + read.buf_add, read.buf_size); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_debug("[%s] read op[0x%x]rc[%d]\n", __func__, + read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_stream_cmd_write write; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr(ac, &write.hdr, sizeof(write), + FALSE); + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = ab->phys; + write.avail_bytes = len; + write.uid = port->dsp_buf; + write.msw_ts = msw_ts; + write.lsw_ts = lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.uflags = (0x00000000 | (flags & 0x800000FF)); + else + write.uflags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]buf_id[0x%x]" + , __func__, + ab->phys, + write.buf_add, + write.hdr.token, + write.uid); + mutex_unlock(&port->lock); +#ifdef CONFIG_DEBUG_FS + if (out_enable_flag) { + char zero_pattern[2] = {0x00, 0x00}; + /* If First two byte is non zero and last two byte + is zero then it is warm output pattern */ + if ((strncmp(((char *)ab->data), zero_pattern, 2)) && + (!strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_warm_tv); + pr_debug("WARM:apr_send_pkt at \ + %ld sec %ld microsec\n", out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec); + pr_debug("Warm Pattern Matched"); + } + /* If First two byte is zero and last two byte is + non zero then it is cont ouput pattern */ + else if ((!strncmp(((char *)ab->data), zero_pattern, 2)) + && (strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_cont_tv); + pr_debug("CONT:apr_send_pkt at \ + %ld sec %ld microsec\n", out_cont_tv.tv_sec,\ + out_cont_tv.tv_usec); + pr_debug("Cont Pattern Matched"); + } + } +#endif + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_stream_cmd_write write; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), + FALSE); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = ab->phys; + write.avail_bytes = len; + write.uid = port->dsp_buf; + write.msw_ts = msw_ts; + write.lsw_ts = lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.uflags = (0x00000000 | (flags & 0x800000FF)); + else + write.uflags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]buf_id[0x%x]" + , __func__, + ab->phys, + write.buf_add, + write.hdr.token, + write.uid); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +uint64_t q6asm_get_session_time(struct audio_client *ac) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + hdr.opcode = ASM_SESSION_CMD_GET_SESSION_TIME; + atomic_set(&ac->time_flag, 1); + + pr_debug("%s: session[%d]opcode[0x%x]\n", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->time_wait, + (atomic_read(&ac->time_flag) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in getting session time from DSP\n", + __func__); + goto fail_cmd; + } + return ac->time_stamp; + +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + atomic_t *state; + int cnt = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + state = &ac->cmd_state; + break; + case CMD_FLUSH: + pr_debug("%s:CMD_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH; + state = &ac->cmd_state; + break; + case CMD_OUT_FLUSH: + pr_debug("%s:CMD_OUT_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + state = &ac->cmd_state; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + atomic_set(&ac->cmd_state, 0); + state = &ac->cmd_state; + break; + case CMD_CLOSE: + pr_debug("%s:CMD_CLOSE\n", __func__); + hdr.opcode = ASM_STREAM_CMD_CLOSE; + state = &ac->cmd_state; + break; + default: + pr_err("Invalid format[%d]\n", cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, (atomic_read(state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for response opcode[0x%x]\n", + hdr.opcode); + goto fail_cmd; + } + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + if (cmd == CMD_CLOSE) { + /* check if DSP return all buffers */ + if (ac->port[IN].buf) { + for (cnt = 0; cnt < ac->port[IN].max_buf_cnt; + cnt++) { + if (ac->port[IN].buf[cnt].used == IN) { + pr_debug("Write Buf[%d] not returned\n", + cnt); + } + } + } + if (ac->port[OUT].buf) { + for (cnt = 0; cnt < ac->port[OUT].max_buf_cnt; cnt++) { + if (ac->port[OUT].buf[cnt].used == OUT) { + pr_debug("Read Buf[%d] not returned\n", + cnt); + } + } + } + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + q6asm_add_hdr_async(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + break; + default: + pr_err("%s:Invalid format[%d]\n", __func__, cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("%s:Commmand 0x%x failed\n", __func__, hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + int cnt = 0; + int loopcnt = 0; + struct audio_port_data *port = NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + mutex_lock(&ac->cmd_lock); + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + cnt = port->max_buf_cnt - 1; + port->dsp_buf = 0; + port->cpu_buf = 0; + while (cnt >= 0) { + if (!port->buf) + continue; + port->buf[cnt].used = 1; + cnt--; + } + } + mutex_unlock(&ac->cmd_lock); + } +} + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable) +{ + struct asm_stream_cmd_reg_tx_overflow_event tx_overflow; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s:session[%d]enable[%d]\n", __func__, + ac->session, enable); + q6asm_add_hdr(ac, &tx_overflow.hdr, sizeof(tx_overflow), TRUE); + + tx_overflow.hdr.opcode = \ + ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS; + /* tx overflow event: enable */ + tx_overflow.enable = enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &tx_overflow); + if (rc < 0) { + pr_err("tx overflow op[0x%x]rc[%d]\n", \ + tx_overflow.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for tx overflow\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_get_apr_service_id(int session_id) +{ + pr_debug("%s\n", __func__); + + if (session_id < 0 || session_id > SESSION_MAX) { + pr_err("%s: invalid session_id = %d\n", __func__, session_id); + return -EINVAL; + } + + return ((struct apr_svc *)session[session_id]->apr)->id; +} + + +static int __init q6asm_init(void) +{ + pr_debug("%s\n", __func__); + init_waitqueue_head(&this_mmap.cmd_wait); + memset(session, 0, sizeof(session)); +#ifdef CONFIG_DEBUG_FS + out_buffer = kmalloc(OUT_BUFFER_SIZE, GFP_KERNEL); + out_dentry = debugfs_create_file("audio_out_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_output_latency_debug_fops); + if (IS_ERR(out_dentry)) + pr_err("debugfs_create_file failed\n"); + in_buffer = kmalloc(IN_BUFFER_SIZE, GFP_KERNEL); + in_dentry = debugfs_create_file("audio_in_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_input_latency_debug_fops); + if (IS_ERR(in_dentry)) + pr_err("debugfs_create_file failed\n"); +#endif + return 0; +} + +device_initcall(q6asm_init); diff --git a/sound/soc/msm/qdsp6/q6voice.c b/sound/soc/msm/qdsp6/q6voice.c new file mode 100644 index 000000000000..e3e531d5bbd9 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.c @@ -0,0 +1,4013 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sound/apr_audio.h" +#include "sound/q6afe.h" + +#include "q6voice.h" + +#define TIMEOUT_MS 3000 + +#define CMD_STATUS_SUCCESS 0 +#define CMD_STATUS_FAIL 1 + +#define VOC_PATH_PASSIVE 0 +#define VOC_PATH_FULL 1 +#define VOC_PATH_VOLTE_PASSIVE 2 + +/* CVP CAL Size: 245760 = 240 * 1024 */ +#define CVP_CAL_SIZE 245760 +/* CVS CAL Size: 49152 = 48 * 1024 */ +#define CVS_CAL_SIZE 49152 + +static struct common_data common; + +static int voice_send_enable_vocproc_cmd(struct voice_data *v); +static int voice_send_netid_timing_cmd(struct voice_data *v); +static int voice_send_attach_vocproc_cmd(struct voice_data *v); +static int voice_send_set_device_cmd(struct voice_data *v); +static int voice_send_disable_vocproc_cmd(struct voice_data *v); +static int voice_send_vol_index_cmd(struct voice_data *v); +static int voice_send_cvp_map_memory_cmd(struct voice_data *v); +static int voice_send_cvp_unmap_memory_cmd(struct voice_data *v); +static int voice_send_cvs_map_memory_cmd(struct voice_data *v); +static int voice_send_cvs_unmap_memory_cmd(struct voice_data *v); +static int voice_send_cvs_register_cal_cmd(struct voice_data *v); +static int voice_send_cvs_deregister_cal_cmd(struct voice_data *v); +static int voice_send_cvp_register_cal_cmd(struct voice_data *v); +static int voice_send_cvp_deregister_cal_cmd(struct voice_data *v); +static int voice_send_cvp_register_vol_cal_table_cmd(struct voice_data *v); +static int voice_send_cvp_deregister_vol_cal_table_cmd(struct voice_data *v); +static int voice_send_set_widevoice_enable_cmd(struct voice_data *v); +static int voice_send_set_pp_enable_cmd(struct voice_data *v, + uint32_t module_id, int enable); +static int voice_cvs_stop_playback(struct voice_data *v); +static int voice_cvs_start_playback(struct voice_data *v); +static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode); +static int voice_cvs_stop_record(struct voice_data *v); + +static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv); +static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv); +static int32_t qdsp_cvp_callback(struct apr_client_data *data, void *priv); + +static u16 voice_get_mvm_handle(struct voice_data *v) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: mvm_handle %d\n", __func__, v->mvm_handle); + + return v->mvm_handle; +} + +static void voice_set_mvm_handle(struct voice_data *v, u16 mvm_handle) +{ + pr_debug("%s: mvm_handle %d\n", __func__, mvm_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + v->mvm_handle = mvm_handle; +} + +static u16 voice_get_cvs_handle(struct voice_data *v) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: cvs_handle %d\n", __func__, v->cvs_handle); + + return v->cvs_handle; +} + +static void voice_set_cvs_handle(struct voice_data *v, u16 cvs_handle) +{ + pr_debug("%s: cvs_handle %d\n", __func__, cvs_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + v->cvs_handle = cvs_handle; +} + +static u16 voice_get_cvp_handle(struct voice_data *v) +{ + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return 0; + } + + pr_debug("%s: cvp_handle %d\n", __func__, v->cvp_handle); + + return v->cvp_handle; +} + +static void voice_set_cvp_handle(struct voice_data *v, u16 cvp_handle) +{ + pr_debug("%s: cvp_handle %d\n", __func__, cvp_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + v->cvp_handle = cvp_handle; +} + +uint16_t voc_get_session_id(char *name) +{ + u16 session_id = 0; + + if (name != NULL) { + if (!strncmp(name, "Voice session", 13)) + session_id = common.voice[VOC_PATH_PASSIVE].session_id; + else if (!strncmp(name, "VoLTE session", 13)) + session_id = + common.voice[VOC_PATH_VOLTE_PASSIVE].session_id; + else + session_id = common.voice[VOC_PATH_FULL].session_id; + + pr_debug("%s: %s has session id 0x%x\n", __func__, name, + session_id); + } + + return session_id; +} + +static struct voice_data *voice_get_session(u16 session_id) +{ + struct voice_data *v = NULL; + + if ((session_id >= SESSION_ID_BASE) && + (session_id < SESSION_ID_BASE + MAX_VOC_SESSIONS)) { + v = &common.voice[session_id - SESSION_ID_BASE]; + } + + pr_debug("%s: session_id 0x%x session handle 0x%x\n", + __func__, session_id, (unsigned int)v); + + return v; +} + +static bool is_voice_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_PASSIVE].session_id); +} + +static bool is_voip_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_FULL].session_id); +} + +static bool is_volte_session(u16 session_id) +{ + return (session_id == common.voice[VOC_PATH_VOLTE_PASSIVE].session_id); +} + +static int voice_apr_register(void) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&common.common_lock); + + /* register callback to APR */ + if (common.apr_q6_mvm == NULL) { + pr_debug("%s: Start to register MVM callback\n", __func__); + + common.apr_q6_mvm = apr_register("ADSP", "MVM", + qdsp_mvm_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_mvm == NULL) { + pr_err("%s: Unable to register MVM\n", __func__); + goto err; + } + } + + if (common.apr_q6_cvs == NULL) { + pr_debug("%s: Start to register CVS callback\n", __func__); + + common.apr_q6_cvs = apr_register("ADSP", "CVS", + qdsp_cvs_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_cvs == NULL) { + pr_err("%s: Unable to register CVS\n", __func__); + goto err; + } + + rtac_set_voice_handle(RTAC_CVS, common.apr_q6_cvs); + } + + if (common.apr_q6_cvp == NULL) { + pr_debug("%s: Start to register CVP callback\n", __func__); + + common.apr_q6_cvp = apr_register("ADSP", "CVP", + qdsp_cvp_callback, + 0xFFFFFFFF, &common); + + if (common.apr_q6_cvp == NULL) { + pr_err("%s: Unable to register CVP\n", __func__); + goto err; + } + + rtac_set_voice_handle(RTAC_CVP, common.apr_q6_cvp); + } + + mutex_unlock(&common.common_lock); + + return 0; + +err: + if (common.apr_q6_cvs != NULL) { + apr_deregister(common.apr_q6_cvs); + common.apr_q6_cvs = NULL; + rtac_set_voice_handle(RTAC_CVS, NULL); + } + if (common.apr_q6_mvm != NULL) { + apr_deregister(common.apr_q6_mvm); + common.apr_q6_mvm = NULL; + } + + mutex_unlock(&common.common_lock); + + return -ENODEV; +} + +static int voice_send_dual_control_cmd(struct voice_data *v) +{ + int ret = 0; + struct mvm_modem_dual_control_session_cmd mvm_voice_ctl_cmd; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + pr_debug("%s: VoLTE command to MVM\n", __func__); + if (is_volte_session(v->session_id)) { + mvm_handle = voice_get_mvm_handle(v); + mvm_voice_ctl_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_voice_ctl_cmd.hdr.pkt_size = APR_PKT_SIZE( + APR_HDR_SIZE, + sizeof(mvm_voice_ctl_cmd) - + APR_HDR_SIZE); + pr_debug("%s: send mvm Voice Ctl pkt size = %d\n", + __func__, mvm_voice_ctl_cmd.hdr.pkt_size); + mvm_voice_ctl_cmd.hdr.src_port = v->session_id; + mvm_voice_ctl_cmd.hdr.dest_port = mvm_handle; + mvm_voice_ctl_cmd.hdr.token = 0; + mvm_voice_ctl_cmd.hdr.opcode = + VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL; + mvm_voice_ctl_cmd.voice_ctl.enable_flag = true; + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_voice_ctl_cmd); + if (ret < 0) { + pr_err("%s: Error sending MVM Voice CTL CMD\n", + __func__); + ret = -EINVAL; + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail; + } + } + ret = 0; +fail: + return ret; +} + + +static int voice_create_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_create_ctl_session_cmd mvm_session_cmd; + struct cvs_create_passive_ctl_session_cmd cvs_session_cmd; + struct cvs_create_full_ctl_session_cmd cvs_full_ctl_cmd; + struct mvm_attach_stream_cmd attach_stream_cmd; + void *apr_mvm, *apr_cvs, *apr_cvp; + u16 mvm_handle, cvs_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + apr_cvs = common.apr_q6_cvs; + apr_cvp = common.apr_q6_cvp; + + if (!apr_mvm || !apr_cvs || !apr_cvp) { + pr_err("%s: apr_mvm or apr_cvs or apr_cvp is NULL\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvs_handle = voice_get_cvs_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + pr_debug("%s: mvm_hdl=%d, cvs_hdl=%d\n", __func__, + mvm_handle, cvs_handle); + /* send cmd to create mvm session and wait for response */ + + if (!mvm_handle) { + if (is_voice_session(v->session_id) || + is_volte_session(v->session_id)) { + mvm_session_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_session_cmd.hdr.pkt_size = APR_PKT_SIZE( + APR_HDR_SIZE, + sizeof(mvm_session_cmd) - + APR_HDR_SIZE); + pr_debug("%s: send mvm create session pkt size = %d\n", + __func__, mvm_session_cmd.hdr.pkt_size); + mvm_session_cmd.hdr.src_port = v->session_id; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + if (is_volte_session(v->session_id)) { + strlcpy(mvm_session_cmd.mvm_session.name, + "default volte voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } else { + strlcpy(mvm_session_cmd.mvm_session.name, + "default modem voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("%s: Error sending MVM_CONTROL_SESSION\n", + __func__); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } else { + pr_debug("%s: creating MVM full ctrl\n", __func__); + mvm_session_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_session_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_session_cmd) - + APR_HDR_SIZE); + mvm_session_cmd.hdr.src_port = v->session_id; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION; + strlcpy(mvm_session_cmd.mvm_session.name, + "default voip", + sizeof(mvm_session_cmd.mvm_session.name)); + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("Fail in sending MVM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + /* Get the created MVM handle. */ + mvm_handle = voice_get_mvm_handle(v); + } + /* send cmd to create cvs session */ + if (!cvs_handle) { + if (is_voice_session(v->session_id) || + is_volte_session(v->session_id)) { + pr_debug("%s: creating CVS passive session\n", + __func__); + + cvs_session_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_session_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_session_cmd) - + APR_HDR_SIZE); + cvs_session_cmd.hdr.src_port = v->session_id; + cvs_session_cmd.hdr.dest_port = 0; + cvs_session_cmd.hdr.token = 0; + cvs_session_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + if (is_volte_session(v->session_id)) { + strlcpy(mvm_session_cmd.mvm_session.name, + "default volte voice", + sizeof(mvm_session_cmd.mvm_session.name)); + } else { + strlcpy(cvs_session_cmd.cvs_session.name, + "default modem voice", + sizeof(cvs_session_cmd.cvs_session.name)); + } + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, + (uint32_t *) &cvs_session_cmd); + if (ret < 0) { + pr_err("Fail in sending STREAM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + + } else { + pr_debug("%s: creating CVS full session\n", __func__); + + cvs_full_ctl_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + + cvs_full_ctl_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_full_ctl_cmd) - + APR_HDR_SIZE); + + cvs_full_ctl_cmd.hdr.src_port = v->session_id; + cvs_full_ctl_cmd.hdr.dest_port = 0; + cvs_full_ctl_cmd.hdr.token = 0; + cvs_full_ctl_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION; + cvs_full_ctl_cmd.cvs_session.direction = 2; + cvs_full_ctl_cmd.cvs_session.enc_media_type = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.dec_media_type = + common.mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.network_id = + common.mvs_info.network_type; + strlcpy(cvs_full_ctl_cmd.cvs_session.name, + "default q6 voice", + sizeof(cvs_full_ctl_cmd.cvs_session.name)); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, + (uint32_t *) &cvs_full_ctl_cmd); + + if (ret < 0) { + pr_err("%s: Err %d sending CREATE_FULL_CTRL\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + + /* Attach MVM to CVS. */ + pr_debug("%s: Attach MVM to stream\n", __func__); + + attach_stream_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + attach_stream_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(attach_stream_cmd) - + APR_HDR_SIZE); + attach_stream_cmd.hdr.src_port = v->session_id; + attach_stream_cmd.hdr.dest_port = mvm_handle; + attach_stream_cmd.hdr.token = 0; + attach_stream_cmd.hdr.opcode = + VSS_IMVM_CMD_ATTACH_STREAM; + attach_stream_cmd.attach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &attach_stream_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending ATTACH_STREAM\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + } + return 0; + +fail: + return -EINVAL; +} + +static int voice_destroy_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_detach_stream_cmd detach_stream; + struct apr_hdr mvm_destroy; + struct apr_hdr cvs_destroy; + void *apr_mvm, *apr_cvs; + u16 mvm_handle, cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + apr_cvs = common.apr_q6_cvs; + + if (!apr_mvm || !apr_cvs) { + pr_err("%s: apr_mvm or apr_cvs is NULL\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvs_handle = voice_get_cvs_handle(v); + + /* MVM, CVS sessions are destroyed only for Full control sessions. */ + if (is_voip_session(v->session_id)) { + pr_debug("%s: MVM detach stream\n", __func__); + + /* Detach voice stream. */ + detach_stream.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + detach_stream.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(detach_stream) - APR_HDR_SIZE); + detach_stream.hdr.src_port = v->session_id; + detach_stream.hdr.dest_port = mvm_handle; + detach_stream.hdr.token = 0; + detach_stream.hdr.opcode = VSS_IMVM_CMD_DETACH_STREAM; + detach_stream.detach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &detach_stream); + if (ret < 0) { + pr_err("%s: Error %d sending DETACH_STREAM\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + goto fail; + } + /* Destroy CVS. */ + pr_debug("%s: CVS destroy session\n", __func__); + + cvs_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_destroy) - APR_HDR_SIZE); + cvs_destroy.src_port = v->session_id; + cvs_destroy.dest_port = cvs_handle; + cvs_destroy.token = 0; + cvs_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending CVS DESTROY\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + cvs_handle = 0; + voice_set_cvs_handle(v, cvs_handle); + + /* Destroy MVM. */ + pr_debug("MVM destroy session\n"); + + mvm_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_destroy) - APR_HDR_SIZE); + mvm_destroy.src_port = v->session_id; + mvm_destroy.dest_port = mvm_handle; + mvm_destroy.token = 0; + mvm_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending MVM DESTROY\n", + __func__, ret); + + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + mvm_handle = 0; + voice_set_mvm_handle(v, mvm_handle); + } + return 0; +fail: + return -EINVAL; +} + +static int voice_send_tty_mode_cmd(struct voice_data *v) +{ + int ret = 0; + struct mvm_set_tty_mode_cmd mvm_tty_mode_cmd; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + if (v->tty_mode) { + /* send tty mode cmd to mvm */ + mvm_tty_mode_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_tty_mode_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_tty_mode_cmd) - + APR_HDR_SIZE); + pr_debug("%s: pkt size = %d\n", + __func__, mvm_tty_mode_cmd.hdr.pkt_size); + mvm_tty_mode_cmd.hdr.src_port = v->session_id; + mvm_tty_mode_cmd.hdr.dest_port = mvm_handle; + mvm_tty_mode_cmd.hdr.token = 0; + mvm_tty_mode_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_TTY_MODE; + mvm_tty_mode_cmd.tty_mode.mode = v->tty_mode; + pr_debug("tty mode =%d\n", mvm_tty_mode_cmd.tty_mode.mode); + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_tty_mode_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending SET_TTY_MODE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + return 0; +fail: + return -EINVAL; +} + +static int voice_set_dtx(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* Set DTX */ + cvs_set_dtx.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_dtx.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_dtx) - APR_HDR_SIZE); + cvs_set_dtx.hdr.src_port = v->session_id; + cvs_set_dtx.hdr.dest_port = cvs_handle; + cvs_set_dtx.hdr.token = 0; + cvs_set_dtx.hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE; + cvs_set_dtx.dtx_mode.enable = common.mvs_info.dtx_mode; + + pr_debug("%s: Setting DTX %d\n", __func__, common.mvs_info.dtx_mode); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_dtx); + if (ret < 0) { + pr_err("%s: Error %d sending SET_DTX\n", __func__, ret); + return -EINVAL; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int voice_config_cvs_vocoder(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + /* Set media type. */ + struct cvs_set_media_type_cmd cvs_set_media_cmd; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + cvs_set_media_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_media_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_media_cmd) - APR_HDR_SIZE); + cvs_set_media_cmd.hdr.src_port = v->session_id; + cvs_set_media_cmd.hdr.dest_port = cvs_handle; + cvs_set_media_cmd.hdr.token = 0; + cvs_set_media_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MEDIA_TYPE; + cvs_set_media_cmd.media_type.tx_media_id = common.mvs_info.media_type; + cvs_set_media_cmd.media_type.rx_media_id = common.mvs_info.media_type; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_media_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending SET_MEDIA_TYPE\n", + __func__, ret); + + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + /* Set encoder properties. */ + switch (common.mvs_info.media_type) { + case VSS_MEDIA_ID_EVRC_MODEM: { + struct cvs_set_cdma_enc_minmax_rate_cmd cvs_set_cdma_rate; + + pr_debug("Setting EVRC min-max rate\n"); + + cvs_set_cdma_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_cdma_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_cdma_rate) - APR_HDR_SIZE); + cvs_set_cdma_rate.hdr.src_port = v->session_id; + cvs_set_cdma_rate.hdr.dest_port = cvs_handle; + cvs_set_cdma_rate.hdr.token = 0; + cvs_set_cdma_rate.hdr.opcode = + VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE; + cvs_set_cdma_rate.cdma_rate.min_rate = common.mvs_info.rate; + cvs_set_cdma_rate.cdma_rate.max_rate = common.mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_cdma_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_EVRC_MINMAX_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + break; + } + case VSS_MEDIA_ID_AMR_NB_MODEM: { + struct cvs_set_amr_enc_rate_cmd cvs_set_amr_rate; + + pr_debug("Setting AMR rate\n"); + + cvs_set_amr_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amr_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amr_rate) - APR_HDR_SIZE); + cvs_set_amr_rate.hdr.src_port = v->session_id; + cvs_set_amr_rate.hdr.dest_port = cvs_handle; + cvs_set_amr_rate.hdr.token = 0; + cvs_set_amr_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE; + cvs_set_amr_rate.amr_rate.mode = common.mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amr_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMR_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + ret = voice_set_dtx(v); + if (ret < 0) + goto fail; + + break; + } + case VSS_MEDIA_ID_AMR_WB_MODEM: { + struct cvs_set_amrwb_enc_rate_cmd cvs_set_amrwb_rate; + + pr_debug("Setting AMR WB rate\n"); + + cvs_set_amrwb_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amrwb_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amrwb_rate) - + APR_HDR_SIZE); + cvs_set_amrwb_rate.hdr.src_port = v->session_id; + cvs_set_amrwb_rate.hdr.dest_port = cvs_handle; + cvs_set_amrwb_rate.hdr.token = 0; + cvs_set_amrwb_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE; + cvs_set_amrwb_rate.amrwb_rate.mode = common.mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amrwb_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMRWB_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + ret = voice_set_dtx(v); + if (ret < 0) + goto fail; + + break; + } + case VSS_MEDIA_ID_G729: + case VSS_MEDIA_ID_G711_ALAW: + case VSS_MEDIA_ID_G711_MULAW: { + ret = voice_set_dtx(v); + + break; + } + default: + /* Do nothing. */ + break; + } + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_start_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_start_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + mvm_start_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_start_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_start_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_start_voice_cmd pkt size = %d\n", + mvm_start_voice_cmd.pkt_size); + mvm_start_voice_cmd.src_port = v->session_id; + mvm_start_voice_cmd.dest_port = mvm_handle; + mvm_start_voice_cmd.token = 0; + mvm_start_voice_cmd.opcode = VSS_IMVM_CMD_START_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_start_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_START_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; +} + +static int voice_send_disable_vocproc_cmd(struct voice_data *v) +{ + struct apr_hdr cvp_disable_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr regist failed\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* disable vocproc and wait for respose */ + cvp_disable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_disable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_disable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_disable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_disable_cmd.pkt_size, cvp_handle); + cvp_disable_cmd.src_port = v->session_id; + cvp_disable_cmd.dest_port = cvp_handle; + cvp_disable_cmd.token = 0; + cvp_disable_cmd.opcode = VSS_IVOCPROC_CMD_DISABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_disable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_DISABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_set_device_cmd(struct voice_data *v) +{ + struct cvp_set_device_cmd cvp_setdev_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* set device and wait for response */ + cvp_setdev_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_setdev_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_setdev_cmd) - APR_HDR_SIZE); + pr_debug(" send create cvp setdev, pkt size = %d\n", + cvp_setdev_cmd.hdr.pkt_size); + cvp_setdev_cmd.hdr.src_port = v->session_id; + cvp_setdev_cmd.hdr.dest_port = cvp_handle; + cvp_setdev_cmd.hdr.token = 0; + cvp_setdev_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_DEVICE; + + /* Use default topology if invalid value in ACDB */ + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + get_voice_tx_topology(); + if (cvp_setdev_cmd.cvp_set_device.tx_topology_id == 0) + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + get_voice_rx_topology(); + if (cvp_setdev_cmd.cvp_set_device.rx_topology_id == 0) + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + cvp_setdev_cmd.cvp_set_device.tx_port_id = v->dev_tx.port_id; + cvp_setdev_cmd.cvp_set_device.rx_port_id = v->dev_rx.port_id; + pr_debug("topology=%d , tx_port_id=%d, rx_port_id=%d\n", + cvp_setdev_cmd.cvp_set_device.tx_topology_id, + cvp_setdev_cmd.cvp_set_device.tx_port_id, + cvp_setdev_cmd.cvp_set_device.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_setdev_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + pr_debug("wait for cvp create session event\n"); + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_stop_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_stop_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + mvm_stop_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_stop_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_stop_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_stop_voice_cmd pkt size = %d\n", + mvm_stop_voice_cmd.pkt_size); + mvm_stop_voice_cmd.src_port = v->session_id; + mvm_stop_voice_cmd.dest_port = mvm_handle; + mvm_stop_voice_cmd.token = 0; + mvm_stop_voice_cmd.opcode = VSS_IMVM_CMD_STOP_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_stop_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_STOP_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_cvs_register_cal_cmd(struct voice_data *v) +{ + struct cvs_register_cal_data_cmd cvs_reg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + /* get the cvs cal data */ + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvs_cal.buf) { + cal_paddr = common.cvs_cal.phy; + + memcpy(common.cvs_cal.buf, + (void *) cal_block.cal_kvaddr, + cal_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_reg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_reg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_reg_cal_cmd) - APR_HDR_SIZE); + cvs_reg_cal_cmd.hdr.src_port = v->session_id; + cvs_reg_cal_cmd.hdr.dest_port = cvs_handle; + cvs_reg_cal_cmd.hdr.token = 0; + cvs_reg_cal_cmd.hdr.opcode = VSS_ISTREAM_CMD_REGISTER_CALIBRATION_DATA; + + cvs_reg_cal_cmd.cvs_cal_data.phys_addr = cal_paddr; + cvs_reg_cal_cmd.cvs_cal_data.mem_size = cal_block.cal_size; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_reg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvs_deregister_cal_cmd(struct voice_data *v) +{ + struct cvs_deregister_cal_data_cmd cvs_dereg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_dereg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_dereg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_dereg_cal_cmd) - APR_HDR_SIZE); + cvs_dereg_cal_cmd.hdr.src_port = v->session_id; + cvs_dereg_cal_cmd.hdr.dest_port = cvs_handle; + cvs_dereg_cal_cmd.hdr.token = 0; + cvs_dereg_cal_cmd.hdr.opcode = + VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_dereg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_map_memory_cmd(struct voice_data *v) +{ + struct vss_map_memory_cmd cvp_map_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get all cvp cal data */ + get_all_cvp_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) + cal_paddr = common.cvp_cal.phy; + else + return -EINVAL; + } else { + cal_paddr = cal_block.cal_paddr; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_map_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_map_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_map_mem_cmd) - APR_HDR_SIZE); + cvp_map_mem_cmd.hdr.src_port = v->session_id; + cvp_map_mem_cmd.hdr.dest_port = cvp_handle; + cvp_map_mem_cmd.hdr.token = 0; + cvp_map_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_MAP_MEMORY; + + pr_debug("%s, phy_addr:0x%x, mem_size:%d\n", __func__, + cal_paddr, cal_block.cal_size); + cvp_map_mem_cmd.vss_map_mem.phys_addr = cal_paddr; + cvp_map_mem_cmd.vss_map_mem.mem_size = cal_block.cal_size; + cvp_map_mem_cmd.vss_map_mem.mem_pool_id = + VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_map_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_unmap_memory_cmd(struct voice_data *v) +{ + struct vss_unmap_memory_cmd cvp_unmap_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + get_all_cvp_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) + cal_paddr = common.cvp_cal.phy; + else + cal_paddr = cal_block.cal_paddr; + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_unmap_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_unmap_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_unmap_mem_cmd) - APR_HDR_SIZE); + cvp_unmap_mem_cmd.hdr.src_port = v->session_id; + cvp_unmap_mem_cmd.hdr.dest_port = cvp_handle; + cvp_unmap_mem_cmd.hdr.token = 0; + cvp_unmap_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_UNMAP_MEMORY; + + cvp_unmap_mem_cmd.vss_unmap_mem.phys_addr = cal_paddr; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_unmap_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvs_map_memory_cmd(struct voice_data *v) +{ + struct vss_map_memory_cmd cvs_map_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + /* get all cvs cal data */ + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvs_cal.buf) + cal_paddr = common.cvs_cal.phy; + else + return -EINVAL; + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_map_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_map_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_map_mem_cmd) - APR_HDR_SIZE); + cvs_map_mem_cmd.hdr.src_port = v->session_id; + cvs_map_mem_cmd.hdr.dest_port = cvs_handle; + cvs_map_mem_cmd.hdr.token = 0; + cvs_map_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_MAP_MEMORY; + + pr_debug("%s, phys_addr: 0x%x, mem_size: %d\n", __func__, + cal_paddr, cal_block.cal_size); + cvs_map_mem_cmd.vss_map_mem.phys_addr = cal_paddr; + cvs_map_mem_cmd.vss_map_mem.mem_size = cal_block.cal_size; + cvs_map_mem_cmd.vss_map_mem.mem_pool_id = + VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_map_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvs_unmap_memory_cmd(struct voice_data *v) +{ + struct vss_unmap_memory_cmd cvs_unmap_mem_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + uint32_t cal_paddr; + + get_all_vocstrm_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) + cal_paddr = common.cvs_cal.phy; + else + cal_paddr = cal_block.cal_paddr; + + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_unmap_mem_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_unmap_mem_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_unmap_mem_cmd) - APR_HDR_SIZE); + cvs_unmap_mem_cmd.hdr.src_port = v->session_id; + cvs_unmap_mem_cmd.hdr.dest_port = cvs_handle; + cvs_unmap_mem_cmd.hdr.token = 0; + cvs_unmap_mem_cmd.hdr.opcode = VSS_ICOMMON_CMD_UNMAP_MEMORY; + + cvs_unmap_mem_cmd.vss_unmap_mem.phys_addr = cal_paddr; + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_unmap_mem_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_register_cal_cmd(struct voice_data *v) +{ + struct cvp_register_cal_data_cmd cvp_reg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get the cvp cal data */ + get_all_vocproc_cal(&cal_block); + if (cal_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) { + cal_paddr = common.cvp_cal.phy; + + memcpy(common.cvp_cal.buf, + (void *)cal_block.cal_kvaddr, + cal_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = cal_block.cal_paddr; + } + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_reg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_reg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_reg_cal_cmd) - APR_HDR_SIZE); + cvp_reg_cal_cmd.hdr.src_port = v->session_id; + cvp_reg_cal_cmd.hdr.dest_port = cvp_handle; + cvp_reg_cal_cmd.hdr.token = 0; + cvp_reg_cal_cmd.hdr.opcode = VSS_IVOCPROC_CMD_REGISTER_CALIBRATION_DATA; + + cvp_reg_cal_cmd.cvp_cal_data.phys_addr = cal_paddr; + cvp_reg_cal_cmd.cvp_cal_data.mem_size = cal_block.cal_size; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_reg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_deregister_cal_cmd(struct voice_data *v) +{ + struct cvp_deregister_cal_data_cmd cvp_dereg_cal_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + get_all_vocproc_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_dereg_cal_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_dereg_cal_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_dereg_cal_cmd) - APR_HDR_SIZE); + cvp_dereg_cal_cmd.hdr.src_port = v->session_id; + cvp_dereg_cal_cmd.hdr.dest_port = cvp_handle; + cvp_dereg_cal_cmd.hdr.token = 0; + cvp_dereg_cal_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_dereg_cal_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_register_vol_cal_table_cmd(struct voice_data *v) +{ + struct cvp_register_vol_cal_table_cmd cvp_reg_cal_tbl_cmd; + struct acdb_cal_block vol_block; + struct acdb_cal_block voc_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + uint32_t cal_paddr; + + /* get the cvp vol cal data */ + get_all_vocvol_cal(&vol_block); + get_all_vocproc_cal(&voc_block); + + if (vol_block.cal_size == 0) + goto fail; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + if (is_voip_session(v->session_id)) { + if (common.cvp_cal.buf) { + cal_paddr = common.cvp_cal.phy + voc_block.cal_size; + + memcpy(common.cvp_cal.buf + voc_block.cal_size, + (void *) vol_block.cal_kvaddr, + vol_block.cal_size); + } else { + return -EINVAL; + } + } else { + cal_paddr = vol_block.cal_paddr; + } + + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_reg_cal_tbl_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_reg_cal_tbl_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_reg_cal_tbl_cmd) - APR_HDR_SIZE); + cvp_reg_cal_tbl_cmd.hdr.src_port = v->session_id; + cvp_reg_cal_tbl_cmd.hdr.dest_port = cvp_handle; + cvp_reg_cal_tbl_cmd.hdr.token = 0; + cvp_reg_cal_tbl_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE; + + cvp_reg_cal_tbl_cmd.cvp_vol_cal_tbl.phys_addr = cal_paddr; + cvp_reg_cal_tbl_cmd.cvp_vol_cal_tbl.mem_size = vol_block.cal_size; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_reg_cal_tbl_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal table,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_cvp_deregister_vol_cal_table_cmd(struct voice_data *v) +{ + struct cvp_deregister_vol_cal_table_cmd cvp_dereg_cal_tbl_cmd; + struct acdb_cal_block cal_block; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + get_all_vocvol_cal(&cal_block); + if (cal_block.cal_size == 0) + return 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* fill in the header */ + cvp_dereg_cal_tbl_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_dereg_cal_tbl_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_dereg_cal_tbl_cmd) - APR_HDR_SIZE); + cvp_dereg_cal_tbl_cmd.hdr.src_port = v->session_id; + cvp_dereg_cal_tbl_cmd.hdr.dest_port = cvp_handle; + cvp_dereg_cal_tbl_cmd.hdr.token = 0; + cvp_dereg_cal_tbl_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_dereg_cal_tbl_cmd); + if (ret < 0) { + pr_err("Fail: sending cvp cal table,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; + +} + +static int voice_send_set_widevoice_enable_cmd(struct voice_data *v) +{ + struct mvm_set_widevoice_enable_cmd mvm_set_wv_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + /* fill in the header */ + mvm_set_wv_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_set_wv_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_wv_cmd) - APR_HDR_SIZE); + mvm_set_wv_cmd.hdr.src_port = v->session_id; + mvm_set_wv_cmd.hdr.dest_port = mvm_handle; + mvm_set_wv_cmd.hdr.token = 0; + mvm_set_wv_cmd.hdr.opcode = VSS_IWIDEVOICE_CMD_SET_WIDEVOICE; + + mvm_set_wv_cmd.vss_set_wv.enable = v->wv_enable; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_wv_cmd); + if (ret < 0) { + pr_err("Fail: sending mvm set widevoice enable,\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; +} + +static int voice_send_set_pp_enable_cmd(struct voice_data *v, + uint32_t module_id, int enable) +{ + struct cvs_set_pp_enable_cmd cvs_set_pp_cmd; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* fill in the header */ + cvs_set_pp_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_set_pp_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_pp_cmd) - APR_HDR_SIZE); + cvs_set_pp_cmd.hdr.src_port = v->session_id; + cvs_set_pp_cmd.hdr.dest_port = cvs_handle; + cvs_set_pp_cmd.hdr.token = 0; + cvs_set_pp_cmd.hdr.opcode = VSS_ICOMMON_CMD_SET_UI_PROPERTY; + + cvs_set_pp_cmd.vss_set_pp.module_id = module_id; + cvs_set_pp_cmd.vss_set_pp.param_id = VOICE_PARAM_MOD_ENABLE; + cvs_set_pp_cmd.vss_set_pp.param_size = MOD_ENABLE_PARAM_LEN; + cvs_set_pp_cmd.vss_set_pp.reserved = 0; + cvs_set_pp_cmd.vss_set_pp.enable = enable; + cvs_set_pp_cmd.vss_set_pp.reserved_field = 0; + pr_debug("voice_send_set_pp_enable_cmd, module_id=%d, enable=%d\n", + module_id, enable); + + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_pp_cmd); + if (ret < 0) { + pr_err("Fail: sending cvs set slowtalk enable,\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; +} + +static int voice_setup_vocproc(struct voice_data *v) +{ + struct cvp_create_full_ctl_session_cmd cvp_session_cmd; + int ret = 0; + void *apr_cvp; + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + /* create cvp session and wait for response */ + cvp_session_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_session_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_session_cmd) - APR_HDR_SIZE); + pr_debug(" send create cvp session, pkt size = %d\n", + cvp_session_cmd.hdr.pkt_size); + cvp_session_cmd.hdr.src_port = v->session_id; + cvp_session_cmd.hdr.dest_port = 0; + cvp_session_cmd.hdr.token = 0; + cvp_session_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION; + + /* Use default topology if invalid value in ACDB */ + cvp_session_cmd.cvp_session.tx_topology_id = + get_voice_tx_topology(); + if (cvp_session_cmd.cvp_session.tx_topology_id == 0) + cvp_session_cmd.cvp_session.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + + cvp_session_cmd.cvp_session.rx_topology_id = + get_voice_rx_topology(); + if (cvp_session_cmd.cvp_session.rx_topology_id == 0) + cvp_session_cmd.cvp_session.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + + cvp_session_cmd.cvp_session.direction = 2; /*tx and rx*/ + cvp_session_cmd.cvp_session.network_id = VSS_NETWORK_ID_DEFAULT; + cvp_session_cmd.cvp_session.tx_port_id = v->dev_tx.port_id; + cvp_session_cmd.cvp_session.rx_port_id = v->dev_rx.port_id; + + pr_debug("topology=%d net_id=%d, dir=%d tx_port_id=%d, rx_port_id=%d\n", + cvp_session_cmd.cvp_session.tx_topology_id, + cvp_session_cmd.cvp_session.network_id, + cvp_session_cmd.cvp_session.direction, + cvp_session_cmd.cvp_session.tx_port_id, + cvp_session_cmd.cvp_session.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_session_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* send cvs cal */ + ret = voice_send_cvs_map_memory_cmd(v); + if (!ret) + voice_send_cvs_register_cal_cmd(v); + + /* send cvp and vol cal */ + ret = voice_send_cvp_map_memory_cmd(v); + if (!ret) { + voice_send_cvp_register_cal_cmd(v); + voice_send_cvp_register_vol_cal_table_cmd(v); + } + + /* enable vocproc */ + ret = voice_send_enable_vocproc_cmd(v); + if (ret < 0) + goto fail; + + /* attach vocproc */ + ret = voice_send_attach_vocproc_cmd(v); + if (ret < 0) + goto fail; + + /* send tty mode if tty device is used */ + voice_send_tty_mode_cmd(v); + + /* enable widevoice if wv_enable is set */ + if (v->wv_enable) + voice_send_set_widevoice_enable_cmd(v); + + /* enable slowtalk if st_enable is set */ + if (v->st_enable) + voice_send_set_pp_enable_cmd(v, MODULE_ID_VOICE_MODULE_ST, + v->st_enable); + voice_send_set_pp_enable_cmd(v, MODULE_ID_VOICE_MODULE_FENS, + v->fens_enable); + + if (is_voip_session(v->session_id)) + voice_send_netid_timing_cmd(v); + + /* Start in-call music delivery if this feature is enabled */ + if (v->music_info.play_enable) + voice_cvs_start_playback(v); + + /* Start in-call recording if this feature is enabled */ + if (v->rec_info.rec_enable) + voice_cvs_start_record(v, v->rec_info.rec_mode); + + rtac_add_voice(voice_get_cvs_handle(v), + voice_get_cvp_handle(v), + v->dev_rx.port_id, v->dev_tx.port_id, + v->session_id); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_enable_vocproc_cmd(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvp_enable_cmd; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* enable vocproc and wait for respose */ + cvp_enable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_enable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_enable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_enable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_enable_cmd.pkt_size, cvp_handle); + cvp_enable_cmd.src_port = v->session_id; + cvp_enable_cmd.dest_port = cvp_handle; + cvp_enable_cmd.token = 0; + cvp_enable_cmd.opcode = VSS_IVOCPROC_CMD_ENABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_enable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_ENABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_netid_timing_cmd(struct voice_data *v) +{ + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + struct mvm_set_network_cmd mvm_set_network; + struct mvm_set_voice_timing_cmd mvm_set_voice_timing; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + ret = voice_config_cvs_vocoder(v); + if (ret < 0) { + pr_err("%s: Error %d configuring CVS voc", + __func__, ret); + goto fail; + } + /* Set network ID. */ + pr_debug("Setting network ID\n"); + + mvm_set_network.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_set_network.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_network) - APR_HDR_SIZE); + mvm_set_network.hdr.src_port = v->session_id; + mvm_set_network.hdr.dest_port = mvm_handle; + mvm_set_network.hdr.token = 0; + mvm_set_network.hdr.opcode = VSS_ICOMMON_CMD_SET_NETWORK; + mvm_set_network.network.network_id = common.mvs_info.network_type; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_network); + if (ret < 0) { + pr_err("%s: Error %d sending SET_NETWORK\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* Set voice timing. */ + pr_debug("Setting voice timing\n"); + + mvm_set_voice_timing.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_set_voice_timing.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_voice_timing) - + APR_HDR_SIZE); + mvm_set_voice_timing.hdr.src_port = v->session_id; + mvm_set_voice_timing.hdr.dest_port = mvm_handle; + mvm_set_voice_timing.hdr.token = 0; + mvm_set_voice_timing.hdr.opcode = VSS_ICOMMON_CMD_SET_VOICE_TIMING; + mvm_set_voice_timing.timing.mode = 0; + mvm_set_voice_timing.timing.enc_offset = 8000; + mvm_set_voice_timing.timing.dec_req_offset = 3300; + mvm_set_voice_timing.timing.dec_offset = 8300; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_voice_timing); + if (ret < 0) { + pr_err("%s: Error %d sending SET_TIMING\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_attach_vocproc_cmd(struct voice_data *v) +{ + int ret = 0; + struct mvm_attach_vocproc_cmd mvm_a_vocproc_cmd; + void *apr_mvm; + u16 mvm_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + /* attach vocproc and wait for response */ + mvm_a_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_a_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_a_vocproc_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_a_vocproc_cmd pkt size = %d\n", + mvm_a_vocproc_cmd.hdr.pkt_size); + mvm_a_vocproc_cmd.hdr.src_port = v->session_id; + mvm_a_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_a_vocproc_cmd.hdr.token = 0; + mvm_a_vocproc_cmd.hdr.opcode = VSS_IMVM_CMD_ATTACH_VOCPROC; + mvm_a_vocproc_cmd.mvm_attach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_a_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_ATTACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_destroy_vocproc(struct voice_data *v) +{ + struct mvm_detach_vocproc_cmd mvm_d_vocproc_cmd; + struct apr_hdr cvp_destroy_session_cmd; + int ret = 0; + void *apr_mvm, *apr_cvp; + u16 mvm_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + apr_cvp = common.apr_q6_cvp; + + if (!apr_mvm || !apr_cvp) { + pr_err("%s: apr_mvm or apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + /* stop playback or recording */ + v->music_info.force = 1; + voice_cvs_stop_playback(v); + voice_cvs_stop_record(v); + /* send stop voice cmd */ + voice_send_stop_voice_cmd(v); + + /* detach VOCPROC and wait for response from mvm */ + mvm_d_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_d_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_d_vocproc_cmd) - APR_HDR_SIZE); + pr_debug("mvm_d_vocproc_cmd pkt size = %d\n", + mvm_d_vocproc_cmd.hdr.pkt_size); + mvm_d_vocproc_cmd.hdr.src_port = v->session_id; + mvm_d_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_d_vocproc_cmd.hdr.token = 0; + mvm_d_vocproc_cmd.hdr.opcode = VSS_IMVM_CMD_DETACH_VOCPROC; + mvm_d_vocproc_cmd.mvm_detach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_d_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_DETACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* deregister cvp and vol cal */ + voice_send_cvp_deregister_vol_cal_table_cmd(v); + voice_send_cvp_deregister_cal_cmd(v); + voice_send_cvp_unmap_memory_cmd(v); + + /* deregister cvs cal */ + voice_send_cvs_deregister_cal_cmd(v); + voice_send_cvs_unmap_memory_cmd(v); + + /* destrop cvp session */ + cvp_destroy_session_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_destroy_session_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_destroy_session_cmd) - APR_HDR_SIZE); + pr_debug("cvp_destroy_session_cmd pkt size = %d\n", + cvp_destroy_session_cmd.pkt_size); + cvp_destroy_session_cmd.src_port = v->session_id; + cvp_destroy_session_cmd.dest_port = cvp_handle; + cvp_destroy_session_cmd.token = 0; + cvp_destroy_session_cmd.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_destroy_session_cmd); + if (ret < 0) { + pr_err("Fail in sending APRV2_IBASIC_CMD_DESTROY_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + rtac_remove_voice(voice_get_cvs_handle(v)); + cvp_handle = 0; + voice_set_cvp_handle(v, cvp_handle); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_mute_cmd(struct voice_data *v) +{ + struct cvs_set_mute_cmd cvs_mute_cmd; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* send mute/unmute to cvs */ + cvs_mute_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_mute_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_mute_cmd) - APR_HDR_SIZE); + cvs_mute_cmd.hdr.src_port = v->session_id; + cvs_mute_cmd.hdr.dest_port = cvs_handle; + cvs_mute_cmd.hdr.token = 0; + cvs_mute_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MUTE; + cvs_mute_cmd.cvs_set_mute.direction = 0; /*tx*/ + cvs_mute_cmd.cvs_set_mute.mute_flag = v->dev_tx.mute; + + pr_info(" mute value =%d\n", cvs_mute_cmd.cvs_set_mute.mute_flag); + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_mute_cmd); + if (ret < 0) { + pr_err("Fail: send STREAM SET MUTE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) + pr_err("%s: wait_event timeout\n", __func__); + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_rx_device_mute_cmd(struct voice_data *v) +{ + struct cvp_set_mute_cmd cvp_mute_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + cvp_mute_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_mute_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_mute_cmd) - APR_HDR_SIZE); + cvp_mute_cmd.hdr.src_port = v->session_id; + cvp_mute_cmd.hdr.dest_port = cvp_handle; + cvp_mute_cmd.hdr.token = 0; + cvp_mute_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_MUTE; + cvp_mute_cmd.cvp_set_mute.direction = 1; + cvp_mute_cmd.cvp_set_mute.mute_flag = v->dev_rx.mute; + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_mute_cmd); + if (ret < 0) { + pr_err("Fail in sending RX device mute cmd\n"); + return -EINVAL; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + return 0; +} + +static int voice_send_vol_index_cmd(struct voice_data *v) +{ + struct cvp_set_rx_volume_index_cmd cvp_vol_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = common.apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* send volume index to cvp */ + cvp_vol_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_vol_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_vol_cmd) - APR_HDR_SIZE); + cvp_vol_cmd.hdr.src_port = v->session_id; + cvp_vol_cmd.hdr.dest_port = cvp_handle; + cvp_vol_cmd.hdr.token = 0; + cvp_vol_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX; + cvp_vol_cmd.cvp_set_vol_idx.vol_index = v->dev_rx.volume; + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_vol_cmd); + if (ret < 0) { + pr_err("Fail in sending RX VOL INDEX\n"); + return -EINVAL; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + return 0; +} + +static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + struct cvs_start_record_cmd cvs_start_record; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (!v->rec_info.recording) { + cvs_start_record.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_start_record.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_start_record) - APR_HDR_SIZE); + cvs_start_record.hdr.src_port = v->session_id; + cvs_start_record.hdr.dest_port = cvs_handle; + cvs_start_record.hdr.token = 0; + cvs_start_record.hdr.opcode = VSS_ISTREAM_CMD_START_RECORD; + + if (rec_mode == VOC_REC_UPLINK) { + cvs_start_record.rec_mode.rx_tap_point = + VSS_TAP_POINT_NONE; + cvs_start_record.rec_mode.tx_tap_point = + VSS_TAP_POINT_STREAM_END; + } else if (rec_mode == VOC_REC_DOWNLINK) { + cvs_start_record.rec_mode.rx_tap_point = + VSS_TAP_POINT_STREAM_END; + cvs_start_record.rec_mode.tx_tap_point = + VSS_TAP_POINT_NONE; + } else if (rec_mode == VOC_REC_BOTH) { + cvs_start_record.rec_mode.rx_tap_point = + VSS_TAP_POINT_STREAM_END; + cvs_start_record.rec_mode.tx_tap_point = + VSS_TAP_POINT_STREAM_END; + } else { + pr_err("%s: Invalid in-call rec_mode %d\n", __func__, + rec_mode); + + ret = -EINVAL; + goto fail; + } + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_start_record); + if (ret < 0) { + pr_err("%s: Error %d sending START_RECORD\n", __func__, + ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + v->rec_info.recording = 1; + } else { + pr_debug("%s: Start record already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_record(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + struct apr_hdr cvs_stop_record; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (v->rec_info.recording) { + cvs_stop_record.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_stop_record.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_stop_record) - APR_HDR_SIZE); + cvs_stop_record.src_port = v->session_id; + cvs_stop_record.dest_port = cvs_handle; + cvs_stop_record.token = 0; + cvs_stop_record.opcode = VSS_ISTREAM_CMD_STOP_RECORD; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_stop_record); + if (ret < 0) { + pr_err("%s: Error %d sending STOP_RECORD\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + v->rec_info.recording = 0; + } else { + pr_debug("%s: Stop record already sent\n", __func__); + } + + return 0; + +fail: + + return ret; +} + +int voc_start_record(uint32_t port_id, uint32_t set) +{ + int ret = 0; + int rec_mode = 0; + u16 cvs_handle; + int i, rec_set = 0; + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + pr_debug("%s: i:%d port_id: %d, set: %d\n", + __func__, i, port_id, set); + + mutex_lock(&v->lock); + rec_mode = v->rec_info.rec_mode; + rec_set = set; + if (set) { + if ((v->rec_route_state.ul_flag != 0) && + (v->rec_route_state.dl_flag != 0)) { + pr_debug("%s: i=%d, rec mode already set.\n", + __func__, i); + mutex_unlock(&v->lock); + if (i < MAX_VOC_SESSIONS) + continue; + else + return 0; + } + + if (port_id == VOICE_RECORD_TX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag == 0)) { + rec_mode = VOC_REC_UPLINK; + v->rec_route_state.ul_flag = 1; + } else if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + rec_mode = VOC_REC_BOTH; + v->rec_route_state.ul_flag = 1; + } + } else if (port_id == VOICE_RECORD_RX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag == 0)) { + rec_mode = VOC_REC_DOWNLINK; + v->rec_route_state.dl_flag = 1; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag == 0)) { + voice_cvs_stop_record(v); + rec_mode = VOC_REC_BOTH; + v->rec_route_state.dl_flag = 1; + } + } + rec_set = 1; + } else { + if ((v->rec_route_state.ul_flag == 0) && + (v->rec_route_state.dl_flag == 0)) { + pr_debug("%s: i=%d, rec already stops.\n", + __func__, i); + mutex_unlock(&v->lock); + if (i < MAX_VOC_SESSIONS) + continue; + else + return 0; + } + + if (port_id == VOICE_RECORD_TX) { + if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag == 0)) { + v->rec_route_state.ul_flag = 0; + rec_set = 0; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + v->rec_route_state.ul_flag = 0; + rec_mode = VOC_REC_DOWNLINK; + rec_set = 1; + } + } else if (port_id == VOICE_RECORD_RX) { + if ((v->rec_route_state.ul_flag == 0) + && (v->rec_route_state.dl_flag != 0)) { + v->rec_route_state.dl_flag = 0; + rec_set = 0; + } else if ((v->rec_route_state.ul_flag != 0) + && (v->rec_route_state.dl_flag != 0)) { + voice_cvs_stop_record(v); + v->rec_route_state.dl_flag = 0; + rec_mode = VOC_REC_UPLINK; + rec_set = 1; + } + } + } + pr_debug("%s: i=%d, mode =%d, set =%d\n", __func__, + i, rec_mode, rec_set); + cvs_handle = voice_get_cvs_handle(v); + + if (cvs_handle != 0) { + if (rec_set) + ret = voice_cvs_start_record(v, rec_mode); + else + ret = voice_cvs_stop_record(v); + } + + /* Cache the value */ + v->rec_info.rec_enable = rec_set; + v->rec_info.rec_mode = rec_mode; + + mutex_unlock(&v->lock); + } + + return ret; +} + +static int voice_cvs_start_playback(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvs_start_playback; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (!v->music_info.playing && v->music_info.count) { + cvs_start_playback.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_start_playback.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_start_playback) - APR_HDR_SIZE); + cvs_start_playback.src_port = v->session_id; + cvs_start_playback.dest_port = cvs_handle; + cvs_start_playback.token = 0; + cvs_start_playback.opcode = VSS_ISTREAM_CMD_START_PLAYBACK; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_start_playback); + + if (ret < 0) { + pr_err("%s: Error %d sending START_PLAYBACK\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + v->music_info.playing = 1; + } else { + pr_debug("%s: Start playback already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_playback(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvs_stop_playback; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = common.apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + if (v->music_info.playing && ((!v->music_info.count) || + (v->music_info.force))) { + cvs_stop_playback.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_stop_playback.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_stop_playback) - APR_HDR_SIZE); + cvs_stop_playback.src_port = v->session_id; + cvs_stop_playback.dest_port = cvs_handle; + cvs_stop_playback.token = 0; + + cvs_stop_playback.opcode = VSS_ISTREAM_CMD_STOP_PLAYBACK; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_stop_playback); + if (ret < 0) { + pr_err("%s: Error %d sending STOP_PLAYBACK\n", + __func__, ret); + + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + v->music_info.playing = 0; + v->music_info.force = 0; + } else { + pr_debug("%s: Stop playback already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +int voc_start_playback(uint32_t set) +{ + int ret = 0; + u16 cvs_handle; + int i; + + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + struct voice_data *v = &common.voice[i]; + + mutex_lock(&v->lock); + v->music_info.play_enable = set; + if (set) + v->music_info.count++; + else + v->music_info.count--; + pr_debug("%s: music_info count =%d\n", __func__, + v->music_info.count); + + cvs_handle = voice_get_cvs_handle(v); + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_playback(v); + else + ret = voice_cvs_stop_playback(v); + } + + mutex_unlock(&v->lock); + } + + return ret; +} + +int voc_disable_cvp(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) + afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, + 0, 0); + + rtac_remove_voice(voice_get_cvs_handle(v)); + /* send cmd to dsp to disable vocproc */ + ret = voice_send_disable_vocproc_cmd(v); + if (ret < 0) { + pr_err("%s: disable vocproc failed\n", __func__); + goto fail; + } + + /* deregister cvp and vol cal */ + voice_send_cvp_deregister_vol_cal_table_cmd(v); + voice_send_cvp_deregister_cal_cmd(v); + voice_send_cvp_unmap_memory_cmd(v); + + v->voc_state = VOC_CHANGE; + } + +fail: mutex_unlock(&v->lock); + + return ret; +} + +int voc_enable_cvp(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct sidetone_cal sidetone_cal_data; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_CHANGE) { + ret = voice_send_set_device_cmd(v); + if (ret < 0) { + pr_err("%s: set device failed\n", __func__); + goto fail; + } + /* send cvp and vol cal */ + ret = voice_send_cvp_map_memory_cmd(v); + if (!ret) { + voice_send_cvp_register_cal_cmd(v); + voice_send_cvp_register_vol_cal_table_cmd(v); + } + ret = voice_send_enable_vocproc_cmd(v); + if (ret < 0) { + pr_err("%s: enable vocproc failed\n", __func__); + goto fail; + + } + /* send tty mode if tty device is used */ + voice_send_tty_mode_cmd(v); + + /* enable widevoice if wv_enable is set */ + if (v->wv_enable) + voice_send_set_widevoice_enable_cmd(v); + + /* enable slowtalk */ + if (v->st_enable) + voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_ST, + v->st_enable); + /* enable FENS */ + if (v->fens_enable) + voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_FENS, + v->fens_enable); + + get_sidetone_cal(&sidetone_cal_data); + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) { + ret = afe_sidetone(v->dev_tx.port_id, + v->dev_rx.port_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + + if (ret < 0) + pr_err("%s: AFE command sidetone failed\n", + __func__); + } + + rtac_add_voice(voice_get_cvs_handle(v), + voice_get_cvp_handle(v), + v->dev_rx.port_id, v->dev_tx.port_id, + v->session_id); + v->voc_state = VOC_RUN; + } + +fail: + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_tx_mute(uint16_t session_id, uint32_t dir, uint32_t mute) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->dev_tx.mute = mute; + + if (v->voc_state == VOC_RUN) + ret = voice_send_mute_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_rx_device_mute(uint16_t session_id, uint32_t mute) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->dev_rx.mute = mute; + + if (v->voc_state == VOC_RUN) + ret = voice_send_rx_device_mute_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_get_rx_device_mute(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->dev_rx.mute; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_tty_mode(uint16_t session_id, uint8_t tty_mode) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->tty_mode = tty_mode; + + mutex_unlock(&v->lock); + + return ret; +} + +uint8_t voc_get_tty_mode(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->tty_mode; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_widevoice_enable(uint16_t session_id, uint32_t wv_enable) +{ + struct voice_data *v = voice_get_session(session_id); + u16 mvm_handle; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->wv_enable = wv_enable; + + mvm_handle = voice_get_mvm_handle(v); + + if (mvm_handle != 0) + voice_send_set_widevoice_enable_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +uint32_t voc_get_widevoice_enable(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + ret = v->wv_enable; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_pp_enable(uint16_t session_id, uint32_t module_id, uint32_t enable) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + if (module_id == MODULE_ID_VOICE_MODULE_ST) + v->st_enable = enable; + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + v->fens_enable = enable; + + if (v->voc_state == VOC_RUN) { + if (module_id == MODULE_ID_VOICE_MODULE_ST) + ret = voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_ST, + enable); + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + ret = voice_send_set_pp_enable_cmd(v, + MODULE_ID_VOICE_MODULE_FENS, + enable); + } + mutex_unlock(&v->lock); + + return ret; +} + +int voc_get_pp_enable(uint16_t session_id, uint32_t module_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + if (module_id == MODULE_ID_VOICE_MODULE_ST) + ret = v->st_enable; + else if (module_id == MODULE_ID_VOICE_MODULE_FENS) + ret = v->fens_enable; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_rx_vol_index(uint16_t session_id, uint32_t dir, uint32_t vol_idx) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + v->dev_rx.volume = vol_idx; + + if (v->voc_state == VOC_RUN) + ret = voice_send_vol_index_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_rxtx_port(uint16_t session_id, uint32_t port_id, uint32_t dev_type) +{ + struct voice_data *v = voice_get_session(session_id); + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + pr_debug("%s: port_id=%d, type=%d\n", __func__, port_id, dev_type); + + mutex_lock(&v->lock); + + if (dev_type == DEV_RX) + v->dev_rx.port_id = port_id; + else + v->dev_tx.port_id = port_id; + + mutex_unlock(&v->lock); + + return 0; +} + +int voc_set_route_flag(uint16_t session_id, uint8_t path_dir, uint8_t set) +{ + struct voice_data *v = voice_get_session(session_id); + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + pr_debug("%s: path_dir=%d, set=%d\n", __func__, path_dir, set); + + mutex_lock(&v->lock); + + if (path_dir == RX_PATH) + v->voc_route_state.rx_route_flag = set; + else + v->voc_route_state.tx_route_flag = set; + + mutex_unlock(&v->lock); + + return 0; +} + +uint8_t voc_get_route_flag(uint16_t session_id, uint8_t path_dir) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return 0; + } + + mutex_lock(&v->lock); + + if (path_dir == RX_PATH) + ret = v->voc_route_state.rx_route_flag; + else + ret = v->voc_route_state.tx_route_flag; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_end_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN || v->voc_state == VOC_STANDBY) { + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) + afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, + 0, 0); + ret = voice_destroy_vocproc(v); + if (ret < 0) + pr_err("%s: destroy voice failed\n", __func__); + voice_destroy_mvm_cvs_session(v); + + v->voc_state = VOC_RELEASE; + } + mutex_unlock(&v->lock); + return ret; +} + +int voc_resume_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct apr_hdr mvm_start_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + pr_debug("%s:\n", __func__); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = common.apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + mvm_start_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_start_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_start_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_start_voice_cmd pkt size = %d\n", + mvm_start_voice_cmd.pkt_size); + mvm_start_voice_cmd.src_port = v->session_id; + mvm_start_voice_cmd.dest_port = mvm_handle; + mvm_start_voice_cmd.token = 0; + mvm_start_voice_cmd.opcode = VSS_IMVM_CMD_START_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_start_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_START_VOICE\n"); + goto fail; + } + v->voc_state = VOC_RUN; + return 0; +fail: + return -EINVAL; +} + +int voc_start_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct sidetone_cal sidetone_cal_data; + int ret = 0; + + if (v == NULL) { + pr_err("%s: invalid session_id 0x%x\n", __func__, session_id); + + return -EINVAL; + } + + mutex_lock(&v->lock); + + if ((v->voc_state == VOC_INIT) || + (v->voc_state == VOC_RELEASE)) { + ret = voice_apr_register(); + if (ret < 0) { + pr_err("%s: apr register failed\n", __func__); + goto fail; + } + ret = voice_create_mvm_cvs_session(v); + if (ret < 0) { + pr_err("create mvm and cvs failed\n"); + goto fail; + } + ret = voice_send_dual_control_cmd(v); + if (ret < 0) { + pr_err("Err Dual command failed\n"); + goto fail; + } + ret = voice_setup_vocproc(v); + if (ret < 0) { + pr_err("setup voice failed\n"); + goto fail; + } + ret = voice_send_start_voice_cmd(v); + if (ret < 0) { + pr_err("start voice failed\n"); + goto fail; + } + get_sidetone_cal(&sidetone_cal_data); + if (v->dev_tx.port_id != RT_PROXY_PORT_001_TX && + v->dev_rx.port_id != RT_PROXY_PORT_001_RX) { + ret = afe_sidetone(v->dev_tx.port_id, + v->dev_rx.port_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + if (ret < 0) + pr_err("AFE command sidetone failed\n"); + } + + v->voc_state = VOC_RUN; + } else if (v->voc_state == VOC_STANDBY) { + pr_err("Error: PCM Prepare when in Standby\n"); + ret = -EINVAL; + goto fail; + } +fail: mutex_unlock(&v->lock); + return ret; +} + +int voc_standby_voice_call(uint16_t session_id) +{ + struct voice_data *v = voice_get_session(session_id); + struct apr_hdr mvm_standby_voice_cmd; + void *apr_mvm; + u16 mvm_handle; + int ret = 0; + + pr_debug("%s: voc state=%d", __func__, v->voc_state); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + if (v->voc_state == VOC_RUN) { + apr_mvm = common.apr_q6_mvm; + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + ret = -EINVAL; + goto fail; + } + mvm_handle = voice_get_mvm_handle(v); + mvm_standby_voice_cmd.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_standby_voice_cmd.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_standby_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_standby_voice_cmd pkt size = %d\n", + mvm_standby_voice_cmd.pkt_size); + mvm_standby_voice_cmd.src_port = v->session_id; + mvm_standby_voice_cmd.dest_port = mvm_handle; + mvm_standby_voice_cmd.token = 0; + mvm_standby_voice_cmd.opcode = VSS_IMVM_CMD_STANDBY_VOICE; + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, + (uint32_t *)&mvm_standby_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_STANDBY_VOICE\n"); + ret = -EINVAL; + goto fail; + } + v->voc_state = VOC_STANDBY; + } +fail: + return ret; +} + +void voc_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data) +{ + common.mvs_info.ul_cb = ul_cb; + common.mvs_info.dl_cb = dl_cb; + common.mvs_info.private_data = private_data; +} + +void voc_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type, + uint32_t dtx_mode) +{ + common.mvs_info.media_type = media_type; + common.mvs_info.rate = rate; + common.mvs_info.network_type = network_type; + common.mvs_info.dtx_mode = dtx_mode; +} + +static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event received in Voice service\n", + __func__); + + apr_reset(c->apr_q6_mvm); + c->apr_q6_mvm = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].mvm_handle = 0; + + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /* ping mvm service ACK */ + switch (ptr[0]) { + case VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION: + case VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION: + /* Passive session is used for CS call + * Full session is used for VoIP call. */ + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + pr_debug("%s: MVM handle is %d\n", + __func__, data->src_port); + voice_set_mvm_handle(v, data->src_port); + } else + pr_err("got NACK for sending \ + MVM create session \n"); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + break; + case VSS_IMVM_CMD_START_VOICE: + case VSS_IMVM_CMD_ATTACH_VOCPROC: + case VSS_IMVM_CMD_STOP_VOICE: + case VSS_IMVM_CMD_DETACH_VOCPROC: + case VSS_ISTREAM_CMD_SET_TTY_MODE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + case VSS_IMVM_CMD_ATTACH_STREAM: + case VSS_IMVM_CMD_DETACH_STREAM: + case VSS_ICOMMON_CMD_SET_NETWORK: + case VSS_ICOMMON_CMD_SET_VOICE_TIMING: + case VSS_IWIDEVOICE_CMD_SET_WIDEVOICE: + case VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL: + case VSS_IMVM_CMD_STANDBY_VOICE: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + break; + default: + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + break; + } + } + } + + return 0; +} + +static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + pr_debug("%s: session_id 0x%x\n", __func__, data->dest_port); + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event received in Voice service\n", + __func__); + + apr_reset(c->apr_q6_cvs); + c->apr_q6_cvs = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvs_handle = 0; + + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /*response from CVS */ + switch (ptr[0]) { + case VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION: + case VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION: + if (!ptr[1]) { + pr_debug("%s: CVS handle is %d\n", + __func__, data->src_port); + voice_set_cvs_handle(v, data->src_port); + } else + pr_err("got NACK for sending \ + CVS create session \n"); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + break; + case VSS_ISTREAM_CMD_SET_MUTE: + case VSS_ISTREAM_CMD_SET_MEDIA_TYPE: + case VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE: + case VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE: + case VSS_ISTREAM_CMD_SET_ENC_DTX_MODE: + case VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + case VSS_ISTREAM_CMD_REGISTER_CALIBRATION_DATA: + case VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA: + case VSS_ICOMMON_CMD_MAP_MEMORY: + case VSS_ICOMMON_CMD_UNMAP_MEMORY: + case VSS_ICOMMON_CMD_SET_UI_PROPERTY: + case VSS_ISTREAM_CMD_START_PLAYBACK: + case VSS_ISTREAM_CMD_STOP_PLAYBACK: + case VSS_ISTREAM_CMD_START_RECORD: + case VSS_ISTREAM_CMD_STOP_RECORD: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + break; + case VOICE_CMD_SET_PARAM: + rtac_make_voice_callback(RTAC_CVS, ptr, + data->payload_size); + break; + default: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + break; + } + } + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_ENC_BUFFER) { + uint32_t *voc_pkt = data->payload; + uint32_t pkt_len = data->payload_size; + + if (voc_pkt != NULL && c->mvs_info.ul_cb != NULL) { + pr_debug("%s: Media type is 0x%x\n", + __func__, voc_pkt[0]); + + /* Remove media ID from payload. */ + voc_pkt++; + pkt_len = pkt_len - 4; + + c->mvs_info.ul_cb((uint8_t *)voc_pkt, + pkt_len, + c->mvs_info.private_data); + } else + pr_err("%s: voc_pkt is 0x%x ul_cb is 0x%x\n", + __func__, (unsigned int)voc_pkt, + (unsigned int) c->mvs_info.ul_cb); + } else if (data->opcode == VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER) { + struct cvs_send_dec_buf_cmd send_dec_buf; + int ret = 0; + uint32_t pkt_len = 0; + + if (c->mvs_info.dl_cb != NULL) { + send_dec_buf.dec_buf.media_id = c->mvs_info.media_type; + + c->mvs_info.dl_cb( + (uint8_t *)&send_dec_buf.dec_buf.packet_data, + &pkt_len, + c->mvs_info.private_data); + + send_dec_buf.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + send_dec_buf.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(send_dec_buf.dec_buf.media_id) + pkt_len); + send_dec_buf.hdr.src_port = v->session_id; + send_dec_buf.hdr.dest_port = voice_get_cvs_handle(v); + send_dec_buf.hdr.token = 0; + send_dec_buf.hdr.opcode = + VSS_ISTREAM_EVT_SEND_DEC_BUFFER; + + ret = apr_send_pkt(c->apr_q6_cvs, + (uint32_t *) &send_dec_buf); + if (ret < 0) { + pr_err("%s: Error %d sending DEC_BUF\n", + __func__, ret); + goto fail; + } + } else + pr_debug("%s: dl_cb is NULL\n", __func__); + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_DEC_BUFFER) { + pr_debug("Send dec buf resp\n"); + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVS, data->payload, + data->payload_size); + } else + pr_debug("Unknown opcode 0x%x\n", data->opcode); + +fail: + return 0; +} + +static int32_t qdsp_cvp_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr = NULL; + struct common_data *c = NULL; + struct voice_data *v = NULL; + int i = 0; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + + c = priv; + + v = voice_get_session(data->dest_port); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + + return -EINVAL; + } + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event received in Voice service\n", + __func__); + + apr_reset(c->apr_q6_cvp); + c->apr_q6_cvp = NULL; + + /* Sub-system restart is applicable to all sessions. */ + for (i = 0; i < MAX_VOC_SESSIONS; i++) + c->voice[i].cvp_handle = 0; + + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + + switch (ptr[0]) { + case VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION: + /*response from CVP */ + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + voice_set_cvp_handle(v, data->src_port); + pr_debug("cvphdl=%d\n", data->src_port); + } else + pr_err("got NACK from CVP create \ + session response\n"); + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + break; + case VSS_IVOCPROC_CMD_SET_DEVICE: + case VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX: + case VSS_IVOCPROC_CMD_ENABLE: + case VSS_IVOCPROC_CMD_DISABLE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + case VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE: + case VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE: + case VSS_IVOCPROC_CMD_REGISTER_CALIBRATION_DATA: + case VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA: + case VSS_ICOMMON_CMD_MAP_MEMORY: + case VSS_ICOMMON_CMD_UNMAP_MEMORY: + case VSS_IVOCPROC_CMD_SET_MUTE: + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + break; + case VOICE_CMD_SET_PARAM: + rtac_make_voice_callback(RTAC_CVP, ptr, + data->payload_size); + break; + default: + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + break; + } + } + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVP, data->payload, + data->payload_size); + } + return 0; +} + + +static int __init voice_init(void) +{ + int rc = 0, i = 0; + int len; + + memset(&common, 0, sizeof(struct common_data)); + + /* Allocate memory for VoIP calibration */ + common.client = msm_ion_client_create(UINT_MAX, "voip_client"); + if (IS_ERR_OR_NULL((void *)common.client)) { + pr_err("%s: ION create client for Voip failed\n", __func__); + goto cont; + } + common.cvp_cal.handle = ion_alloc(common.client, CVP_CAL_SIZE, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) common.cvp_cal.handle)) { + pr_err("%s: ION memory allocation for CVP failed\n", + __func__); + ion_client_destroy(common.client); + goto cont; + } + + rc = ion_phys(common.client, common.cvp_cal.handle, + (ion_phys_addr_t *)&common.cvp_cal.phy, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for cvp failed, rc = %d\n", + __func__, rc); + ion_free(common.client, common.cvp_cal.handle); + ion_client_destroy(common.client); + goto cont; + } + + common.cvp_cal.buf = ion_map_kernel(common.client, + common.cvp_cal.handle, 0); + if (IS_ERR_OR_NULL((void *) common.cvp_cal.buf)) { + pr_err("%s: ION memory mapping for cvp failed\n", __func__); + common.cvp_cal.buf = NULL; + ion_free(common.client, common.cvp_cal.handle); + ion_client_destroy(common.client); + goto cont; + } + memset((void *)common.cvp_cal.buf, 0, CVP_CAL_SIZE); + + common.cvs_cal.handle = ion_alloc(common.client, CVS_CAL_SIZE, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) common.cvs_cal.handle)) { + pr_err("%s: ION memory allocation for CVS failed\n", + __func__); + goto cont; + } + + rc = ion_phys(common.client, common.cvs_cal.handle, + (ion_phys_addr_t *)&common.cvs_cal.phy, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for cvs failed, rc = %d\n", + __func__, rc); + ion_free(common.client, common.cvs_cal.handle); + goto cont; + } + + common.cvs_cal.buf = ion_map_kernel(common.client, + common.cvs_cal.handle, 0); + if (IS_ERR_OR_NULL((void *) common.cvs_cal.buf)) { + pr_err("%s: ION memory mapping for cvs failed\n", __func__); + common.cvs_cal.buf = NULL; + ion_free(common.client, common.cvs_cal.handle); + goto cont; + } + memset((void *)common.cvs_cal.buf, 0, CVS_CAL_SIZE); +cont: + /* set default value */ + common.default_mute_val = 1; /* default is mute */ + common.default_vol_val = 0; + common.default_sample_val = 8000; + + /* Initialize MVS info. */ + common.mvs_info.network_type = VSS_NETWORK_ID_DEFAULT; + + mutex_init(&common.common_lock); + + for (i = 0; i < MAX_VOC_SESSIONS; i++) { + common.voice[i].session_id = SESSION_ID_BASE + i; + + /* initialize dev_rx and dev_tx */ + common.voice[i].dev_rx.volume = common.default_vol_val; + common.voice[i].dev_rx.mute = 0; + common.voice[i].dev_tx.mute = common.default_mute_val; + + common.voice[i].dev_tx.port_id = 1; + common.voice[i].dev_rx.port_id = 0; + common.voice[i].sidetone_gain = 0x512; + + common.voice[i].voc_state = VOC_INIT; + + init_waitqueue_head(&common.voice[i].mvm_wait); + init_waitqueue_head(&common.voice[i].cvs_wait); + init_waitqueue_head(&common.voice[i].cvp_wait); + + mutex_init(&common.voice[i].lock); + } + + return rc; +} + +device_initcall(voice_init); diff --git a/sound/soc/msm/qdsp6/q6voice.h b/sound/soc/msm/qdsp6/q6voice.h new file mode 100644 index 000000000000..c02389b2a904 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.h @@ -0,0 +1,997 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QDSP6VOICE_H__ +#define __QDSP6VOICE_H__ + +#include +#include + +#define MAX_VOC_PKT_SIZE 642 +#define SESSION_NAME_LEN 20 + +#define VOC_REC_UPLINK 0x00 +#define VOC_REC_DOWNLINK 0x01 +#define VOC_REC_BOTH 0x02 + +struct voice_header { + uint32_t id; + uint32_t data_len; +}; + +struct voice_init { + struct voice_header hdr; + void *cb_handle; +}; + +/* Device information payload structure */ + +struct device_data { + uint32_t volume; /* in index */ + uint32_t mute; + uint32_t sample; + uint32_t enabled; + uint32_t dev_id; + uint32_t port_id; +}; + +struct voice_dev_route_state { + u16 rx_route_flag; + u16 tx_route_flag; +}; + +struct voice_rec_route_state { + u16 ul_flag; + u16 dl_flag; +}; + +enum { + VOC_INIT = 0, + VOC_RUN, + VOC_CHANGE, + VOC_RELEASE, + VOC_STANDBY, +}; + +/* Common */ +#define VSS_ICOMMON_CMD_SET_UI_PROPERTY 0x00011103 +/* Set a UI property */ +#define VSS_ICOMMON_CMD_MAP_MEMORY 0x00011025 +#define VSS_ICOMMON_CMD_UNMAP_MEMORY 0x00011026 +/* General shared memory; byte-accessible, 4 kB-aligned. */ +#define VSS_ICOMMON_MAP_MEMORY_SHMEM8_4K_POOL 3 + +struct vss_icommon_cmd_map_memory_t { + uint32_t phys_addr; + /* Physical address of a memory region; must be at least + * 4 kB aligned. + */ + + uint32_t mem_size; + /* Number of bytes in the region; should be a multiple of 32. */ + + uint16_t mem_pool_id; + /* Type of memory being provided. The memory ID implicitly defines + * the characteristics of the memory. The characteristics might include + * alignment type, permissions, etc. + * Memory pool ID. Possible values: + * 3 -- VSS_ICOMMON_MEM_TYPE_SHMEM8_4K_POOL. + */ +} __packed; + +struct vss_icommon_cmd_unmap_memory_t { + uint32_t phys_addr; + /* Physical address of a memory region; must be at least + * 4 kB aligned. + */ +} __packed; + +struct vss_map_memory_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_map_memory_t vss_map_mem; +} __packed; + +struct vss_unmap_memory_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_unmap_memory_t vss_unmap_mem; +} __packed; + +/* TO MVM commands */ +#define VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x000110FF +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL 0x00011327 +/* + * VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL + * Description: This command is required to let MVM know + * who is in control of session. + * Payload: Defined by vss_imvm_cmd_set_policy_dual_control_t. + * Result: Wait for APRV2_IBASIC_RSP_RESULT response. + */ + +#define VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110FE +/* Create a new full control MVM session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_ATTACH_STREAM 0x0001123C +/* Attach a stream to the MVM. */ + +#define VSS_IMVM_CMD_DETACH_STREAM 0x0001123D +/* Detach a stream from the MVM. */ + +#define VSS_IMVM_CMD_ATTACH_VOCPROC 0x0001123E +/* Attach a vocproc to the MVM. The MVM will symmetrically connect this vocproc + * to all the streams currently attached to it. + */ + +#define VSS_IMVM_CMD_DETACH_VOCPROC 0x0001123F +/* Detach a vocproc from the MVM. The MVM will symmetrically disconnect this + * vocproc from all the streams to which it is currently attached. +*/ + +#define VSS_IMVM_CMD_START_VOICE 0x00011190 +/* + * Start Voice call command. + * Wait for APRV2_IBASIC_RSP_RESULT response. + * No pay load. + */ + +#define VSS_IMVM_CMD_STANDBY_VOICE 0x00011191 +/* No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_STOP_VOICE 0x00011192 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_ATTACH_VOCPROC 0x000110F8 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_DETACH_VOCPROC 0x000110F9 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + + +#define VSS_ISTREAM_CMD_SET_TTY_MODE 0x00011196 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ICOMMON_CMD_SET_NETWORK 0x0001119C +/* Set the network type. */ + +#define VSS_ICOMMON_CMD_SET_VOICE_TIMING 0x000111E0 +/* Set the voice timing parameters. */ + +#define VSS_IWIDEVOICE_CMD_SET_WIDEVOICE 0x00011243 +/* Enable/disable WideVoice */ + +enum msm_audio_voc_rate { + VOC_0_RATE, /* Blank frame */ + VOC_8_RATE, /* 1/8 rate */ + VOC_4_RATE, /* 1/4 rate */ + VOC_2_RATE, /* 1/2 rate */ + VOC_1_RATE /* Full rate */ +}; + +struct vss_istream_cmd_set_tty_mode_t { + uint32_t mode; + /**< + * TTY mode. + * + * 0 : TTY disabled + * 1 : HCO + * 2 : VCO + * 3 : FULL + */ +} __packed; + +struct vss_istream_cmd_attach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being attached. */ +} __packed; + +struct vss_istream_cmd_detach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being detached. */ +} __packed; + +struct vss_imvm_cmd_attach_stream_t { + uint16_t handle; + /* The stream handle to attach. */ +} __packed; + +struct vss_imvm_cmd_detach_stream_t { + uint16_t handle; + /* The stream handle to detach. */ +} __packed; + +struct vss_icommon_cmd_set_network_t { + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ +} __packed; + +struct vss_icommon_cmd_set_voice_timing_t { + uint16_t mode; + /* + * The vocoder frame synchronization mode. + * + * 0 : No frame sync. + * 1 : Hard VFR (20ms Vocoder Frame Reference interrupt). + */ + uint16_t enc_offset; + /* + * The offset in microseconds from the VFR to deliver a Tx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_req_offset; + /* + * The offset in microseconds from the VFR to request for an Rx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_offset; + /* + * The offset in microseconds from the VFR to indicate the deadline to + * receive an Rx vocoder packet. The offset should be less than 20000us. + * Rx vocoder packets received after this deadline are not guaranteed to + * be processed. + */ +} __packed; + +struct vss_imvm_cmd_create_control_session_t { + char name[SESSION_NAME_LEN]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + + +struct vss_imvm_cmd_set_policy_dual_control_t { + bool enable_flag; + /* Set to TRUE to enable modem state machine control */ +} __packed; + +struct vss_iwidevoice_cmd_set_widevoice_t { + uint32_t enable; + /* WideVoice enable/disable; possible values: + * - 0 -- WideVoice disabled + * - 1 -- WideVoice enabled + */ +} __packed; + +struct mvm_attach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_attach_vocproc_t mvm_attach_cvp_handle; +} __packed; + +struct mvm_detach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_detach_vocproc_t mvm_detach_cvp_handle; +} __packed; + +struct mvm_create_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_create_control_session_t mvm_session; +} __packed; + +struct mvm_modem_dual_control_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_set_policy_dual_control_t voice_ctl; +} __packed; + +struct mvm_set_tty_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_tty_mode_t tty_mode; +} __packed; + +struct mvm_attach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_attach_stream_t attach_stream; +} __packed; + +struct mvm_detach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_detach_stream_t detach_stream; +} __packed; + +struct mvm_set_network_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_network_t network; +} __packed; + +struct mvm_set_voice_timing_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_voice_timing_t timing; +} __packed; + +struct mvm_set_widevoice_enable_cmd { + struct apr_hdr hdr; + struct vss_iwidevoice_cmd_set_widevoice_t vss_set_wv; +} __packed; + +/* TO CVS commands */ +#define VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x00011140 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110F7 +/* Create a new full control stream session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_ISTREAM_CMD_SET_MUTE 0x00011022 + +#define VSS_ISTREAM_CMD_REGISTER_CALIBRATION_DATA 0x00011279 + +#define VSS_ISTREAM_CMD_DEREGISTER_CALIBRATION_DATA 0x0001127A + +#define VSS_ISTREAM_CMD_SET_MEDIA_TYPE 0x00011186 +/* Set media type on the stream. */ + +#define VSS_ISTREAM_EVT_SEND_ENC_BUFFER 0x00011015 +/* Event sent by the stream to its client to provide an encoded packet. */ + +#define VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER 0x00011017 +/* Event sent by the stream to its client requesting for a decoder packet. + * The client should respond with a VSS_ISTREAM_EVT_SEND_DEC_BUFFER event. + */ + +#define VSS_ISTREAM_EVT_SEND_DEC_BUFFER 0x00011016 +/* Event sent by the client to the stream in response to a + * VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER event, providing a decoder packet. + */ + +#define VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE 0x0001113E +/* Set AMR encoder rate. */ + +#define VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE 0x0001113F +/* Set AMR-WB encoder rate. */ + +#define VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE 0x00011019 +/* Set encoder minimum and maximum rate. */ + +#define VSS_ISTREAM_CMD_SET_ENC_DTX_MODE 0x0001101D +/* Set encoder DTX mode. */ + +#define MODULE_ID_VOICE_MODULE_FENS 0x00010EEB +#define MODULE_ID_VOICE_MODULE_ST 0x00010EE3 +#define VOICE_PARAM_MOD_ENABLE 0x00010E00 +#define MOD_ENABLE_PARAM_LEN 4 + +#define VSS_ISTREAM_CMD_START_PLAYBACK 0x00011238 +/* Start in-call music delivery on the Tx voice path. */ + +#define VSS_ISTREAM_CMD_STOP_PLAYBACK 0x00011239 +/* Stop the in-call music delivery on the Tx voice path. */ + +#define VSS_ISTREAM_CMD_START_RECORD 0x00011236 +/* Start in-call conversation recording. */ +#define VSS_ISTREAM_CMD_STOP_RECORD 0x00011237 +/* Stop in-call conversation recording. */ + +#define VSS_TAP_POINT_NONE 0x00010F78 +/* Indicates no tapping for specified path. */ + +#define VSS_TAP_POINT_STREAM_END 0x00010F79 +/* Indicates that specified path should be tapped at the end of the stream. */ + +struct vss_istream_cmd_start_record_t { + uint32_t rx_tap_point; + /* Tap point to use on the Rx path. Supported values are: + * VSS_TAP_POINT_NONE : Do not record Rx path. + * VSS_TAP_POINT_STREAM_END : Rx tap point is at the end of the stream. + */ + uint32_t tx_tap_point; + /* Tap point to use on the Tx path. Supported values are: + * VSS_TAP_POINT_NONE : Do not record tx path. + * VSS_TAP_POINT_STREAM_END : Tx tap point is at the end of the stream. + */ +} __packed; + +struct vss_istream_cmd_create_passive_control_session_t { + char name[SESSION_NAME_LEN]; + /**< + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + +struct vss_istream_cmd_set_mute_t { + uint16_t direction; + /**< + * 0 : TX only + * 1 : RX only + * 2 : TX and Rx + */ + uint16_t mute_flag; + /**< + * Mute, un-mute. + * + * 0 : Silence disable + * 1 : Silence enable + * 2 : CNG enable. Applicable to TX only. If set on RX behavior + * will be the same as 1 + */ +} __packed; + +struct vss_istream_cmd_create_full_control_session_t { + uint16_t direction; + /* + * Stream direction. + * + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + * 3 : TX and RX loopback + */ + uint32_t enc_media_type; + /* Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t dec_media_type; + /* Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ + char name[SESSION_NAME_LEN]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + +struct vss_istream_cmd_set_media_type_t { + uint32_t rx_media_id; + /* Set the Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t tx_media_id; + /* Set the Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ +} __packed; + +struct vss_istream_evt_send_enc_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data buffer. */ +} __packed; + +struct vss_istream_evt_send_dec_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data. */ +} __packed; + +struct vss_istream_cmd_voc_amr_set_enc_rate_t { + uint32_t mode; + /* Set the AMR encoder rate. + * + * 0x00000000 : 4.75 kbps + * 0x00000001 : 5.15 kbps + * 0x00000002 : 5.90 kbps + * 0x00000003 : 6.70 kbps + * 0x00000004 : 7.40 kbps + * 0x00000005 : 7.95 kbps + * 0x00000006 : 10.2 kbps + * 0x00000007 : 12.2 kbps + */ +} __packed; + +struct vss_istream_cmd_voc_amrwb_set_enc_rate_t { + uint32_t mode; + /* Set the AMR-WB encoder rate. + * + * 0x00000000 : 6.60 kbps + * 0x00000001 : 8.85 kbps + * 0x00000002 : 12.65 kbps + * 0x00000003 : 14.25 kbps + * 0x00000004 : 15.85 kbps + * 0x00000005 : 18.25 kbps + * 0x00000006 : 19.85 kbps + * 0x00000007 : 23.05 kbps + * 0x00000008 : 23.85 kbps + */ +} __packed; + +struct vss_istream_cmd_cdma_set_enc_minmax_rate_t { + uint16_t min_rate; + /* Set the lower bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ + uint16_t max_rate; + /* Set the upper bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ +} __packed; + +struct vss_istream_cmd_set_enc_dtx_mode_t { + uint32_t enable; + /* Toggle DTX on or off. + * + * 0 : Disables DTX + * 1 : Enables DTX + */ +} __packed; + +struct vss_istream_cmd_register_calibration_data_t { + uint32_t phys_addr; + /* Phsical address to be registered with stream. The calibration data + * is stored at this address. + */ + uint32_t mem_size; + /* Size of the calibration data in bytes. */ +}; + +struct vss_icommon_cmd_set_ui_property_enable_t { + uint32_t module_id; + /* Unique ID of the module. */ + uint32_t param_id; + /* Unique ID of the parameter. */ + uint16_t param_size; + /* Size of the parameter in bytes: MOD_ENABLE_PARAM_LEN */ + uint16_t reserved; + /* Reserved; set to 0. */ + uint16_t enable; + uint16_t reserved_field; + /* Reserved, set to 0. */ +}; + +struct cvs_create_passive_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_passive_control_session_t cvs_session; +} __packed; + +struct cvs_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_full_control_session_t cvs_session; +} __packed; + +struct cvs_destroy_session_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvs_set_mute_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_mute_t cvs_set_mute; +} __packed; + +struct cvs_set_media_type_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_media_type_t media_type; +} __packed; + +struct cvs_send_dec_buf_cmd { + struct apr_hdr hdr; + struct vss_istream_evt_send_dec_buffer_t dec_buf; +} __packed; + +struct cvs_set_amr_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amr_set_enc_rate_t amr_rate; +} __packed; + +struct cvs_set_amrwb_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amrwb_set_enc_rate_t amrwb_rate; +} __packed; + +struct cvs_set_cdma_enc_minmax_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_cdma_set_enc_minmax_rate_t cdma_rate; +} __packed; + +struct cvs_set_enc_dtx_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_enc_dtx_mode_t dtx_mode; +} __packed; + +struct cvs_register_cal_data_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_register_calibration_data_t cvs_cal_data; +} __packed; + +struct cvs_deregister_cal_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvs_set_pp_enable_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_ui_property_enable_t vss_set_pp; +} __packed; +struct cvs_start_record_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_start_record_t rec_mode; +} __packed; + +/* TO CVP commands */ + +#define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION 0x000100C3 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_IVOCPROC_CMD_SET_DEVICE 0x000100C4 + +#define VSS_IVOCPROC_CMD_SET_VP3_DATA 0x000110EB + +#define VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX 0x000110EE + +#define VSS_IVOCPROC_CMD_ENABLE 0x000100C6 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_CMD_DISABLE 0x000110E1 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_CMD_REGISTER_CALIBRATION_DATA 0x00011275 +#define VSS_IVOCPROC_CMD_DEREGISTER_CALIBRATION_DATA 0x00011276 + +#define VSS_IVOCPROC_CMD_REGISTER_VOLUME_CAL_TABLE 0x00011277 +#define VSS_IVOCPROC_CMD_DEREGISTER_VOLUME_CAL_TABLE 0x00011278 + +#define VSS_IVOCPROC_TOPOLOGY_ID_NONE 0x00010F70 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE 0x00010F72 + +#define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 + +/* Newtwork IDs */ +#define VSS_NETWORK_ID_DEFAULT 0x00010037 +#define VSS_NETWORK_ID_VOIP_NB 0x00011240 +#define VSS_NETWORK_ID_VOIP_WB 0x00011241 +#define VSS_NETWORK_ID_VOIP_WV 0x00011242 + +/* Media types */ +#define VSS_MEDIA_ID_EVRC_MODEM 0x00010FC2 +/* 80-VF690-47 CDMA enhanced variable rate vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_NB_MODEM 0x00010FC6 +/* 80-VF690-47 UMTS AMR-NB vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_WB_MODEM 0x00010FC7 +/* 80-VF690-47 UMTS AMR-WB vocoder modem format. */ +#define VSS_MEDIA_ID_PCM_NB 0x00010FCB +#define VSS_MEDIA_ID_PCM_WB 0x00010FCC +/* Linear PCM (16-bit, little-endian). */ +#define VSS_MEDIA_ID_G711_ALAW 0x00010FCD +/* G.711 a-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G711_MULAW 0x00010FCE +/* G.711 mu-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G729 0x00010FD0 +/* G.729AB (contains two 10ms vocoder frames. */ +#define VSS_MEDIA_ID_4GV_NB_MODEM 0x00010FC3 +/*CDMA EVRC-B vocoder modem format */ +#define VSS_MEDIA_ID_4GV_WB_MODEM 0x00010FC4 +/*CDMA EVRC-WB vocoder modem format */ + +#define VSS_IVOCPROC_CMD_SET_MUTE 0x000110EF + +#define VOICE_CMD_SET_PARAM 0x00011006 +#define VOICE_CMD_GET_PARAM 0x00011007 +#define VOICE_EVT_GET_PARAM_ACK 0x00011008 + +struct vss_ivocproc_cmd_create_full_control_session_t { + uint16_t direction; + /* + * stream direction. + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + */ + uint32_t tx_port_id; + /* + * TX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t tx_topology_id; + /* + * Tx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + uint32_t rx_port_id; + /* + * RX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t rx_topology_id; + /* + * Rx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + int32_t network_id; + /* + * Network ID. (Refer to VSS_NETWORK_ID_XXX). If not supplying a network + * ID set to VSS_NETWORK_ID_DEFAULT. + */ +} __packed; + +struct vss_ivocproc_cmd_set_volume_index_t { + uint16_t vol_index; + /**< + * Volume index utilized by the vocproc to index into the volume table + * provided in VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE and set + * volume on the VDSP. + */ +} __packed; + +struct vss_ivocproc_cmd_set_device_t { + uint32_t tx_port_id; + /**< + * TX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t tx_topology_id; + /**< + * TX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ + int32_t rx_port_id; + /**< + * RX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t rx_topology_id; + /**< + * RX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ +} __packed; + +struct vss_ivocproc_cmd_register_calibration_data_t { + uint32_t phys_addr; + /* Phsical address to be registered with vocproc. Calibration data + * is stored at this address. + */ + uint32_t mem_size; + /* Size of the calibration data in bytes. */ +} __packed; + +struct vss_ivocproc_cmd_register_volume_cal_table_t { + uint32_t phys_addr; + /* Phsical address to be registered with the vocproc. The volume + * calibration table is stored at this location. + */ + + uint32_t mem_size; + /* Size of the volume calibration table in bytes. */ +} __packed; + +struct vss_ivocproc_cmd_set_mute_t { + uint16_t direction; + /* + * 0 : TX only. + * 1 : RX only. + * 2 : TX and Rx. + */ + uint16_t mute_flag; + /* + * Mute, un-mute. + * + * 0 : Disable. + * 1 : Enable. + */ +} __packed; + +struct cvp_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_create_full_control_session_t cvp_session; +} __packed; + +struct cvp_command { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_device_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_device_t cvp_set_device; +} __packed; + +struct cvp_set_vp3_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_rx_volume_index_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_volume_index_t cvp_set_vol_idx; +} __packed; + +struct cvp_register_cal_data_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_register_calibration_data_t cvp_cal_data; +} __packed; + +struct cvp_deregister_cal_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_register_vol_cal_table_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_register_volume_cal_table_t cvp_vol_cal_tbl; +} __packed; + +struct cvp_deregister_vol_cal_table_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_mute_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_mute_t cvp_set_mute; +} __packed; + +/* CB for up-link packets. */ +typedef void (*ul_cb_fn)(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data); + +/* CB for down-link packets. */ +typedef void (*dl_cb_fn)(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data); + + +struct mvs_driver_info { + uint32_t media_type; + uint32_t rate; + uint32_t network_type; + uint32_t dtx_mode; + ul_cb_fn ul_cb; + dl_cb_fn dl_cb; + void *private_data; +}; + +struct incall_rec_info { + uint32_t rec_enable; + uint32_t rec_mode; + uint32_t recording; +}; + +struct incall_music_info { + uint32_t play_enable; + uint32_t playing; + int count; + int force; +}; + +struct voice_data { + int voc_state;/*INIT, CHANGE, RELEASE, RUN */ + + wait_queue_head_t mvm_wait; + wait_queue_head_t cvs_wait; + wait_queue_head_t cvp_wait; + + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + + u32 mvm_state; + u32 cvs_state; + u32 cvp_state; + + /* Handle to MVM in the Q6 */ + u16 mvm_handle; + /* Handle to CVS in the Q6 */ + u16 cvs_handle; + /* Handle to CVP in the Q6 */ + u16 cvp_handle; + + struct mutex lock; + + uint16_t sidetone_gain; + uint8_t tty_mode; + /* widevoice enable value */ + uint8_t wv_enable; + /* slowtalk enable value */ + uint32_t st_enable; + /* FENC enable value */ + uint32_t fens_enable; + + struct voice_dev_route_state voc_route_state; + + u16 session_id; + + struct incall_rec_info rec_info; + + struct incall_music_info music_info; + + struct voice_rec_route_state rec_route_state; +}; + +struct cal_mem { + struct ion_handle *handle; + uint32_t phy; + void *buf; +}; + +#define MAX_VOC_SESSIONS 3 +#define SESSION_ID_BASE 0xFFF0 + +struct common_data { + /* these default values are for all devices */ + uint32_t default_mute_val; + uint32_t default_vol_val; + uint32_t default_sample_val; + + /* APR to MVM in the Q6 */ + void *apr_q6_mvm; + /* APR to CVS in the Q6 */ + void *apr_q6_cvs; + /* APR to CVP in the Q6 */ + void *apr_q6_cvp; + + struct ion_client *client; + struct cal_mem cvp_cal; + struct cal_mem cvs_cal; + + struct mutex common_lock; + + struct mvs_driver_info mvs_info; + + struct voice_data voice[MAX_VOC_SESSIONS]; +}; + +void voc_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data); + +void voc_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type, + uint32_t dtx_mode); + +enum { + DEV_RX = 0, + DEV_TX, +}; + +enum { + RX_PATH = 0, + TX_PATH, +}; + +/* called by alsa driver */ +int voc_set_pp_enable(uint16_t session_id, uint32_t module_id, uint32_t enable); +int voc_get_pp_enable(uint16_t session_id, uint32_t module_id); +int voc_set_widevoice_enable(uint16_t session_id, uint32_t wv_enable); +uint32_t voc_get_widevoice_enable(uint16_t session_id); +uint8_t voc_get_tty_mode(uint16_t session_id); +int voc_set_tty_mode(uint16_t session_id, uint8_t tty_mode); +int voc_start_voice_call(uint16_t session_id); +int voc_standby_voice_call(uint16_t session_id); +int voc_resume_voice_call(uint16_t session_id); +int voc_end_voice_call(uint16_t session_id); +int voc_set_rxtx_port(uint16_t session_id, + uint32_t dev_port_id, + uint32_t dev_type); +int voc_set_rx_vol_index(uint16_t session_id, uint32_t dir, uint32_t voc_idx); +int voc_set_tx_mute(uint16_t session_id, uint32_t dir, uint32_t mute); +int voc_set_rx_device_mute(uint16_t session_id, uint32_t mute); +int voc_get_rx_device_mute(uint16_t session_id); +int voc_disable_cvp(uint16_t session_id); +int voc_enable_cvp(uint16_t session_id); +int voc_set_route_flag(uint16_t session_id, uint8_t path_dir, uint8_t set); +uint8_t voc_get_route_flag(uint16_t session_id, uint8_t path_dir); + +#define VOICE_SESSION_NAME "Voice session" +#define VOIP_SESSION_NAME "VoIP session" +#define VOLTE_SESSION_NAME "VoLTE session" +uint16_t voc_get_session_id(char *name); + +int voc_start_playback(uint32_t set); +int voc_start_record(uint32_t port_id, uint32_t set); +#endif diff --git a/sound/soc/msm/qdsp6v2/Makefile b/sound/soc/msm/qdsp6v2/Makefile new file mode 100644 index 000000000000..6f765d1ce205 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/Makefile @@ -0,0 +1,4 @@ +snd-soc-qdsp6v2-objs += msm-dai-q6-v2.o msm-pcm-q6-v2.o msm-pcm-routing-v2.o msm-compr-q6-v2.o msm-multi-ch-pcm-q6-v2.o +snd-soc-qdsp6v2-objs += msm-pcm-lpa-v2.o msm-pcm-afe-v2.o +obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o +obj-y += q6adm.o q6afe.o q6asm.o q6audio-v2.o diff --git a/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c new file mode 100644 index 000000000000..9871968a47cb --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.c @@ -0,0 +1,666 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-compr-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm compressed_audio = {NULL, 0x2000} ; + +static struct audio_locks the_locks; + +static struct snd_pcm_hardware msm_compr_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 1200 * 1024 * 2, + .period_bytes_min = 4800, + .period_bytes_max = 1200 * 1024, + .periods_min = 2, + .periods_max = 512, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void compr_event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct compr_audio *compr = priv; + struct msm_audio *prtd = &compr->prtd; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + int i = 0; + + pr_debug("%s opcode =%08x\n", __func__, opcode); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing %d bytes of buffer[%d] to dsp 2\n", + __func__, prtd->pcm_count, prtd->out_head); + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + + param.paddr = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + for (i = 0; i < sizeof(struct audio_aio_write_param)/4; + i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) & (runtime->periods - 1); + break; + } + case ASM_DATA_EVENT_RENDERED_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: { + if (!atomic_read(&prtd->pending_buffer)) + break; + pr_debug("%s:writing %d bytes" + " of buffer[%d] to dsp\n", + __func__, prtd->pcm_count, prtd->out_head); + buf = prtd->audio_client->port[IN].buf; + pr_debug("%s:writing buffer[%d] from 0x%08x\n", + __func__, prtd->out_head, + ((unsigned int)buf[0].phys + + (prtd->out_head * prtd->pcm_count))); + param.paddr = (unsigned long)buf[prtd->out_head].phys; + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[prtd->out_head].phys; + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) + & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_compr_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct asm_aac_cfg aac_cfg; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + prtd->out_head = 0; + atomic_set(&prtd->out_count, runtime->periods); + + if (prtd->enabled) + return 0; + + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* No media format block for mp3 */ + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + memset(&aac_cfg, 0x0, sizeof(struct asm_aac_cfg)); + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + aac_cfg.format = 0x03; + aac_cfg.ch_cfg = runtime->channels; + aac_cfg.sample_rate = runtime->rate; + ret = q6asm_media_format_block_aac(prtd->audio_client, + &aac_cfg); + if (ret < 0) + pr_err("%s: CMD Format block failed\n", __func__); + break; + default: + return -EINVAL; + } + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_compr_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void populate_codec_list(struct compr_audio *compr, + struct snd_pcm_runtime *runtime) +{ + pr_debug("%s\n", __func__); + /* MP3 Block */ + compr->info.compr_cap.num_codecs = 1; + compr->info.compr_cap.min_fragment_size = runtime->hw.period_bytes_min; + compr->info.compr_cap.max_fragment_size = runtime->hw.period_bytes_max; + compr->info.compr_cap.min_fragments = runtime->hw.periods_min; + compr->info.compr_cap.max_fragments = runtime->hw.periods_max; + compr->info.compr_cap.codecs[0] = SND_AUDIOCODEC_MP3; + compr->info.compr_cap.codecs[1] = SND_AUDIOCODEC_AAC; + /* Add new codecs here */ +} + +static int msm_compr_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + pr_debug("%s\n", __func__); + compr = kzalloc(sizeof(struct compr_audio), GFP_KERNEL); + if (compr == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd = &compr->prtd; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)compr_event_handler, compr); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + runtime->hw = msm_compr_hardware_playback; + + pr_info("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + atomic_set(&prtd->pending_buffer, 1); + compr->codec = FORMAT_MP3; + populate_codec_list(compr, runtime); + runtime->private_data = compr; + compressed_audio.prtd = &compr->prtd; + ret = compressed_set_volume(compressed_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(compressed_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(compressed_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int compressed_set_volume(unsigned volume) +{ + int rc = 0; + if (compressed_audio.prtd && compressed_audio.prtd->audio_client) { + rc = q6asm_set_volume(compressed_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + compressed_audio.volume = volume; + return rc; +} + +static int msm_compr_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + int dir = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + compressed_audio.prtd = NULL; + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_compr_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} +static int msm_compr_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_compr_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = EINVAL; + return ret; +} + +static snd_pcm_uframes_t msm_compr_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_compr_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_compr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EINVAL; + + ret = q6asm_open_write(prtd->audio_client, compr->codec); + if (ret < 0) { + pr_err("%s: Session out open failed\n", __func__); + return -ENOMEM; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + return -ENOMEM; + } + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed " + "rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int msm_compr_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct compr_audio *compr = runtime->private_data; + struct msm_audio *prtd = &compr->prtd; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.rendered = (size_t)(temp & 0xFFFFFFFF); + tstamp.decoded = (size_t)((temp >> 32) & 0xFFFFFFFF); + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:lsb = %d, msb = %d," + "timestamp = %lld,\n", + __func__, tstamp.rendered, tstamp.decoded, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_COMPRESS_GET_CAPS: + pr_debug("SNDRV_COMPRESS_GET_CAPS\n"); + if (copy_to_user((void *) arg, &compr->info.compr_cap, + sizeof(struct snd_compr_caps))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy to user\n", __func__); + return rc; + } + return 0; + case SNDRV_COMPRESS_SET_PARAMS: + pr_debug("SNDRV_COMPRESS_SET_PARAMS: "); + if (copy_from_user(&compr->info.codec_param, (void *) arg, + sizeof(struct snd_compr_params))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy from user\n", __func__); + return rc; + } + switch (compr->info.codec_param.codec.id) { + case SND_AUDIOCODEC_MP3: + /* For MP3 we dont need any other parameter */ + pr_debug("SND_AUDIOCODEC_MP3\n"); + compr->codec = FORMAT_MP3; + break; + case SND_AUDIOCODEC_AAC: + pr_debug("SND_AUDIOCODEC_AAC\n"); + compr->codec = FORMAT_MPEG4_AAC; + break; + default: + pr_debug("FORMAT_LINEAR_PCM\n"); + compr->codec = FORMAT_LINEAR_PCM; + break; + } + return 0; + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_compr_ops = { + .open = msm_compr_open, + .hw_params = msm_compr_hw_params, + .close = msm_compr_close, + .ioctl = msm_compr_ioctl, + .prepare = msm_compr_prepare, + .trigger = msm_compr_trigger, + .pointer = msm_compr_pointer, + .mmap = msm_compr_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_compr_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_compr_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_compr_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_compr_driver = { + .driver = { + .name = "msm-compr-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_compr_probe, + .remove = msm_compr_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_compr_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_compr_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h new file mode 100644 index 000000000000..a046b3b4b259 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-compr-q6-v2.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MSM_COMPR_H +#define _MSM_COMPR_H +#include +#include +#include +#include +#include + +#include "msm-pcm-q6-v2.h" + +struct compr_info { + struct snd_compr_caps compr_cap; + struct snd_compr_codec_caps codec_caps; + struct snd_compr_params codec_param; +}; + +struct compr_audio { + struct msm_audio prtd; + struct compr_info info; + uint32_t codec; +}; + +#endif /*_MSM_COMPR_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c new file mode 100644 index 000000000000..f30aeeb80283 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-dai-q6-v2.c @@ -0,0 +1,1229 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + STATUS_PORT_STARTED, /* track if AFE port has started */ + STATUS_MAX +}; + +struct msm_dai_q6_dai_data { + DECLARE_BITMAP(status_mask, STATUS_MAX); + u32 rate; + u32 channels; + union afe_port_config port_config; +}; + +static struct clk *pcm_clk; +static DEFINE_MUTEX(aux_pcm_mutex); +static int aux_pcm_count; +static struct msm_dai_auxpcm_pdata *auxpcm_plat_data; + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + num_bits_set++; + sd_line_mask = sd_line_mask & (sd_line_mask - 1); + } + return num_bits_set; +} + +static int msm_dai_q6_cdc_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_MONO; + break; + default: + return -EINVAL; + break; + } + dai_data->rate = params_rate(params); + dai_data->port_config.i2s.sample_rate = dai_data->rate; + dai_data->port_config.i2s.i2s_cfg_minor_version = + AFE_API_VERSION_I2S_CONFIG; + dai_data->port_config.i2s.data_format = AFE_LINEAR_PCM_DATA; + dev_dbg(dai->dev, " channel %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + /* Q6 only supports 16 as now */ + dai_data->port_config.i2s.bit_width = 16; + dai_data->port_config.i2s.channel_mode = 1; + return 0; +} + +static int msm_dai_q6_i2s_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_i2s_data *i2s_pdata = + (struct msm_i2s_data *) dai->dev->platform_data; + + dai_data->channels = params_channels(params); + if (num_of_bits_set(i2s_pdata->sd_lines) == 1) { + switch (dai_data->channels) { + case 2: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.i2s.mono_stereo = MSM_AFE_MONO; + break; + default: + pr_warn("greater than stereo has not been validated"); + break; + } + } + dai_data->rate = params_rate(params); + dai_data->port_config.i2s.sample_rate = dai_data->rate; + dai_data->port_config.i2s.i2s_cfg_minor_version = + AFE_API_VERSION_I2S_CONFIG; + dai_data->port_config.i2s.data_format = AFE_LINEAR_PCM_DATA; + /* Q6 only supports 16 as now */ + dai_data->port_config.i2s.bit_width = 16; + dai_data->port_config.i2s.channel_mode = 1; + + return 0; +} + +static int msm_dai_q6_i2s_platform_data_validation( + struct snd_soc_dai *dai) +{ + u8 num_of_sd_lines; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_i2s_data *i2s_pdata = + (struct msm_i2s_data *)dai->dev->platform_data; + struct snd_soc_dai_driver *dai_driver = + (struct snd_soc_dai_driver *)dai->driver; + + num_of_sd_lines = num_of_bits_set(i2s_pdata->sd_lines); + + switch (num_of_sd_lines) { + case 1: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD0; + break; + case MSM_MI2S_SD1: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD1; + break; + case MSM_MI2S_SD2: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD2; + break; + case MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_SD3; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 2: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_QUAD01; + break; + case MSM_MI2S_SD2 | MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_QUAD23; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + break; + case 3: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_6CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + case 4: + switch (i2s_pdata->sd_lines) { + case MSM_MI2S_SD0 | MSM_MI2S_SD1 | MSM_MI2S_SD2 | MSM_MI2S_SD3: + dai_data->port_config.i2s.channel_mode = + AFE_PORT_I2S_8CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + break; + default: + pr_err("%s: invalid SD lines\n", __func__); + goto error_invalid_data; + } + if (i2s_pdata->capability == MSM_MI2S_CAP_RX) + dai_driver->playback.channels_max = num_of_sd_lines << 1; + + return 0; + +error_invalid_data: + return -EINVAL; +} + +static int msm_dai_q6_cdc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_data->port_config.i2s.ws_src = 1; /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_data->port_config.i2s.ws_src = 0; /* CPU is slave */ + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int msm_dai_q6_slim_bus_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + /* Q6 only supports 16 as now */ + dai_data->port_config.slim_sch.sb_cfg_minor_version = + AFE_API_VERSION_SLIMBUS_CONFIG; + dai_data->port_config.slim_sch.bit_width = 16; + dai_data->port_config.slim_sch.data_format = 0; + dai_data->port_config.slim_sch.num_channels = dai_data->channels; + dai_data->port_config.slim_sch.sample_rate = dai_data->rate; + + dev_dbg(dai->dev, "%s:slimbus_dev_id[%hu] bit_wd[%hu] format[%hu]\n" + "num_channel %hu shared_ch_mapping[0] %hu\n" + "slave_port_mapping[1] %hu slave_port_mapping[2] %hu\n" + "sample_rate %d\n", __func__, + dai_data->port_config.slim_sch.slimbus_dev_id, + dai_data->port_config.slim_sch.bit_width, + dai_data->port_config.slim_sch.data_format, + dai_data->port_config.slim_sch.num_channels, + dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1], + dai_data->port_config.slim_sch.shared_ch_mapping[2], + dai_data->rate); + + return 0; +} + +static int msm_dai_q6_bt_fm_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + dev_dbg(dai->dev, "channels %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + memset(&dai_data->port_config, 0, sizeof(dai_data->port_config)); + + return 0; +} +static int msm_dai_q6_auxpcm_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + if (params_channels(params) != 1) { + dev_err(dai->dev, "AUX PCM supports only mono stream\n"); + return -EINVAL; + } + dai_data->channels = params_channels(params); + + if (params_rate(params) != 8000) { + dev_err(dai->dev, "AUX PCM supports only 8KHz sampling rate\n"); + return -EINVAL; + } + dai_data->rate = params_rate(params); + + dai_data->port_config.pcm.pcm_cfg_minor_version = + AFE_API_VERSION_PCM_CONFIG; + dai_data->port_config.pcm.aux_mode = auxpcm_pdata->mode; + dai_data->port_config.pcm.sync_src = auxpcm_pdata->sync; + dai_data->port_config.pcm.frame_setting = auxpcm_pdata->frame; + dai_data->port_config.pcm.quantype = auxpcm_pdata->quant; + dai_data->port_config.pcm.ctrl_data_out_enable = auxpcm_pdata->data; + dai_data->port_config.pcm.sample_rate = dai_data->rate; + dai_data->port_config.pcm.num_channels = dai_data->channels; + dai_data->port_config.pcm.bit_width = 16; + dai_data->port_config.pcm.slot_number_mapping[0] = auxpcm_pdata->slot; + + return 0; +} + +static int msm_dai_q6_afe_rtproxy_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->rate = params_rate(params); + dai_data->port_config.rtproxy.num_channels = params_channels(params); + dai_data->port_config.rtproxy.sample_rate = params_rate(params); + + pr_debug("channel %d entered,dai_id: %d,rate: %d\n", + dai_data->port_config.rtproxy.num_channels, dai->id, dai_data->rate); + + dai_data->port_config.rtproxy.rt_proxy_cfg_minor_version = + AFE_API_VERSION_RT_PROXY_CONFIG; + dai_data->port_config.rtproxy.bit_width = 16; /* Q6 only supports 16 */ + dai_data->port_config.rtproxy.interleaved = 1; + dai_data->port_config.rtproxy.frame_size = params_period_bytes(params); + dai_data->port_config.rtproxy.jitter_allowance = + dai_data->port_config.rtproxy.frame_size/2; + dai_data->port_config.rtproxy.low_water_mark = 0; + dai_data->port_config.rtproxy.high_water_mark = 0; + + return 0; +} + +/* Current implementation assumes hw_param is called once + * This may not be the case but what to do when ADM and AFE + * port are already opened and parameter changes + */ +static int msm_dai_q6_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int rc = 0; + + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream); + break; + case MI2S_RX: + rc = msm_dai_q6_i2s_hw_params(params, dai, substream->stream); + break; + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + rc = msm_dai_q6_slim_bus_hw_params(params, dai, + substream->stream); + break; + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_FM_RX: + case INT_FM_TX: + rc = msm_dai_q6_bt_fm_hw_params(params, dai, substream->stream); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_TX: + case RT_PROXY_DAI_002_RX: + rc = msm_dai_q6_afe_rtproxy_hw_params(params, dai); + break; + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = 0; + break; + default: + dev_err(dai->dev, "invalid AFE port ID\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static void msm_dai_q6_auxpcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int rc = 0; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. Just" + " return\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + aux_pcm_count = 0; + mutex_unlock(&aux_pcm_mutex); + return; + } + + pr_debug("%s: dai->id = %d aux_pcm_count = %d\n", __func__, + dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close PCM_RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX port\n"); + + mutex_unlock(&aux_pcm_mutex); +} + +static void msm_dai_q6_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + break; + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + pr_debug("%s: dai_data->status_mask = %ld\n", __func__, + *dai_data->status_mask); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } +} + +static int msm_dai_q6_auxpcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 2. Just" + " return.\n", __func__, dai->id); + mutex_unlock(&aux_pcm_mutex); + return 0; + } else if (aux_pcm_count > 2) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d > 2\n", + __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + aux_pcm_count++; + if (aux_pcm_count == 2) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d after " + " increment\n", __func__, dai->id, aux_pcm_count); + mutex_unlock(&aux_pcm_mutex); + return 0; + } + + pr_debug("%s:dai->id:%d aux_pcm_count = %d. opening afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + + /* + * For AUX PCM Interface the below sequence of clk + * settings and afe_open is a strict requirement. + * + * Also using afe_open instead of afe_port_start_nowait + * to make sure the port is open before deasserting the + * clock line. This is required because pcm register is + * not written before clock deassert. Hence the hw does + * not get updated with new setting if the below clock + * assert/deasset and afe_open sequence is not followed. + */ + + afe_open(PCM_RX, &dai_data->port_config, dai_data->rate); + + afe_open(PCM_TX, &dai_data->port_config, dai_data->rate); + + mutex_unlock(&aux_pcm_mutex); + + return rc; +} + +static int msm_dai_q6_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* PORT START should be set if prepare called in active state */ + rc = afe_q6_interface_prepare(); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open AFE APR\n"); + } + return rc; +} + +static int msm_dai_q6_auxpcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int rc = 0; + + pr_debug("%s:port:%d cmd:%d aux_pcm_count= %d", + __func__, dai->id, cmd, aux_pcm_count); + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* afe_open will be called from prepare */ + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return 0; + + default: + rc = -EINVAL; + } + + return rc; + +} + +static int msm_dai_q6_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + /* Start/stop port without waiting for Q6 AFE response. Need to have + * native q6 AFE driver propagates AFE response in order to handle + * port start/stop command error properly if error does arise. + */ + pr_debug("%s:port:%d cmd:%d dai_data->status_mask = %ld", + __func__, dai->id, cmd, *dai_data->status_mask); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_start_nowait(dai->id); + break; + default: + afe_port_start_nowait(dai->id, + &dai_data->port_config, dai_data->rate); + break; + } + set_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + afe_pseudo_port_stop_nowait(dai->id); + break; + default: + afe_port_stop_nowait(dai->id); + break; + } + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} +static int msm_dai_q6_dai_auxpcm_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + struct msm_dai_auxpcm_pdata *auxpcm_pdata = + (struct msm_dai_auxpcm_pdata *) dai->dev->platform_data; + + mutex_lock(&aux_pcm_mutex); + + if (!auxpcm_plat_data) + auxpcm_plat_data = auxpcm_pdata; + else if (auxpcm_plat_data != auxpcm_pdata) { + + dev_err(dai->dev, "AUX PCM RX and TX devices does not have" + " same platform data\n"); + return -EINVAL; + } + + /* + * The clk name for AUX PCM operation is passed as platform + * data to the cpu driver, since cpu drive is unaware of any + * boarc specific configuration. + */ + if (!pcm_clk) + pcm_clk = clk_get(dai->dev, auxpcm_pdata->clk); + + mutex_unlock(&aux_pcm_mutex); + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + pr_err("%s : probe done for dai->id %d\n", __func__, dai->id); + return rc; +} + +static int msm_dai_q6_dai_auxpcm_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + mutex_lock(&aux_pcm_mutex); + + if (aux_pcm_count == 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count is 0. clean" + " up and return\n", __func__, dai->id); + goto done; + } + + aux_pcm_count--; + + if (aux_pcm_count > 0) { + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d\n", + __func__, dai->id, aux_pcm_count); + goto done; + } else if (aux_pcm_count < 0) { + dev_err(dai->dev, "%s(): ERROR: dai->id %d" + " aux_pcm_count = %d < 0\n", + __func__, dai->id, aux_pcm_count); + goto done; + } + + dev_dbg(dai->dev, "%s(): dai->id %d aux_pcm_count = %d." + "closing afe\n", + __func__, dai->id, aux_pcm_count); + + rc = afe_close(PCM_RX); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM RX AFE port\n"); + + rc = afe_close(PCM_TX); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AUX PCM TX AFE port\n"); + +done: + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + mutex_unlock(&aux_pcm_mutex); + + return 0; +} +static int msm_dai_q6_dai_i2s_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + goto rtn; + } else + dev_set_drvdata(dai->dev, dai_data); + + rc = msm_dai_q6_i2s_platform_data_validation(dai); + if (rc != 0) { + pr_err("%s: The msm_dai_q6_i2s_platform_data_validation failed\n", + __func__); + kfree(dai_data); + } +rtn: + return rc; +} + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + return rc; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + switch (dai->id) { + case VOICE_PLAYBACK_TX: + case VOICE_RECORD_TX: + case VOICE_RECORD_RX: + pr_debug("%s, stop pseudo port:%d\n", + __func__, dai->id); + rc = afe_stop_pseudo_port(dai->id); + break; + default: + rc = afe_close(dai->id); /* can block */ + } + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static int msm_dai_q6_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + int rc = 0; + + dev_dbg(dai->dev, "enter %s, id = %d fmt[%d]\n", __func__, + dai->id, fmt); + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + case MI2S_RX: + case SECONDARY_I2S_RX: + rc = msm_dai_q6_cdc_set_fmt(dai, fmt); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_dai_q6_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) + +{ + int rc = 0; + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + unsigned int i = 0; + + dev_dbg(dai->dev, "enter %s, id = %d\n", __func__, + dai->id); + switch (dai->id) { + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!rx_slot) + return -EINVAL; + for (i = 0; i < rx_num; i++) { + dai_data->port_config.slim_sch.shared_ch_mapping[i] = + rx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, + rx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = rx_num; + pr_debug("%s:SLIMBUS_0_RX cnt[%d] ch[%d %d]\n", __func__, + rx_num, dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1]); + + break; + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + /* channel number to be between 128 and 255. For RX port + * use channel numbers from 138 to 144, for TX port + * use channel numbers from 128 to 137 + * For ports between MDM-APQ use channel numbers from 145 + */ + if (!tx_slot) + return -EINVAL; + for (i = 0; i < tx_num; i++) { + dai_data->port_config.slim_sch.shared_ch_mapping[i] = + tx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, tx_slot[i]); + } + dai_data->port_config.slim_sch.num_channels = tx_num; + pr_debug("%s:SLIMBUS_0_TX cnt[%d] ch[%d %d]\n", __func__, + tx_num, dai_data->port_config.slim_sch.shared_ch_mapping[0], + dai_data->port_config.slim_sch.shared_ch_mapping[1]); + break; + default: + dev_err(dai->dev, "invalid cpu_dai set_fmt\n"); + rc = -EINVAL; + break; + } + return rc; +} + +static struct snd_soc_dai_ops msm_dai_q6_ops = { + .prepare = msm_dai_q6_prepare, + .trigger = msm_dai_q6_trigger, + .hw_params = msm_dai_q6_hw_params, + .shutdown = msm_dai_q6_shutdown, + .set_fmt = msm_dai_q6_set_fmt, + .set_channel_map = msm_dai_q6_set_channel_map, +}; + +static struct snd_soc_dai_ops msm_dai_q6_auxpcm_ops = { + .prepare = msm_dai_q6_auxpcm_prepare, + .trigger = msm_dai_q6_auxpcm_trigger, + .hw_params = msm_dai_q6_auxpcm_hw_params, + .shutdown = msm_dai_q6_auxpcm_shutdown, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_i2s_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_afe_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_afe_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_voice_playback_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_incall_record_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 8000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_aux_pcm_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 8000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_auxpcm_ops, + .probe = msm_dai_q6_dai_auxpcm_probe, + .remove = msm_dai_q6_dai_auxpcm_remove, +}; + + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_1_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_min = 8000, + .rate_max = 16000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static int msm_dai_q6_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + + switch (pdev->id) { + case PRIMARY_I2S_RX: + case SECONDARY_I2S_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_rx_dai); + break; + case PRIMARY_I2S_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_tx_dai); + break; + case AFE_PORT_ID_PRIMARY_PCM_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_rx_dai); + break; + case AFE_PORT_ID_PRIMARY_PCM_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_aux_pcm_tx_dai); + break; + case MI2S_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_i2s_rx_dai); + break; + case SLIMBUS_0_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_rx_dai); + break; + case SLIMBUS_0_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_tx_dai); + break; + + case SLIMBUS_1_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_rx_dai); + break; + case SLIMBUS_1_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_1_tx_dai); + break; + case INT_BT_SCO_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_rx_dai); + break; + case INT_BT_SCO_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_tx_dai); + break; + case INT_FM_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_rx_dai); + break; + case INT_FM_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_tx_dai); + break; + case RT_PROXY_DAI_001_RX: + case RT_PROXY_DAI_002_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_rx_dai); + break; + case RT_PROXY_DAI_001_TX: + case RT_PROXY_DAI_002_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_afe_tx_dai); + break; + case VOICE_PLAYBACK_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_voice_playback_tx_dai); + break; + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_incall_record_dai); + break; + default: + rc = -ENODEV; + break; + } + return rc; +} + +static int msm_dai_q6_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_dai_q6_driver = { + .probe = msm_dai_q6_dev_probe, + .remove = msm_dai_q6_dev_remove, + .driver = { + .name = "msm-dai-q6", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_init(void) +{ + return platform_driver_register(&msm_dai_q6_driver); +} +module_init(msm_dai_q6_init); + +static void __exit msm_dai_q6_exit(void) +{ + platform_driver_unregister(&msm_dai_q6_driver); +} +module_exit(msm_dai_q6_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM DSP DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c new file mode 100644 index 000000000000..824521546dac --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-multi-ch-pcm-q6-v2.c @@ -0,0 +1,777 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +struct snd_msm_volume { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm_volume multi_ch_pcm_audio = {NULL, 0x2000}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 4032 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 320 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 6, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static uint32_t in_frame_info[CAPTURE_NUM_PERIODS][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + uint32_t idx = 0; + uint32_t size = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + if (q6asm_is_cpu_buf_avail_nolock(IN, + prtd->audio_client, + &size, &idx)) { + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + } + break; + } + case ASM_DATA_EVENT_RENDERED_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE_V2: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag + && q6asm_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + break; + } + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + atomic_set(&prtd->start, 1); + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, + runtime->rate, runtime->channels); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + prtd->periods = runtime->periods; + + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_err("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, + FORMAT_MULTI_CHANNEL_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_hardware_capture; + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + pr_debug("substream->pcm->device = %d\n", substream->pcm->device); + pr_debug("soc_prtd->dai_link->be_id = %d\n", soc_prtd->dai_link->be_id); + multi_ch_pcm_audio.prtd = prtd; + ret = multi_ch_pcm_set_volume(multi_ch_pcm_audio.volume); + if (ret < 0) + pr_err("%s : Set Volume failed : %d", __func__, ret); + + ret = q6asm_set_softpause(multi_ch_pcm_audio.prtd->audio_client, + &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(multi_ch_pcm_audio.prtd->audio_client, + &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int multi_ch_pcm_set_volume(unsigned volume) +{ + int rc = 0; + pr_err("multi_ch_pcm_set_volume\n"); + + if (multi_ch_pcm_audio.prtd && multi_ch_pcm_audio.prtd->audio_client) { + pr_err("%s q6asm_set_volume\n", __func__); + rc = q6asm_set_volume(multi_ch_pcm_audio.prtd->audio_client, + volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + multi_ch_pcm_audio.volume = volume; + return rc; +} + + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_err("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + multi_ch_pcm_audio.prtd = NULL; + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_CAPTURE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-multi-ch-pcm-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Multi channel PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c new file mode 100644 index 000000000000..d5385061eaa8 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.c @@ -0,0 +1,581 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 and +* only version 2 as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-afe-v2.h" + +#define MIN_PERIOD_SIZE (128 * 2) +#define MAX_PERIOD_SIZE (128 * 2 * 2 * 6) +static struct snd_pcm_hardware msm_afe_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_PERIOD_SIZE * 32, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = 32, + .periods_max = 384, + .fifo_size = 0, +}; +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt); +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt); + +static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + u32 mem_map_handle = 0; + if (prtd->start) { + pr_debug("sending frame to DSP: poll_time: %d\n", + prtd->poll_time); + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + pr_debug("%s: mem_map_handle 0x%x\n", __func__, mem_map_handle); + afe_rt_proxy_port_write( + (prtd->dma_addr + + (prtd->dsp_cnt * + snd_pcm_lib_period_bytes(prtd->substream))), mem_map_handle, + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt) +{ + struct pcm_afe_info *prtd = + container_of(hrt, struct pcm_afe_info, hrt); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + u32 mem_map_handle = 0; + if (prtd->start) { + if (prtd->dsp_cnt == runtime->periods) + prtd->dsp_cnt = 0; + pr_err("%s: mem_map_handle 0x%x\n", __func__, mem_map_handle); + afe_rt_proxy_port_read( + (prtd->dma_addr + (prtd->dsp_cnt + * snd_pcm_lib_period_bytes(prtd->substream))), mem_map_handle, + snd_pcm_lib_period_bytes(prtd->substream)); + prtd->dsp_cnt++; + pr_debug("sending frame rec to DSP: poll_time: %d\n", + prtd->poll_time); + hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time + * 1000)); + + return HRTIMER_RESTART; + } else + return HRTIMER_NORESTART; +} +static void pcm_afe_process_tx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes + (prtd->substream) * + 1000 * 1000)/ + (runtime->rate * + runtime->channels * 2))); + pr_debug("prtd->poll_time: %d", + prtd->poll_time); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2: + pr_debug("write done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static void pcm_afe_process_rx_pkt(uint32_t opcode, + uint32_t token, uint32_t *payload, + void *priv) +{ + struct pcm_afe_info *prtd = priv; + unsigned long dsp_flags; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime = NULL; + uint16_t event; + + if (prtd == NULL) + return; + substream = prtd->substream; + runtime = substream->runtime; + pr_debug("%s\n", __func__); + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + switch (opcode) { + case AFE_EVENT_RT_PROXY_PORT_STATUS: { + event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); + switch (event) { + case AFE_EVENT_RTPORT_START: { + prtd->dsp_cnt = 0; + prtd->poll_time = ((unsigned long)(( + snd_pcm_lib_period_bytes(prtd->substream) + * 1000 * 1000)/(runtime->rate + * runtime->channels * 2))); + hrtimer_start(&prtd->hrt, + ns_to_ktime(0), + HRTIMER_MODE_REL); + pr_debug("prtd->poll_time : %d", prtd->poll_time); + break; + } + case AFE_EVENT_RTPORT_STOP: + pr_debug("%s: event!=0\n", __func__); + prtd->start = 0; + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + case AFE_EVENT_RTPORT_LOW_WM: + pr_debug("%s: Underrun\n", __func__); + break; + case AFE_EVENT_RTPORT_HI_WM: + pr_debug("%s: Overrun\n", __func__); + break; + default: + break; + } + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2: + pr_debug("Read done\n"); + prtd->pcm_irq_pos += snd_pcm_lib_period_bytes + (prtd->substream); + snd_pcm_period_elapsed(prtd->substream); + break; + default: + break; + } + break; + } + default: + break; + } + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); +} + +static int msm_afe_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_tx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return ret; +} + +static int msm_afe_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret = 0; + + pr_debug("%s\n", __func__); + + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + ret = afe_register_get_events(dai->id, + pcm_afe_process_rx_pkt, prtd); + if (ret < 0) { + pr_err("afe-pcm:register for events failed\n"); + return ret; + } + pr_debug("%s:success\n", __func__); + prtd->prepared++; + return 0; +} + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 16000, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_afe_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = NULL; + int ret = 0; + + prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } else + pr_debug("prtd %x\n", (unsigned int)prtd); + + mutex_init(&prtd->lock); + spin_lock_init(&prtd->dsp_lock); + prtd->dsp_cnt = 0; + + mutex_lock(&prtd->lock); + + runtime->hw = msm_afe_hardware; + prtd->substream = substream; + runtime->private_data = prtd; + mutex_unlock(&prtd->lock); + hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->hrt.function = afe_hrtimer_callback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->hrt.function = afe_hrtimer_rec_callback; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_err("snd_pcm_hw_constraint_integer failed\n"); + + return 0; +} + +static int msm_afe_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_dma_buffer *dma_buf; + struct snd_pcm_runtime *runtime; + struct pcm_afe_info *prtd; + struct snd_soc_pcm_runtime *rtd = NULL; + struct snd_soc_dai *dai = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + rtd = substream->private_data; + dai = rtd->cpu_dai; + runtime = substream->runtime; + prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = afe_unregister_get_events(dai->id); + if (ret < 0) + pr_err("AFE unregister for events failed\n"); + } + hrtimer_cancel(&prtd->hrt); + + rc = afe_cmd_memory_unmap(runtime->dma_addr); + if (rc < 0) + pr_err("AFE memory unmap failed\n"); + + pr_debug("release all buffer\n"); + dma_buf = &substream->dma_buffer; + if (dma_buf == NULL) { + pr_debug("dma_buf is NULL\n"); + goto done; + } + if (dma_buf->area != NULL) { + dma_free_coherent(substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, dma_buf->area, + dma_buf->addr); + dma_buf->area = NULL; + } +done: + pr_debug("%s: dai->id =%x\n", __func__, dai->id); + mutex_unlock(&prtd->lock); + prtd->prepared--; + kfree(prtd); + return 0; +} +static int msm_afe_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + prtd->pcm_irq_pos = 0; + if (prtd->prepared) + return 0; + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_afe_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_afe_capture_prepare(substream); + mutex_unlock(&prtd->lock); + return ret; +} +static int msm_afe_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} +static int msm_afe_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: SNDRV_PCM_TRIGGER_START\n", __func__); + prtd->start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + prtd->start = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +static int msm_afe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct pcm_afe_info *prtd = runtime->private_data; + int rc; + + pr_debug("%s:\n", __func__); + + mutex_lock(&prtd->lock); + + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = dma_alloc_coherent(dma_buf->dev.dev, + runtime->hw.buffer_bytes_max, + &dma_buf->addr, GFP_KERNEL); + + pr_debug("%s: dma_buf->area: 0x%p, dma_buf->addr: 0x%x", __func__, + (unsigned int *) dma_buf->area, dma_buf->addr); + if (!dma_buf->area) { + pr_err("%s:MSM AFE memory allocation failed\n", __func__); + mutex_unlock(&prtd->lock); + return -ENOMEM; + } + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + prtd->dma_addr = (u32) dma_buf->addr; + + mutex_unlock(&prtd->lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + rc = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes); + if (rc < 0) + pr_err("fail to map memory to DSP\n"); + + return rc; +} +static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_afe_info *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream)) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static struct snd_pcm_ops msm_afe_ops = { + .open = msm_afe_open, + .hw_params = msm_afe_hw_params, + .trigger = msm_afe_trigger, + .close = msm_afe_close, + .prepare = msm_afe_prepare, + .mmap = msm_afe_mmap, + .pointer = msm_afe_pointer, +}; + + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + pr_debug("%s\n", __func__); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_afe_afe_probe(struct snd_soc_platform *platform) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_afe_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_afe_afe_probe, +}; + +static int msm_afe_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_afe_remove(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_afe_driver = { + .driver = { + .name = "msm-pcm-afe", + .owner = THIS_MODULE, + }, + .probe = msm_afe_probe, + .remove = msm_afe_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + pr_debug("%s\n", __func__); + return platform_driver_register(&msm_afe_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + pr_debug("%s\n", __func__); + platform_driver_unregister(&msm_afe_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("AFE PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h new file mode 100644 index 000000000000..146992a1a83f --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-afe-v2.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_AFE_H +#define _MSM_PCM_AFE_H +#include +#include + + +struct pcm_afe_info { + unsigned long dma_addr; + struct snd_pcm_substream *substream; + unsigned int pcm_irq_pos; /* IRQ position */ + struct mutex lock; + spinlock_t dsp_lock; + uint32_t samp_rate; + uint32_t channel_mode; + uint8_t start; + uint32_t dsp_cnt; + uint32_t buf_phys; + int32_t mmap_flag; + int prepared; + struct hrtimer hrt; + int poll_time; +}; + + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ + } + +#endif /*_MSM_PCM_AFE_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c new file mode 100644 index 000000000000..c23a60484e72 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c @@ -0,0 +1,609 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct msm_audio *prtd; + unsigned volume; +}; +static struct snd_msm lpa_audio; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 2 * 1024 * 1024, + .period_bytes_min = 128 * 1024, + .period_bytes_max = 512 * 1024, + .periods_min = 4, + .periods_max = 16, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = NULL; + unsigned long flag = 0; + int i = 0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&the_locks.event_lock, flag); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE_V2\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + else + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) { + atomic_set(&prtd->pending_buffer, 1); + break; + } else + atomic_set(&prtd->pending_buffer, 0); + if (runtime->status->hw_ptr >= runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + for (i = 0; i < sizeof(struct audio_aio_write_param)/4; + i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + break; + } + case ASM_DATA_EVENT_RENDERED_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: { + if (!atomic_read(&prtd->pending_buffer)) + break; + if (runtime->status->hw_ptr >= + runtime->control->appl_ptr) + break; + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, prtd->pcm_count); + buf = prtd->audio_client->port[IN].buf; + param.paddr = (unsigned long)buf[prtd->out_head].phys; + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[prtd->out_head].phys; + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) + & (runtime->periods - 1); + atomic_set(&prtd->pending_buffer, 0); + } + break; + case ASM_STREAM_CMD_FLUSH: + pr_debug("ASM_STREAM_CMD_FLUSH\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } + spin_unlock_irqrestore(&the_locks.event_lock, flag); +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + prtd->out_head = 0; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_debug("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + prtd->enabled = 1; + prtd->cmd_ack = 0; + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->pcm_irq_pos = 0; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("SNDRV_PCM_TRIGGER_START\n"); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + struct asm_softpause_params softpause = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + struct asm_softvolume_params softvol = { + .period = SOFT_VOLUME_PERIOD, + .step = SOFT_VOLUME_STEP, + .rampingcurve = SOFT_VOLUME_CURVE_LINEAR, + }; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_debug("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EPERM; + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + atomic_set(&prtd->pending_buffer, 1); + runtime->private_data = prtd; + lpa_audio.prtd = prtd; + lpa_set_volume(lpa_audio.volume); + ret = q6asm_set_softpause(lpa_audio.prtd->audio_client, &softpause); + if (ret < 0) + pr_err("%s: Send SoftPause Param failed ret=%d\n", + __func__, ret); + ret = q6asm_set_softvolume(lpa_audio.prtd->audio_client, &softvol); + if (ret < 0) + pr_err("%s: Send SoftVolume Param failed ret=%d\n", + __func__, ret); + + return 0; +} + +int lpa_set_volume(unsigned volume) +{ + int rc = 0; + if (lpa_audio.prtd && lpa_audio.prtd->audio_client) { + rc = q6asm_set_volume(lpa_audio.prtd->audio_client, volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + lpa_audio.volume = volume; + return rc; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int rc = 0; + + /* + If routing is still enabled, we need to issue EOS to + the DSP + To issue EOS to dsp, we need to be run state otherwise + EOS is not honored. + */ + if (msm_routing_check_backend_enabled(soc_prtd->dai_link->be_id)) { + rc = q6asm_run(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->pending_buffer, 0); + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + pr_debug("%s\n", __func__); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("EOS cmd timeout\n"); + prtd->pcm_irq_pos = 0; + } + + dir = IN; + atomic_set(&prtd->pending_buffer, 0); + lpa_audio.prtd = NULL; + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + pr_debug("%s\n", __func__); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + pr_debug("%s\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s: pcm_irq_pos = %d\n", __func__, prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EPERM; + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed " + "rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + if (buf == NULL || buf[0].data == NULL) + return -ENOMEM; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int msm_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + uint64_t timestamp; + uint64_t temp; + + switch (cmd) { + case SNDRV_COMPRESS_TSTAMP: { + struct snd_compr_tstamp tstamp; + pr_debug("SNDRV_COMPRESS_TSTAMP\n"); + + memset(&tstamp, 0x0, sizeof(struct snd_compr_tstamp)); + timestamp = q6asm_get_session_time(prtd->audio_client); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * runtime->channels); + temp = temp * (runtime->rate/1000); + temp = div_u64(temp, 1000); + tstamp.sampling_rate = runtime->rate; + tstamp.rendered = (size_t)(temp & 0xFFFFFFFF); + tstamp.decoded = (size_t)((temp >> 32) & 0xFFFFFFFF); + tstamp.timestamp = timestamp; + pr_debug("%s: bytes_consumed:lsb = %d, msb = %d," + "timestamp = %lld,\n", + __func__, tstamp.rendered, tstamp.decoded, + tstamp.timestamp); + if (copy_to_user((void *) arg, &tstamp, + sizeof(struct snd_compr_tstamp))) + return -EFAULT; + return 0; + } + case SNDRV_PCM_IOCTL1_RESET: + prtd->cmd_ack = 0; + rc = q6asm_cmd(prtd->audio_client, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + rc = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (rc < 0) + pr_err("Flush cmd timeout\n"); + prtd->pcm_irq_pos = 0; + break; + default: + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = msm_pcm_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", + __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-lpa", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + spin_lock_init(&the_locks.event_lock); + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c new file mode 100644 index 000000000000..21d1a89349cf --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c @@ -0,0 +1,725 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +#define PLAYBACK_NUM_PERIODS 8 +#define PLAYBACK_PERIOD_SIZE 2048 +#define CAPTURE_NUM_PERIODS 16 +#define CAPTURE_PERIOD_SIZE 512 + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = CAPTURE_NUM_PERIODS * CAPTURE_PERIOD_SIZE, + .period_bytes_min = CAPTURE_PERIOD_SIZE, + .period_bytes_max = CAPTURE_PERIOD_SIZE, + .periods_min = CAPTURE_NUM_PERIODS, + .periods_max = CAPTURE_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = PLAYBACK_NUM_PERIODS * PLAYBACK_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_PERIOD_SIZE, + .periods_min = PLAYBACK_NUM_PERIODS, + .periods_max = PLAYBACK_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static uint32_t in_frame_info[CAPTURE_NUM_PERIODS][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + uint32_t idx = 0; + uint32_t size = 0; + + pr_err("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE_V2\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + if (q6asm_is_cpu_buf_avail_nolock(IN, + prtd->audio_client, + &size, &idx)) { + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + } + break; + } + case ASM_DATA_EVENT_RENDERED_EOS: + pr_debug("ASM_DATA_EVENT_RENDERED_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE_V2: { + pr_debug("ASM_DATA_EVENT_READ_DONE_V2\n"); + pr_debug("token = 0x%08x\n", token); + in_frame_info[token][0] = payload[4]; + in_frame_info[token][1] = payload[5]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag + && q6asm_is_cpu_buf_avail_nolock(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&prtd->start, 1); + break; + } + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + atomic_set(&prtd->start, 1); + break; + default: + pr_debug("%s:Payload = [0x%x]stat[0x%x]\n", + __func__, payload[0], payload[1]); + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + prtd->periods = runtime->periods; + + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw = msm_pcm_hardware_playback; + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_hardware_capture; + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_err("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_CAPTURE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + int result = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + + if (runtime->dma_addr && runtime->dma_bytes) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + result = remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + runtime->dma_bytes, + vma->vm_page_prot); + } else { + pr_err("Physical address or size of buf is NULL"); + return -EINVAL; + } + + return result; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; +pr_err("%s: before buf alloc\n", __func__); + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed " + "rc = %d\n", ret); + return -ENOMEM; + } +pr_err("%s: after buf alloc\n", __func__); + buf = prtd->audio_client->port[dir].buf; + if (buf == NULL || buf[0].data == NULL) + return -ENOMEM; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h new file mode 100644 index 000000000000..e70d1d7eb6d7 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2012 The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H +#include +#include + + + +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +extern int copy_count; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + spinlock_t event_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +struct msm_audio { + struct snd_pcm_substream *substream; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + uint16_t source; /* Encoding source bit mask */ + + struct audio_client *audio_client; + + uint16_t session_id; + + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t dsp_cnt; + + int abort; /* set when error, like sample rate mismatch */ + + int enabled; + int close_ack; + int cmd_ack; + atomic_t start; + atomic_t out_count; + atomic_t in_count; + atomic_t out_needed; + int out_head; + int periods; + int mmap_flag; + atomic_t pending_buffer; +}; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c new file mode 100644 index 000000000000..81402cadb201 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.c @@ -0,0 +1,1834 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing-v2.h" +#include "../qdsp6/q6voice.h" + +struct msm_pcm_routing_bdai_data { + u16 port_id; /* AFE port ID */ + u8 active; /* track if this backend is enabled */ + struct snd_pcm_hw_params *hw_params; /* to get freq and channel mode */ + unsigned long fe_sessions; /* Front-end sessions */ + unsigned long port_sessions; /* track Tx BE ports -> Rx BE */ +}; + +#define INVALID_SESSION -1 +#define SESSION_TYPE_RX 0 +#define SESSION_TYPE_TX 1 + +static struct mutex routing_lock; + +static int fm_switch_enable; + +#define INT_FM_RX_VOL_MAX_STEPS 100 +#define INT_FM_RX_VOL_GAIN 2000 + +static int msm_route_fm_vol_control; +static const DECLARE_TLV_DB_SCALE(fm_rx_vol_gain, 0, + INT_FM_RX_VOL_MAX_STEPS, 0); + +#define INT_RX_VOL_MAX_STEPS 100 +#define INT_RX_VOL_GAIN 0x2000 + +static int msm_route_lpa_vol_control; +static const DECLARE_TLV_DB_SCALE(lpa_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + +static int msm_route_multimedia2_vol_control; +static const DECLARE_TLV_DB_SCALE(multimedia2_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + +static int msm_route_compressed_vol_control; +static const DECLARE_TLV_DB_SCALE(compressed_rx_vol_gain, 0, + INT_RX_VOL_MAX_STEPS, 0); + + + +/* Equal to Frontend after last of the MULTIMEDIA SESSIONS */ +#define MAX_EQ_SESSIONS MSM_FRONTEND_DAI_CS_VOICE + +enum { + EQ_BAND1 = 0, + EQ_BAND2, + EQ_BAND3, + EQ_BAND4, + EQ_BAND5, + EQ_BAND6, + EQ_BAND7, + EQ_BAND8, + EQ_BAND9, + EQ_BAND10, + EQ_BAND11, + EQ_BAND12, + EQ_BAND_MAX, +}; + +struct msm_audio_eq_band { + uint16_t band_idx; /* The band index, 0 .. 11 */ + uint32_t filter_type; /* Filter band type */ + uint32_t center_freq_hz; /* Filter band center frequency */ + uint32_t filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + uint32_t q_factor; +} __packed; + +struct msm_audio_eq_stream_config { + uint32_t enable; /* Number of consequtive bands specified */ + uint32_t num_bands; + struct msm_audio_eq_band eq_bands[EQ_BAND_MAX]; +} __packed; + +struct msm_audio_eq_stream_config eq_data[MAX_EQ_SESSIONS]; + +static void msm_send_eq_values(int eq_idx); +/* This array is indexed by back-end DAI ID defined in msm-pcm-routing.h + * If new back-end is defined, add new back-end DAI ID at the end of enum + */ +static struct msm_pcm_routing_bdai_data msm_bedais[MSM_BACKEND_DAI_MAX] = { + { PRIMARY_I2S_RX, 0, NULL, 0, 0}, + { PRIMARY_I2S_TX, 0, NULL, 0, 0}, + { SLIMBUS_0_RX, 0, NULL, 0, 0}, + { SLIMBUS_0_TX, 0, NULL, 0, 0}, + { HDMI_RX, 0, NULL, 0, 0}, + { INT_BT_SCO_RX, 0, NULL, 0, 0}, + { INT_BT_SCO_TX, 0, NULL, 0, 0}, + { INT_FM_RX, 0, NULL, 0, 0}, + { INT_FM_TX, 0, NULL, 0, 0}, + { RT_PROXY_PORT_001_RX, 0, NULL, 0, 0}, + { RT_PROXY_PORT_001_TX, 0, NULL, 0, 0}, + { PCM_RX, 0, NULL, 0, 0}, + { PCM_TX, 0, NULL, 0, 0}, + { VOICE_PLAYBACK_TX, 0, NULL, 0, 0}, + { VOICE_RECORD_RX, 0, NULL, 0, 0}, + { VOICE_RECORD_TX, 0, NULL, 0, 0}, + { MI2S_RX, 0, NULL, 0, 0}, + { SECONDARY_I2S_RX, 0, NULL, 0, 0}, + { SLIMBUS_1_RX, 0, NULL, 0, 0}, + { SLIMBUS_1_TX, 0, NULL, 0, 0}, + { SLIMBUS_INVALID, 0, NULL, 0, 0}, +}; + + +/* Track ASM playback & capture sessions of DAI */ +static int fe_dai_map[MSM_FRONTEND_DAI_MM_SIZE][2] = { + /* MULTIMEDIA1 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA2 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA3 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA4 */ + {INVALID_SESSION, INVALID_SESSION}, +}; + +static void msm_pcm_routing_build_matrix(int fedai_id, int dspst_id, + int path_type) +{ + int i, port_type; + struct route_payload payload; + + payload.num_copps = 0; + port_type = (path_type == ADM_PATH_PLAYBACK ? + MSM_AFE_PORT_TYPE_RX : MSM_AFE_PORT_TYPE_TX); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && + msm_bedais[i].active && (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); +} + +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, int stream_type) +{ + int i, session_type, path_type, port_type; + struct route_payload payload; + u32 channels; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID %d\n", __func__, fedai_id); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + port_type = MSM_AFE_PORT_TYPE_TX; + } + + mutex_lock(&routing_lock); + + payload.num_copps = 0; /* only RX needs to use payload */ + fe_dai_map[fedai_id][session_type] = dspst_id; + /* re-enable EQ if active */ + if (eq_data[fedai_id].enable) + msm_send_eq_values(fedai_id); + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && msm_bedais[i].active && + (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) { + + channels = params_channels(msm_bedais[i].hw_params); + + if ((stream_type == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[i].port_id, + path_type, + params_rate(msm_bedais[i].hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[i].port_id, + path_type, + params_rate(msm_bedais[i].hw_params), + params_channels(msm_bedais[i].hw_params), + DEFAULT_COPP_TOPOLOGY); + + payload.copp_ids[payload.num_copps++] = + msm_bedais[i].port_id; + } + } + if (payload.num_copps) + adm_matrix_map(dspst_id, path_type, + payload.num_copps, payload.copp_ids, 0); + + mutex_unlock(&routing_lock); +} + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type) +{ + int i, port_type, session_type; + + if (fedai_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + port_type = MSM_AFE_PORT_TYPE_RX; + session_type = SESSION_TYPE_RX; + } else { + port_type = MSM_AFE_PORT_TYPE_TX; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((afe_get_port_type(msm_bedais[i].port_id) == + port_type) && msm_bedais[i].active && + (test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) + adm_close(msm_bedais[i].port_id); + } + + fe_dai_map[fedai_id][session_type] = INVALID_SESSION; + + mutex_unlock(&routing_lock); +} + +/* Check if FE/BE route is set */ +static bool msm_pcm_routing_route_is_set(u16 be_id, u16 fe_id) +{ + bool rc = false; + + if (fe_id > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return rc; + } + + if (test_bit(fe_id, &msm_bedais[be_id].fe_sessions)) + rc = true; + + return rc; +} + +static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) +{ + int session_type, path_type; + u32 channels; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (val > MSM_FRONTEND_DAI_MM_MAX_ID) { + /* recheck FE ID in the mixer control defined in this file */ + pr_err("%s: bad MM ID\n", __func__); + return; + } + + if (afe_get_port_type(msm_bedais[reg].port_id) == + MSM_AFE_PORT_TYPE_RX) { + session_type = SESSION_TYPE_RX; + path_type = ADM_PATH_PLAYBACK; + } else { + session_type = SESSION_TYPE_TX; + path_type = ADM_PATH_LIVE_REC; + } + + mutex_lock(&routing_lock); + + if (set) { + set_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + + channels = params_channels(msm_bedais[reg].hw_params); + + if ((session_type == SESSION_TYPE_RX) && (channels > 2)) + adm_multi_ch_copp_open(msm_bedais[reg].port_id, + path_type, + params_rate(msm_bedais[reg].hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(msm_bedais[reg].port_id, + path_type, + params_rate(msm_bedais[reg].hw_params), + params_channels(msm_bedais[reg].hw_params), + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } else { + clear_bit(val, &msm_bedais[reg].fe_sessions); + if (msm_bedais[reg].active && fe_dai_map[val][session_type] != + INVALID_SESSION) { + adm_close(msm_bedais[reg].port_id); + msm_pcm_routing_build_matrix(val, + fe_dai_map[val][session_type], path_type); + } + } + mutex_unlock(&routing_lock); +} + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_info("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + + if (ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == false) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else if (!ucontrol->value.integer.value[0] && + msm_pcm_routing_route_is_set(mc->reg, mc->shift) == true) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + pr_info("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static void msm_pcm_routing_process_voice(u16 reg, u16 val, int set) +{ + return; +} + +static int msm_routing_get_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + return 1; +} + +static int msm_routing_get_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + mutex_lock(&routing_lock); + + if (test_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + mutex_unlock(&routing_lock); + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_voice_stub_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + mutex_lock(&routing_lock); + set_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + mutex_lock(&routing_lock); + clear_bit(mc->shift, &msm_bedais[mc->reg].fe_sessions); + mutex_unlock(&routing_lock); + + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 1; +} + +static int msm_routing_get_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = fm_switch_enable; + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + return 0; +} + +static int msm_routing_put_switch_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + + pr_debug("%s: FM Switch enable %ld\n", __func__, + ucontrol->value.integer.value[0]); + if (ucontrol->value.integer.value[0]) + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + else + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + fm_switch_enable = ucontrol->value.integer.value[0]; + return 1; +} + +static int msm_routing_get_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &msm_bedais[mc->reg].port_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, + mc->shift, ucontrol->value.integer.value[0]); + + if (ucontrol->value.integer.value[0]) { + afe_loopback(1, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + set_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } else { + afe_loopback(0, msm_bedais[mc->reg].port_id, + msm_bedais[mc->shift].port_id); + clear_bit(mc->shift, + &msm_bedais[mc->reg].port_sessions); + } + + return 1; +} + +static int msm_routing_get_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_fm_vol_control; + return 0; +} + +static int msm_routing_set_fm_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + afe_loopback_gain(INT_FM_TX , ucontrol->value.integer.value[0]); + + msm_route_fm_vol_control = ucontrol->value.integer.value[0]; + + return 0; +} + +static int msm_routing_get_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = msm_route_lpa_vol_control; + return 0; +} + +static int msm_routing_set_lpa_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!lpa_set_volume(ucontrol->value.integer.value[0])) + msm_route_lpa_vol_control = + ucontrol->value.integer.value[0]; + + return 0; + +} + +static int msm_routing_get_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_multimedia2_vol_control; + return 0; +} + +static int msm_routing_set_multimedia2_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!multi_ch_pcm_set_volume(ucontrol->value.integer.value[0])) + msm_route_multimedia2_vol_control = + ucontrol->value.integer.value[0]; + return 0; +} + +static int msm_routing_get_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.integer.value[0] = msm_route_compressed_vol_control; + return 0; +} + +static int msm_routing_set_compressed_vol_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!compressed_set_volume(ucontrol->value.integer.value[0])) + msm_route_compressed_vol_control = + ucontrol->value.integer.value[0]; + return 0; +} + +static void msm_send_eq_values(int eq_idx) +{ + int result; + struct audio_client *ac = + q6asm_get_audio_client(fe_dai_map[eq_idx][SESSION_TYPE_RX]); + + if (ac == NULL) { + pr_err("%s: Could not get audio client for session: %d\n", + __func__, fe_dai_map[eq_idx][SESSION_TYPE_RX]); + goto done; + } + + result = q6asm_equalizer(ac, &eq_data[eq_idx]); + + if (result < 0) + pr_err("%s: Call to ASM equalizer failed, returned = %d\n", + __func__, result); +done: + return; +} + +static int msm_routing_get_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].enable; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, eq_data[eq_idx].enable); + return 0; +} + +static int msm_routing_put_eq_enable_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d enable %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].enable = value; + + msm_send_eq_values(eq_idx); + return 0; +} + +static int msm_routing_get_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + + ucontrol->value.integer.value[0] = eq_data[eq_idx].num_bands; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, eq_data[eq_idx].num_bands); + return eq_data[eq_idx].num_bands; +} + +static int msm_routing_put_eq_band_count_audio_mixer( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int value = ucontrol->value.integer.value[0]; + + pr_debug("%s: EQ #%d bands %d\n", __func__, + eq_idx, value); + eq_data[eq_idx].num_bands = value; + return 0; +} + +static int msm_routing_get_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = + eq_data[eq_idx].eq_bands[band_idx].band_idx; + ucontrol->value.integer.value[1] = + eq_data[eq_idx].eq_bands[band_idx].filter_type; + ucontrol->value.integer.value[2] = + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz; + ucontrol->value.integer.value[3] = + eq_data[eq_idx].eq_bands[band_idx].filter_gain; + ucontrol->value.integer.value[4] = + eq_data[eq_idx].eq_bands[band_idx].q_factor; + + pr_debug("%s: band_idx = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].band_idx); + pr_debug("%s: filter_type = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_type); + pr_debug("%s: center_freq_hz = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz); + pr_debug("%s: filter_gain = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].filter_gain); + pr_debug("%s: q_factor = %d\n", __func__, + eq_data[eq_idx].eq_bands[band_idx].q_factor); + return 0; +} + +static int msm_routing_put_eq_band_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->reg; + int band_idx = ((struct soc_multi_mixer_control *) + kcontrol->private_value)->shift; + + eq_data[eq_idx].eq_bands[band_idx].band_idx = + ucontrol->value.integer.value[0]; + eq_data[eq_idx].eq_bands[band_idx].filter_type = + ucontrol->value.integer.value[1]; + eq_data[eq_idx].eq_bands[band_idx].center_freq_hz = + ucontrol->value.integer.value[2]; + eq_data[eq_idx].eq_bands[band_idx].filter_gain = + ucontrol->value.integer.value[3]; + eq_data[eq_idx].eq_bands[band_idx].q_factor = + ucontrol->value.integer.value[4]; + return 0; +} + +static const struct snd_kcontrol_new pri_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_PRI_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SEC_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_MI2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + /* incall music delivery mixer */ +static const struct snd_kcontrol_new incall_music_delivery_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_bt_sco_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_fm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_DL", MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("VOC_REC_UL", MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_INT_FM_TX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new pri_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_PRI_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new sec_i2s_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new bt_sco_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new afe_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new aux_pcm_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new hdmi_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", MSM_BACKEND_DAI_HDMI_RX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new stub_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_INVALID, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("Voice Stub", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voice", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voice", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voice", + MSM_BACKEND_DAI_INT_BT_SCO_TX, MSM_FRONTEND_DAI_CS_VOICE, 1, 0, + msm_routing_get_voice_mixer, msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voice", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voice", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voip_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voip", MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voip", MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voip", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AFE_PCM_TX_Voip", MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("AUX_PCM_TX_Voip", MSM_BACKEND_DAI_AUXPCM_TX, + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_stub_mixer_controls[] = { + SOC_SINGLE_EXT("STUB_TX_HL", MSM_BACKEND_DAI_INVALID, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_FRONTEND_DAI_VOICE_STUB, 1, 0, msm_routing_get_voice_stub_mixer, + msm_routing_put_voice_stub_mixer), +}; + +static const struct snd_kcontrol_new sbus_0_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new auxpcm_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("AUX_PCM_UL_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new sbus_1_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new bt_sco_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("SLIM_1_TX", MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_kcontrol_new fm_switch_mixer_controls = + SOC_SINGLE_EXT("Switch", SND_SOC_NOPM, + 0, 1, 0, msm_routing_get_switch_mixer, + msm_routing_put_switch_mixer); + +static const struct snd_kcontrol_new int_fm_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("Internal FM RX Volume", SND_SOC_NOPM, 0, + INT_FM_RX_VOL_GAIN, 0, msm_routing_get_fm_vol_mixer, + msm_routing_set_fm_vol_mixer, fm_rx_vol_gain), +}; + +static const struct snd_kcontrol_new lpa_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("LPA RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_lpa_vol_mixer, + msm_routing_set_lpa_vol_mixer, lpa_rx_vol_gain), +}; + +static const struct snd_kcontrol_new multimedia2_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("HIFI2 RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_multimedia2_vol_mixer, + msm_routing_set_multimedia2_vol_mixer, multimedia2_rx_vol_gain), +}; + +static const struct snd_kcontrol_new compressed_vol_mixer_controls[] = { + SOC_SINGLE_EXT_TLV("COMPRESSED RX Volume", SND_SOC_NOPM, 0, + INT_RX_VOL_GAIN, 0, msm_routing_get_compressed_vol_mixer, + msm_routing_set_compressed_vol_mixer, compressed_rx_vol_gain), +}; + +static const struct snd_kcontrol_new eq_enable_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Enable", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_eq_enable_mixer, + msm_routing_put_eq_enable_mixer), +}; + +static const struct snd_kcontrol_new eq_band_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA1, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA2, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3 EQ Band Count", SND_SOC_NOPM, + MSM_FRONTEND_DAI_MULTIMEDIA3, 11, 0, + msm_routing_get_eq_band_count_audio_mixer, + msm_routing_put_eq_band_count_audio_mixer), +}; + +static const struct snd_kcontrol_new eq_coeff_mixer_controls[] = { + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia1 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA1, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia2 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA2, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band1", EQ_BAND1, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band2", EQ_BAND2, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band3", EQ_BAND3, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band4", EQ_BAND4, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band5", EQ_BAND5, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band6", EQ_BAND6, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band7", EQ_BAND7, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band8", EQ_BAND8, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band9", EQ_BAND9, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band10", EQ_BAND10, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band11", EQ_BAND11, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), + SOC_SINGLE_MULTI_EXT("MultiMedia3 EQ Band12", EQ_BAND12, + MSM_FRONTEND_DAI_MULTIMEDIA3, 255, 0, 5, + msm_routing_get_eq_band_audio_mixer, + msm_routing_put_eq_band_audio_mixer), +}; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Frontend AIF */ + /* Widget name equals to Front-End DAI name, + * Stream name must contains substring of front-end dai name + */ + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOIP_DL", "VoIP Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOIP_UL", "VoIP Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM0_DL_HL", "SLIMBUS0_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIM0_UL_HL", "SLIMBUS0_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INTFM_DL_HL", "INT_FM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INTFM_UL_HL", "INT_FM_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("HDMI_DL_HL", "HDMI_HOSTLESS Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUXPCM_DL_HL", "AUXPCM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUXPCM_UL_HL", "AUXPCM_HOSTLESS Capture", + 0, 0, 0, 0), + + /* Backend AIF */ + /* Stream name equals to backend dai link stream name + */ + SND_SOC_DAPM_AIF_OUT("PRI_I2S_RX", "Primary I2S Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_I2S_RX", "Secondary I2S Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_OUT("MI2S_RX", "MI2S Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRI_I2S_TX", "Primary I2S Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_0_TX", "Slimbus Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_BT_SCO_RX", "Internal BT-SCO Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_BT_SCO_TX", "Internal BT-SCO Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_FM_RX", "Internal FM Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_FM_TX", "Internal FM Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PCM_RX", "AFE Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("PCM_TX", "AFE Capture", + 0, 0, 0 , 0), + /* incall */ + SND_SOC_DAPM_AIF_OUT("VOICE_PLAYBACK_TX", "Voice Farend Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_TX", "Voice Uplink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INCALL_RECORD_RX", "Voice Downlink Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("AUX_PCM_RX", "AUX PCM Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("AUX_PCM_TX", "AUX PCM Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOICE_STUB_DL", "VOICE_STUB Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOICE_STUB_UL", "VOICE_STUB Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("STUB_RX", "Stub Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("STUB_TX", "Stub Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_TX", "Slimbus1 Capture", 0, 0, 0, 0), + + /* Switch Definitions */ + SND_SOC_DAPM_SWITCH("SLIMBUS_DL_HL", SND_SOC_NOPM, 0, 0, + &fm_switch_mixer_controls), + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("PRI_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_i2s_rx_mixer_controls, ARRAY_SIZE(pri_i2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_i2s_rx_mixer_controls, ARRAY_SIZE(sec_i2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)), + SND_SOC_DAPM_MIXER("MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + mi2s_rx_mixer_controls, ARRAY_SIZE(mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + auxpcm_rx_mixer_controls, ARRAY_SIZE(auxpcm_rx_mixer_controls)), + /* incall */ + SND_SOC_DAPM_MIXER("Incall_Music Audio Mixer", SND_SOC_NOPM, 0, 0, + incall_music_delivery_mixer_controls, + ARRAY_SIZE(incall_music_delivery_mixer_controls)), + /* Voice Mixer */ + SND_SOC_DAPM_MIXER("PRI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, pri_rx_voice_mixer_controls, + ARRAY_SIZE(pri_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + sec_i2s_rx_voice_mixer_controls, + ARRAY_SIZE(sec_i2s_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIM_0_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + slimbus_rx_voice_mixer_controls, + ARRAY_SIZE(slimbus_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + bt_sco_rx_voice_mixer_controls, + ARRAY_SIZE(bt_sco_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + afe_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(afe_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("AUX_PCM_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + aux_pcm_rx_voice_mixer_controls, + ARRAY_SIZE(aux_pcm_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + hdmi_rx_voice_mixer_controls, + ARRAY_SIZE(hdmi_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voice_mixer_controls, + ARRAY_SIZE(tx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voip_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voip_mixer_controls, + ARRAY_SIZE(tx_voip_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_bt_sco_rx_mixer_controls, ARRAY_SIZE(int_bt_sco_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_FM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_fm_rx_mixer_controls, ARRAY_SIZE(int_fm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("AFE_PCM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + afe_pcm_rx_mixer_controls, ARRAY_SIZE(afe_pcm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice Stub Tx Mixer", SND_SOC_NOPM, 0, 0, + tx_voice_stub_mixer_controls, ARRAY_SIZE(tx_voice_stub_mixer_controls)), + SND_SOC_DAPM_MIXER("STUB_RX Mixer", SND_SOC_NOPM, 0, 0, + stub_rx_mixer_controls, ARRAY_SIZE(stub_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sbus_0_rx_port_mixer_controls, + ARRAY_SIZE(sbus_0_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("AUXPCM_RX Port Mixer", + SND_SOC_NOPM, 0, 0, auxpcm_rx_port_mixer_controls, + ARRAY_SIZE(auxpcm_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Port Mixer", SND_SOC_NOPM, 0, 0, + sbus_1_rx_port_mixer_controls, + ARRAY_SIZE(sbus_1_rx_port_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Port Mixer", SND_SOC_NOPM, 0, 0, + bt_sco_rx_port_mixer_controls, + ARRAY_SIZE(bt_sco_rx_port_mixer_controls)), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"PRI_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"PRI_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"PRI_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"PRI_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PRI_I2S_RX", NULL, "PRI_RX Audio Mixer"}, + + {"SEC_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SEC_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SEC_I2S_RX", NULL, "SEC_RX Audio Mixer"}, + + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI Mixer", "MultiMedia3", "MM_DL3"}, + {"HDMI Mixer", "MultiMedia4", "MM_DL4"}, + {"HDMI", NULL, "HDMI Mixer"}, + + /* incall */ + {"Incall_Music Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"Incall_Music Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"VOICE_PLAYBACK_TX", NULL, "Incall_Music Audio Mixer"}, + + {"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"}, + {"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"}, + {"MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"MI2S_RX", NULL, "MI2S_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"}, + {"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"MultiMedia1 Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Audio Mixer"}, + + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"INT_FM_RX", NULL, "INTERNAL_FM_RX Audio Mixer"}, + + {"AFE_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AFE_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PCM_RX", NULL, "AFE_PCM_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"MultiMedia1 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + + {"MultiMedia1 Mixer", "AFE_PCM_TX", "PCM_TX"}, + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MultiMedia2 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + + {"AUX_PCM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"AUX_PCM_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX Audio Mixer"}, + + {"PRI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"PRI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PRI_I2S_RX", NULL, "PRI_RX_Voice Mixer"}, + + {"SEC_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SEC_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SEC_I2S_RX", NULL, "SEC_RX_Voice Mixer"}, + + {"SLIM_0_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SLIM_0_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SLIMBUS_0_RX", NULL, "SLIM_0_RX_Voice Mixer"}, + + {"INTERNAL_BT_SCO_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX_Voice Mixer"}, + + {"AFE_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AFE_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PCM_RX", NULL, "AFE_PCM_RX_Voice Mixer"}, + + {"AUX_PCM_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"AUX_PCM_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"AUX_PCM_RX", NULL, "AUX_PCM_RX_Voice Mixer"}, + + {"HDMI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"HDMI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"HDMI", NULL, "HDMI_RX_Voice Mixer"}, + {"HDMI", NULL, "HDMI_DL_HL"}, + + {"Voice_Tx Mixer", "PRI_TX_Voice", "PRI_I2S_TX"}, + {"Voice_Tx Mixer", "SLIM_0_TX_Voice", "SLIMBUS_0_TX"}, + {"Voice_Tx Mixer", "INTERNAL_BT_SCO_TX_Voice", "INT_BT_SCO_TX"}, + {"Voice_Tx Mixer", "AFE_PCM_TX_Voice", "PCM_TX"}, + {"Voice_Tx Mixer", "AUX_PCM_TX_Voice", "AUX_PCM_TX"}, + {"CS-VOICE_UL1", NULL, "Voice_Tx Mixer"}, + {"Voip_Tx Mixer", "PRI_TX_Voip", "PRI_I2S_TX"}, + {"Voip_Tx Mixer", "SLIM_0_TX_Voip", "SLIMBUS_0_TX"}, + {"Voip_Tx Mixer", "INTERNAL_BT_SCO_TX_Voip", "INT_BT_SCO_TX"}, + {"Voip_Tx Mixer", "AFE_PCM_TX_Voip", "PCM_TX"}, + {"Voip_Tx Mixer", "AUX_PCM_TX_Voip", "AUX_PCM_TX"}, + + {"VOIP_UL", NULL, "Voip_Tx Mixer"}, + {"SLIMBUS_DL_HL", "Switch", "SLIM0_DL_HL"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_DL_HL"}, + {"SLIM0_UL_HL", NULL, "SLIMBUS_0_TX"}, + {"INT_FM_RX", NULL, "INTFM_DL_HL"}, + {"INTFM_UL_HL", NULL, "INT_FM_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_DL_HL"}, + {"AUXPCM_UL_HL", NULL, "AUX_PCM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"SLIMBUS_0_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Port Mixer"}, + + {"AUXPCM_RX Port Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"}, + {"AUXPCM_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"AUX_PCM_RX", NULL, "AUXPCM_RX Port Mixer"}, + + {"Voice Stub Tx Mixer", "STUB_TX_HL", "STUB_TX"}, + {"Voice Stub Tx Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"Voice Stub Tx Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"VOICE_STUB_UL", NULL, "Voice Stub Tx Mixer"}, + + {"STUB_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"STUB_RX", NULL, "STUB_RX Mixer"}, + {"SLIMBUS_1_RX Mixer", "Voice Stub", "VOICE_STUB_DL"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Mixer"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voice Stub", "VOICE_STUB_DL"}, + + {"SLIMBUS_1_RX Port Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Port Mixer"}, + {"INTERNAL_BT_SCO_RX Port Mixer", "SLIM_1_TX", "SLIMBUS_1_TX"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Port Mixer"}, +}; + +static int msm_pcm_routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + mutex_lock(&routing_lock); + msm_bedais[be_id].hw_params = params; + mutex_unlock(&routing_lock); + return 0; +} + +static int msm_pcm_routing_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, session_type; + struct msm_pcm_routing_bdai_data *bedai; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + bedai = &msm_bedais[be_id]; + + session_type = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 0 : 1); + + mutex_lock(&routing_lock); + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) + adm_close(bedai->port_id); + } + + bedai->active = 0; + bedai->hw_params = NULL; + + mutex_unlock(&routing_lock); + + return 0; +} + +static int msm_pcm_routing_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->dai_link->be_id; + int i, path_type, session_type; + struct msm_pcm_routing_bdai_data *bedai; + u32 channels; + + if (be_id >= MSM_BACKEND_DAI_MAX) { + pr_err("%s: unexpected be_id %d\n", __func__, be_id); + return -EINVAL; + } + + + bedai = &msm_bedais[be_id]; + + if (bedai->hw_params == NULL) { + pr_err("%s: HW param is not configured", __func__); + return -EINVAL; + } + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path_type = ADM_PATH_PLAYBACK; + session_type = SESSION_TYPE_RX; + } else { + path_type = ADM_PATH_LIVE_REC; + session_type = SESSION_TYPE_TX; + } + + mutex_lock(&routing_lock); + + if (bedai->active == 1) + goto done; /* Ignore prepare if back-end already active */ + + /* AFE port is not active at this point. However, still + * go ahead setting active flag under the notion that + * QDSP6 is able to handle ADM starting before AFE port + * is started. + */ + bedai->active = 1; + + for_each_set_bit(i, &bedai->fe_sessions, MSM_FRONTEND_DAI_MM_SIZE) { + if (fe_dai_map[i][session_type] != INVALID_SESSION) { + + channels = params_channels(bedai->hw_params); + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (channels > 2)) + adm_multi_ch_copp_open(bedai->port_id, + path_type, + params_rate(bedai->hw_params), + channels, + DEFAULT_COPP_TOPOLOGY); + else + adm_open(bedai->port_id, + path_type, + params_rate(bedai->hw_params), + params_channels(bedai->hw_params), + DEFAULT_COPP_TOPOLOGY); + + msm_pcm_routing_build_matrix(i, + fe_dai_map[i][session_type], path_type); + } + } + +done: + mutex_unlock(&routing_lock); + + return 0; +} + +static struct snd_pcm_ops msm_routing_pcm_ops = { + .hw_params = msm_pcm_routing_hw_params, + .close = msm_pcm_routing_close, + .prepare = msm_pcm_routing_prepare, +}; + +static unsigned int msm_routing_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + dev_dbg(platform->dev, "reg %x\n", reg); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val) +{ + dev_dbg(platform->dev, "reg %x val %x\n", reg, val); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_probe(struct snd_soc_platform *platform) +{ + snd_soc_dapm_new_controls(&platform->dapm, msm_qdsp6_widgets, + ARRAY_SIZE(msm_qdsp6_widgets)); + snd_soc_dapm_add_routes(&platform->dapm, intercon, + ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&platform->dapm); + + snd_soc_add_platform_controls(platform, + int_fm_vol_mixer_controls, + ARRAY_SIZE(int_fm_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + lpa_vol_mixer_controls, + ARRAY_SIZE(lpa_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_enable_mixer_controls, + ARRAY_SIZE(eq_enable_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_band_mixer_controls, + ARRAY_SIZE(eq_band_mixer_controls)); + + snd_soc_add_platform_controls(platform, + eq_coeff_mixer_controls, + ARRAY_SIZE(eq_coeff_mixer_controls)); + + snd_soc_add_platform_controls(platform, + multimedia2_vol_mixer_controls, + ARRAY_SIZE(multimedia2_vol_mixer_controls)); + + snd_soc_add_platform_controls(platform, + compressed_vol_mixer_controls, + ARRAY_SIZE(compressed_vol_mixer_controls)); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_routing_platform = { + .ops = &msm_routing_pcm_ops, + .probe = msm_routing_probe, + .read = msm_routing_read, + .write = msm_routing_write, +}; + +static int msm_routing_pcm_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_routing_platform); +} + +static int msm_routing_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_routing_pcm_driver = { + .driver = { + .name = "msm-pcm-routing", + .owner = THIS_MODULE, + }, + .probe = msm_routing_pcm_probe, + .remove = msm_routing_pcm_remove, +}; + +int msm_routing_check_backend_enabled(int fedai_id) +{ + int i; + if (fedai_id >= MSM_FRONTEND_DAI_MM_MAX_ID) { + /* bad ID assigned in machine driver */ + pr_err("%s: bad MM ID\n", __func__); + return 0; + } + for (i = 0; i < MSM_BACKEND_DAI_MAX; i++) { + if ((test_bit(fedai_id, + &msm_bedais[i].fe_sessions))) { + return msm_bedais[i].active; + } + } + return 0; +} + +static int __init msm_soc_routing_platform_init(void) +{ + mutex_init(&routing_lock); + return platform_driver_register(&msm_routing_pcm_driver); +} +module_init(msm_soc_routing_platform_init); + +static void __exit msm_soc_routing_platform_exit(void) +{ + platform_driver_unregister(&msm_routing_pcm_driver); +} +module_exit(msm_soc_routing_platform_exit); + +MODULE_DESCRIPTION("MSM routing platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h new file mode 100644 index 000000000000..e5bb194122b3 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_ROUTING_H +#define _MSM_PCM_ROUTING_H +#include + +#define LPASS_BE_PRI_I2S_RX "(Backend) PRIMARY_I2S_RX" +#define LPASS_BE_PRI_I2S_TX "(Backend) PRIMARY_I2S_TX" +#define LPASS_BE_SLIMBUS_0_RX "(Backend) SLIMBUS_0_RX" +#define LPASS_BE_SLIMBUS_0_TX "(Backend) SLIMBUS_0_TX" +#define LPASS_BE_HDMI "(Backend) HDMI" +#define LPASS_BE_INT_BT_SCO_RX "(Backend) INT_BT_SCO_RX" +#define LPASS_BE_INT_BT_SCO_TX "(Backend) INT_BT_SCO_TX" +#define LPASS_BE_INT_FM_RX "(Backend) INT_FM_RX" +#define LPASS_BE_INT_FM_TX "(Backend) INT_FM_TX" +#define LPASS_BE_AFE_PCM_RX "(Backend) RT_PROXY_DAI_001_RX" +#define LPASS_BE_AFE_PCM_TX "(Backend) RT_PROXY_DAI_002_TX" +#define LPASS_BE_AUXPCM_RX "(Backend) AUX_PCM_RX" +#define LPASS_BE_AUXPCM_TX "(Backend) AUX_PCM_TX" +#define LPASS_BE_VOICE_PLAYBACK_TX "(Backend) VOICE_PLAYBACK_TX" +#define LPASS_BE_INCALL_RECORD_RX "(Backend) INCALL_RECORD_TX" +#define LPASS_BE_INCALL_RECORD_TX "(Backend) INCALL_RECORD_RX" +#define LPASS_BE_SEC_I2S_RX "(Backend) SECONDARY_I2S_RX" + +#define LPASS_BE_MI2S_RX "(Backend) MI2S_RX" +#define LPASS_BE_STUB_RX "(Backend) STUB_RX" +#define LPASS_BE_STUB_TX "(Backend) STUB_TX" +#define LPASS_BE_SLIMBUS_1_RX "(Backend) SLIMBUS_1_RX" +#define LPASS_BE_SLIMBUS_1_TX "(Backend) SLIMBUS_1_TX" + +/* For multimedia front-ends, asm session is allocated dynamically. + * Hence, asm session/multimedia front-end mapping has to be maintained. + * Due to this reason, additional multimedia front-end must be placed before + * non-multimedia front-ends. + */ + +enum { + MSM_FRONTEND_DAI_MULTIMEDIA1 = 0, + MSM_FRONTEND_DAI_MULTIMEDIA2, + MSM_FRONTEND_DAI_MULTIMEDIA3, + MSM_FRONTEND_DAI_MULTIMEDIA4, + MSM_FRONTEND_DAI_CS_VOICE, + MSM_FRONTEND_DAI_VOIP, + MSM_FRONTEND_DAI_AFE_RX, + MSM_FRONTEND_DAI_AFE_TX, + MSM_FRONTEND_DAI_VOICE_STUB, + MSM_FRONTEND_DAI_MAX, +}; + +#define MSM_FRONTEND_DAI_MM_SIZE (MSM_FRONTEND_DAI_MULTIMEDIA4 + 1) +#define MSM_FRONTEND_DAI_MM_MAX_ID MSM_FRONTEND_DAI_MULTIMEDIA4 + +enum { + MSM_BACKEND_DAI_PRI_I2S_RX = 0, + MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_BACKEND_DAI_INT_FM_RX, + MSM_BACKEND_DAI_INT_FM_TX, + MSM_BACKEND_DAI_AFE_PCM_RX, + MSM_BACKEND_DAI_AFE_PCM_TX, + MSM_BACKEND_DAI_AUXPCM_RX, + MSM_BACKEND_DAI_AUXPCM_TX, + MSM_BACKEND_DAI_VOICE_PLAYBACK_TX, + MSM_BACKEND_DAI_INCALL_RECORD_RX, + MSM_BACKEND_DAI_INCALL_RECORD_TX, + MSM_BACKEND_DAI_MI2S_RX, + MSM_BACKEND_DAI_SEC_I2S_RX, + MSM_BACKEND_DAI_SLIMBUS_1_RX, + MSM_BACKEND_DAI_SLIMBUS_1_TX, + MSM_BACKEND_DAI_INVALID, + MSM_BACKEND_DAI_MAX, +}; + +/* dai_id: front-end ID, + * dspst_id: DSP audio stream ID + * stream_type: playback or capture + */ +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, + int stream_type); +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type); + +int lpa_set_volume(unsigned volume); + +int msm_routing_check_backend_enabled(int fedai_id); + +int multi_ch_pcm_set_volume(unsigned volume); + +int compressed_set_volume(unsigned volume); + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/qdsp6v2/q6adm.c b/sound/soc/msm/qdsp6v2/q6adm.c new file mode 100644 index 000000000000..8554da3d945e --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6adm.c @@ -0,0 +1,621 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + + +#include +#include + +#include +#include +#include +#include + + +#define TIMEOUT_MS 1000 + +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF + +struct adm_ctl { + void *apr; + atomic_t copp_id[Q6_AFE_MAX_PORTS]; + atomic_t copp_cnt[Q6_AFE_MAX_PORTS]; + atomic_t copp_stat[Q6_AFE_MAX_PORTS]; + u32 mem_map_handle[Q6_AFE_MAX_PORTS]; + wait_queue_head_t wait[Q6_AFE_MAX_PORTS]; +}; + +static struct adm_ctl this_adm; + +static int32_t adm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *payload; + int i, index; + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("adm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, + this_adm.apr); + if (this_adm.apr) { + apr_reset(this_adm.apr); + for (i = 0; i < Q6_AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], + RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + } + this_adm.apr = NULL; + } + return 0; + } + + pr_debug("%s: code = 0x%x PL#0[%x], PL#1[%x], size = %d\n", __func__, + data->opcode, payload[0], payload[1], + data->payload_size); + + if (data->payload_size) { + index = q6audio_get_port_index(data->token); + if (index < 0 || index >= Q6_AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, data->token); + return 0; + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + pr_debug("APR_BASIC_RSP_RESULT\n"); + switch (payload[0]) { + case ADM_CMD_SET_PP_PARAMS_V5: + if (rtac_make_adm_callback( + payload, data->payload_size)) + pr_debug("%s: payload[0]: 0x%x\n", + __func__, payload[0]); + break; + case ADM_CMD_DEVICE_CLOSE_V5: + case ADM_CMD_SHARED_MEM_UNMAP_REGIONS: + case ADM_CMD_SHARED_MEM_MAP_REGIONS: + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + pr_debug("ADM_CMD_MATRIX_MAP_ROUTINGS\n"); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait[index]); + break; + default: + pr_err("%s: Unknown Cmd: 0x%x\n", __func__, + payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ADM_CMDRSP_DEVICE_OPEN_V5: { + struct adm_cmd_rsp_device_open_v5 *open = + (struct adm_cmd_rsp_device_open_v5 *)data->payload; + if (open->copp_id == INVALID_COPP_ID) { + pr_err("%s: invalid coppid rxed %d\n", + __func__, open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait[index]); + break; + } + atomic_set(&this_adm.copp_id[index], open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + pr_debug("%s: coppid rxed=%d\n", __func__, + open->copp_id); + wake_up(&this_adm.wait[index]); + } + break; + case ADM_CMD_GET_PP_PARAMS_V5: + pr_debug("%s: ADM_CMD_GET_PP_PARAMS_V5\n", __func__); + rtac_make_adm_callback(payload, + data->payload_size); + break; + default: + pr_err("%s: Unknown cmd:0x%x\n", __func__, + data->opcode); + break; + } + } + return 0; +} + +/* TODO: send_adm_cal_block function to be defined + when calibration available for 8974 */ +static void send_adm_cal(int port_id, int path) +{ + /* function to be defined when calibration available for 8974 */ + pr_debug("%s\n", __func__); +} + +int adm_open(int port_id, int path, int rate, int channel_mode, int topology) +{ + struct adm_cmd_device_open_v5 open; + int ret = 0; + int index; + int tmp_port = q6audio_get_port_id(port_id); + + pr_debug("%s: port %d path:%d rate:%d mode:%d\n", __func__, + port_id, path, rate, channel_mode); + + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + + + /* Create a COPP if port id are not enabled */ + if (atomic_read(&this_adm.copp_cnt[index]) == 0) { + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + open.hdr.pkt_size = sizeof(open); + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = tmp_port; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = tmp_port; + open.hdr.token = port_id; + open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + + open.mode_of_operation = path; + /* Reserved for future use, need to set this to 0 */ + open.flags = 0x00; + open.endpoint_id_1 = tmp_port; + open.endpoint_id_2 = 0xFFFF; + + /* convert path to acdb path */ + if (path == ADM_PATH_PLAYBACK) + open.topology_id = get_adm_rx_topology(); + else { + open.topology_id = get_adm_tx_topology(); + if ((open.topology_id == + VPM_TX_SM_ECNS_COPP_TOPOLOGY) || + (open.topology_id == + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY)) + rate = 16000; + } + + if (open.topology_id == 0) + open.topology_id = topology; + + open.dev_num_channel = channel_mode & 0x00FF; + open.bit_width = 16; + open.sample_rate = rate; + memset(open.dev_channel_mapping, 0, 8); + + if (channel_mode == 1) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FC; + } else if (channel_mode == 2) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channel_mode == 6) { + open.dev_channel_mapping[0] = PCM_CHANNEL_FL; + open.dev_channel_mapping[1] = PCM_CHANNEL_FR; + open.dev_channel_mapping[2] = PCM_CHANNEL_LFE; + open.dev_channel_mapping[3] = PCM_CHANNEL_FC; + open.dev_channel_mapping[4] = PCM_CHANNEL_LB; + open.dev_channel_mapping[5] = PCM_CHANNEL_RB; + } else { + pr_err("%s invalid num_chan %d\n", __func__, + channel_mode); + return -EINVAL; + } + + pr_debug("%s: port_id=%d rate=%d" + "topology_id=0x%X\n", __func__, open.endpoint_id_1, \ + open.sample_rate, open.topology_id); + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&open); + if (ret < 0) { + pr_err("%s:ADM enable for port %d for[%d] failed\n", + __func__, tmp_port, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d" + "for [%d]\n", __func__, tmp_port, port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + + +int adm_multi_ch_copp_open(int port_id, int path, int rate, int channel_mode, + int topology) +{ + int ret = 0; + + ret = adm_open(port_id, path, rate, channel_mode, topology); + + return ret; +} + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id) +{ + struct adm_cmd_matrix_map_routings_v5 *route; + struct adm_session_map_node_v5 *node; + uint32_t *copps_list; + int cmd_size = 0; + int ret = 0, i = 0; + void *payload = NULL; + void *matrix_map = NULL; + + /* Assumes port_ids have already been validated during adm_open */ + int index = q6audio_get_port_index(copp_id); + if (index < 0 || index >= Q6_AFE_MAX_PORTS) { + pr_err("%s: invalid port idx %d token %d\n", + __func__, index, copp_id); + return 0; + } + cmd_size = (sizeof(struct adm_cmd_matrix_map_routings_v5) + + sizeof(struct adm_session_map_node_v5) + + (sizeof(uint32_t) * num_copps)); + matrix_map = kzalloc(cmd_size, GFP_KERNEL); + if (matrix_map == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + ret = -EINVAL; + return ret; + } + route = (struct adm_cmd_matrix_map_routings_v5 *)matrix_map; + + pr_debug("%s: session 0x%x path:%d num_copps:%d port_id[0] :%d coppid[%d]\n", + __func__, session_id, path, num_copps, port_id[0], copp_id); + + route->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + route->hdr.pkt_size = cmd_size; + route->hdr.src_svc = 0; + route->hdr.src_domain = APR_DOMAIN_APPS; + route->hdr.src_port = copp_id; + route->hdr.dest_svc = APR_SVC_ADM; + route->hdr.dest_domain = APR_DOMAIN_ADSP; + route->hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + route->hdr.token = copp_id; + route->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5; + route->num_sessions = 1; + + switch (path) { + case 0x1: + route->matrix_id = ADM_MATRIX_ID_AUDIO_RX; + break; + case 0x2: + case 0x3: + route->matrix_id = ADM_MATRIX_ID_AUDIO_TX; + break; + default: + pr_err("%s: Wrong path set[%d]\n", __func__, path); + break; + } + payload = ((u8 *)matrix_map + + sizeof(struct adm_cmd_matrix_map_routings_v5)); + node = (struct adm_session_map_node_v5 *)payload; + + node->session_id = session_id; + node->num_copps = num_copps; + payload = (u8 *)node + sizeof(struct adm_session_map_node_v5); + copps_list = (uint32_t *)payload; + for (i = 0; i < num_copps; i++) { + int tmp; + port_id[i] = q6audio_convert_virtual_to_portid(port_id[i]); + + tmp = q6audio_get_port_index(port_id[i]); + + + if (tmp >= 0 && tmp < Q6_AFE_MAX_PORTS) + copps_list[i] = + atomic_read(&this_adm.copp_id[tmp]); + pr_debug("%s: port_id[%d]: %d, index: %d act coppid[0x%x]\n", + __func__, i, port_id[i], tmp, + atomic_read(&this_adm.copp_id[tmp])); + } + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)matrix_map); + if (ret < 0) { + pr_err("%s: ADM routing for port %d failed\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + for (i = 0; i < num_copps; i++) + send_adm_cal(port_id[i], path); + +fail_cmd: + kfree(matrix_map); + return ret; +} + +int adm_memory_map_regions(int port_id, + uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + int index = 0; + + pr_debug("%s\n", __func__); + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + rtac_set_adm_handle(this_adm.apr); + } + + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port id[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_get_port_index(port_id); + + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + sizeof(struct avs_shared_map_region_payload) + * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + mmap_regions = (struct avs_cmd_shared_mem_map_regions *)mmap_region_cmd; + mmap_regions->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mmap_regions->hdr.pkt_size = cmd_size; + mmap_regions->hdr.src_port = 0; + mmap_regions->hdr.dest_port = 0; + mmap_regions->hdr.token = 0; + mmap_regions->hdr.opcode = ADM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL & 0x00ff; + mmap_regions->num_regions = bufcnt & 0x00ff; + mmap_regions->property_flag = 0x00; + + pr_debug("%s: map_regions->num_regions = %d\n", __func__, + mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->shm_addr_lsw = buf_add[i]; + mregions->shm_addr_msw = 0x00; + mregions->mem_size_bytes = bufsz[i]; + ++mregions; + } + + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + mmap_regions->hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_map\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + kfree(mmap_region_cmd); + return ret; +} + +int adm_memory_unmap_regions(int32_t port_id, uint32_t *buf_add, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_unmap_regions unmap_regions; + int ret = 0; + int cmd_size = 0; + int index = 0; + + pr_debug("%s\n", __func__); + + if (this_adm.apr == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + port_id = q6audio_convert_virtual_to_portid(port_id); + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = q6audio_get_port_index(port_id); + + unmap_regions.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + unmap_regions.hdr.pkt_size = cmd_size; + unmap_regions.hdr.src_port = 0; + unmap_regions.hdr.dest_port = 0; + unmap_regions.hdr.token = 0; + unmap_regions.hdr.opcode = ADM_CMD_SHARED_MEM_UNMAP_REGIONS; + unmap_regions.mem_map_handle = this_adm.mem_map_handle[index]; + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) &unmap_regions); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + unmap_regions.hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_unmap\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + return ret; +} + +int adm_get_copp_id(int port_index) +{ + pr_debug("%s\n", __func__); + + if (port_index < 0) { + pr_err("%s: invalid port_id = %d\n", __func__, port_index); + return -EINVAL; + } + + return atomic_read(&this_adm.copp_id[port_index]); +} + +int adm_close(int port_id) +{ + struct apr_hdr close; + + int ret = 0; + int index = 0; + + port_id = q6audio_convert_virtual_to_portid(port_id); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + pr_debug("%s port_id=%d index %d\n", __func__, port_id, index); + + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + pr_err("%s: copp count for port[%d]is 0\n", __func__, port_id); + + goto fail_cmd; + } + atomic_dec(&this_adm.copp_cnt[index]); + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + + close.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + close.pkt_size = sizeof(close); + close.src_svc = APR_SVC_ADM; + close.src_domain = APR_DOMAIN_APPS; + close.src_port = port_id; + close.dest_svc = APR_SVC_ADM; + close.dest_domain = APR_DOMAIN_ADSP; + close.dest_port = atomic_read(&this_adm.copp_id[index]); + close.token = port_id; + close.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + atomic_set(&this_adm.copp_id[index], RESET_COPP_ID); + atomic_set(&this_adm.copp_stat[index], 0); + + + pr_debug("%s:coppid %d portid=%d index=%d coppcnt=%d\n", + __func__, + atomic_read(&this_adm.copp_id[index]), + port_id, index, + atomic_read(&this_adm.copp_cnt[index])); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&close); + if (ret < 0) { + pr_err("%s ADM close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait[index], + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + rtac_remove_adm_device(port_id); + } + +fail_cmd: + return ret; +} + +static int __init adm_init(void) +{ + int i = 0; + this_adm.apr = NULL; + + for (i = 0; i < Q6_AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + init_waitqueue_head(&this_adm.wait[i]); + } + return 0; +} + +device_initcall(adm_init); diff --git a/sound/soc/msm/qdsp6v2/q6afe.c b/sound/soc/msm/qdsp6v2/q6afe.c new file mode 100644 index 000000000000..a21cd9dd8c46 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6afe.c @@ -0,0 +1,1584 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +struct afe_ctl { + void *apr; + atomic_t state; + atomic_t status; + wait_queue_head_t wait[AFE_MAX_PORTS]; + void (*tx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void (*rx_cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv); + void *tx_private_data; + void *rx_private_data; +}; + +static struct afe_ctl this_afe; + +static struct acdb_cal_block afe_cal_addr[MAX_AUDPROC_TYPES]; + +#define TIMEOUT_MS 1000 +#define Q6AFE_MAX_VOLUME 0x3FFF + +#define SIZEOF_CFG_CMD(y) \ + (sizeof(struct apr_hdr) + sizeof(u16) + (sizeof(struct y))) + +static int32_t afe_callback(struct apr_client_data *data, void *priv) +{ + if (data->opcode == RESET_EVENTS) { + pr_debug("q6afe: reset event = %d %d apr[%p]\n", + data->reset_event, data->reset_proc, this_afe.apr); + if (this_afe.apr) { + apr_reset(this_afe.apr); + atomic_set(&this_afe.state, 0); + this_afe.apr = NULL; + } + return 0; + } + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x\n", + __func__, data->opcode, + ((uint32_t *)(data->payload))[0], + ((uint32_t *)(data->payload))[1]); + if (data->payload_size) { + uint32_t *payload; + uint16_t port_id = 0; + payload = data->payload; + pr_debug("%s:opcode = 0x%x cmd = 0x%x status = 0x%x token=%d\n", + __func__, data->opcode, + payload[0], payload[1], data->token); + /* payload[1] contains the error status for response */ + if (payload[1] != 0) { + atomic_set(&this_afe.status, -1); + pr_err("%s: cmd = 0x%x returned error = 0x%x\n", + __func__, payload[0], payload[1]); + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case AFE_PORT_CMD_DEVICE_STOP: + case AFE_PORT_CMD_DEVICE_START: + case AFE_PORT_CMD_SET_PARAM_V2: + case AFE_PSEUDOPORT_CMD_START: + case AFE_PSEUDOPORT_CMD_STOP: + case AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS: + case AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS: + case AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER: + atomic_set(&this_afe.state, 0); + wake_up(&this_afe.wait[data->token]); + break; + case AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER: + break; + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2: + port_id = RT_PROXY_PORT_001_TX; + break; + case AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2: + port_id = RT_PROXY_PORT_001_RX; + break; + default: + pr_err("%s:Unknown cmd 0x%x\n", __func__, + payload[0]); + break; + } + } else if (data->opcode == AFE_EVENT_RT_PROXY_PORT_STATUS) { + port_id = (uint16_t)(0x0000FFFF & payload[0]); + } + pr_debug("%s:port_id = %x\n", __func__, port_id); + switch (port_id) { + case RT_PROXY_PORT_001_TX: { + if (this_afe.tx_cb) { + this_afe.tx_cb(data->opcode, data->token, + data->payload, + this_afe.tx_private_data); + } + break; + } + case RT_PROXY_PORT_001_RX: { + if (this_afe.rx_cb) { + this_afe.rx_cb(data->opcode, data->token, + data->payload, + this_afe.rx_private_data); + } + break; + } + default: + break; + } + } + return 0; +} + + +int afe_get_port_type(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PCM_RX: + case SECONDARY_I2S_RX: + case MI2S_RX: + case HDMI_RX: + case SLIMBUS_0_RX: + case SLIMBUS_1_RX: + case INT_BT_SCO_RX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case VOICE_PLAYBACK_TX: + case RT_PROXY_PORT_001_RX: + ret = MSM_AFE_PORT_TYPE_RX; + break; + + case PRIMARY_I2S_TX: + case PCM_TX: + case SECONDARY_I2S_TX: + case MI2S_TX: + case DIGI_MIC_TX: + case VOICE_RECORD_TX: + case SLIMBUS_0_TX: + case SLIMBUS_1_TX: + case INT_FM_TX: + case VOICE_RECORD_RX: + case INT_BT_SCO_TX: + case RT_PROXY_PORT_001_TX: + ret = MSM_AFE_PORT_TYPE_TX; + break; + + default: + pr_err("%s: invalid port id %d\n", __func__, port_id); + ret = -EINVAL; + } + + return ret; +} + +int afe_sizeof_cfg_cmd(u16 port_id) +{ + int ret_size; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + ret_size = SIZEOF_CFG_CMD(afe_param_id_i2s_cfg); + break; + case HDMI_RX: + ret_size = + SIZEOF_CFG_CMD(afe_param_id_hdmi_multi_chan_audio_cfg); + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + ret_size = SIZEOF_CFG_CMD(afe_param_id_slimbus_cfg); + break; + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + ret_size = SIZEOF_CFG_CMD(afe_param_id_rt_proxy_port_cfg); + break; + case PCM_RX: + case PCM_TX: + default: + ret_size = SIZEOF_CFG_CMD(afe_param_id_pcm_cfg); + break; + } + return ret_size; +} + +int afe_q6_interface_prepare(void) +{ + int ret = 0; + + pr_debug("%s:", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + } + } + return ret; +} +static void afe_send_cal_block(int32_t path, u16 port_id) +{ + /* To come back */ +} + +void afe_send_cal(u16 port_id) +{ + pr_debug("%s\n", __func__); + + if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_TX) + afe_send_cal_block(TX_CAL, port_id); + else if (afe_get_port_type(port_id) == MSM_AFE_PORT_TYPE_RX) + afe_send_cal_block(RX_CAL, port_id); +} + +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate) /* This function is no blocking */ +{ + struct afe_port_cmd_device_start start; + struct afe_audioif_config_command config; + int ret; + int cfg_type; + int index = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + pr_err("%s: %d %d\n", __func__, port_id, rate); + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + + config.hdr.token = index; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + cfg_type = AFE_PARAM_ID_PCM_CONFIG; + break; + case PCM_RX: + case PCM_TX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + default: + pr_err("%s: Invalid port id 0x%x\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + config.param.port_id = port_id; + config.param.payload_size = (afe_sizeof_cfg_cmd(port_id) + + sizeof(struct afe_port_param_data_v2)); + config.param.payload_address_lsw = 0x00; + config.param.payload_address_msw = 0x00; + config.param.mem_map_handle = 0x00; + config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + config.pdata.param_id = cfg_type; + config.pdata.param_size = afe_sizeof_cfg_cmd(port_id); + + config.port = *afe_config; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + /* send AFE cal */ + afe_send_cal(port_id); + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PORT_CMD_DEVICE_START; + start.port_id = port_id; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + + if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + return 0; + +fail_cmd: + return ret; +} + +int afe_open(u16 port_id, + union afe_port_config *afe_config, int rate) +{ + struct afe_port_cmd_device_start start; + struct afe_audioif_config_command config; + int ret = 0; + int cfg_type; + int index = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + + pr_err("%s: %d %d\n", __func__, port_id, rate); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_001_RX) || + (port_id == RT_PROXY_DAI_002_TX)) + return -EINVAL; + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + if (q6audio_validate_port(port_id) < 0) { + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = sizeof(config); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = index; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case PCM_RX: + case PCM_TX: + cfg_type = AFE_PARAM_ID_PCM_CONFIG; + break; + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case SLIMBUS_2_RX: + case SLIMBUS_2_TX: + case SLIMBUS_3_RX: + case SLIMBUS_3_TX: + case SLIMBUS_4_RX: + case SLIMBUS_4_TX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + default: + pr_err("%s: Invalid port id 0x%x\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + config.param.port_id = q6audio_get_port_id(port_id); + config.param.payload_size = sizeof(config) - sizeof(struct apr_hdr) + - sizeof(config.param); + config.param.payload_address_lsw = 0x00; + config.param.payload_address_msw = 0x00; + config.param.mem_map_handle = 0x00; + config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + config.pdata.param_id = cfg_type; + config.pdata.param_size = sizeof(config.port); + + config.port = *afe_config; + pr_debug("%s: param PL size=%d iparam_size[%d][%d %d %d %d]" + " param_id[%x]\n", + __func__, config.param.payload_size, config.pdata.param_size, + sizeof(config), sizeof(config.param), sizeof(config.port), + sizeof(struct apr_hdr), config.pdata.param_id); + atomic_set(&this_afe.state, 1); + atomic_set(&this_afe.status, 0); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d opcode[0x%x]failed\n", + __func__, port_id, cfg_type); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + if (atomic_read(&this_afe.status) != 0) { + pr_err("%s: config cmd failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = index; + start.hdr.opcode = AFE_PORT_CMD_DEVICE_START; + start.port_id = q6audio_get_port_id(port_id); + pr_debug("%s: cmd device start opcode[0x%x] port id[0x%x]\n", + __func__, start.hdr.opcode, start.port_id); + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + return 0; +fail_cmd: + return ret; +} + +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port) +{ + struct afe_loopback_cfg_v1 lb_cmd; + int ret = 0; + int index = 0; + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + index = q6audio_get_port_index(rx_port); + if (q6audio_validate_port(rx_port) < 0) + return -EINVAL; + + lb_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + lb_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(lb_cmd) - APR_HDR_SIZE); + lb_cmd.hdr.src_port = 0; + lb_cmd.hdr.dest_port = 0; + lb_cmd.hdr.token = 0; + lb_cmd.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + lb_cmd.param.port_id = tx_port; + lb_cmd.param.payload_size = (sizeof(lb_cmd) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + lb_cmd.param.payload_address_lsw = 0x00; + lb_cmd.param.payload_address_msw = 0x00; + lb_cmd.param.mem_map_handle = 0x00; + lb_cmd.pdata.module_id = AFE_MODULE_LOOPBACK; + lb_cmd.pdata.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + lb_cmd.pdata.param_size = lb_cmd.param.payload_size - + sizeof(struct afe_port_param_data_v2); + + lb_cmd.dst_port_id = rx_port; + lb_cmd.routing_mode = LB_MODE_DEFAULT; + lb_cmd.enable = (enable ? 1 : 0); + lb_cmd.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + atomic_set(&this_afe.state, 1); + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &lb_cmd); + if (ret < 0) { + pr_err("%s: AFE loopback failed\n", __func__); + ret = -EINVAL; + goto done; + } + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + } +done: + return ret; +} + +int afe_loopback_gain(u16 port_id, u16 volume) +{ + struct afe_loopback_gain_per_path_param set_param; + int ret = 0; + int index = 0; + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + if (q6audio_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + /* RX ports numbers are even .TX ports numbers are odd. */ + if (port_id % 2 == 0) { + pr_err("%s: Failed : afe loopback gain only for TX ports." + " port_id %d\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + pr_debug("%s: %d %hX\n", __func__, port_id, volume); + + set_param.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + set_param.hdr.pkt_size = sizeof(set_param); + set_param.hdr.src_port = 0; + set_param.hdr.dest_port = 0; + set_param.hdr.token = 0; + set_param.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + + set_param.param.port_id = port_id; + set_param.param.payload_size = + (sizeof(struct afe_loopback_gain_per_path_param) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + set_param.param.payload_address_lsw = 0; + set_param.param.payload_address_msw = 0; + set_param.param.mem_map_handle = 0; + + set_param.pdata.module_id = AFE_MODULE_LOOPBACK; + set_param.pdata.param_id = AFE_PARAM_ID_LOOPBACK_GAIN_PER_PATH; + set_param.pdata.param_size = (set_param.param.payload_size - + sizeof(struct afe_port_param_data_v2)); + set_param.rx_port_id = port_id; + set_param.gain = volume; + + set_param.hdr.token = index; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &set_param); + if (ret < 0) { + pr_err("%s: AFE param set failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_pseudo_port_start_nowait(u16 port_id) +{ + struct afe_pseudoport_start_command start; + int ret = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + if (this_afe.apr == NULL) { + pr_err("%s: AFE APR is not registered\n", __func__); + return -ENODEV; + } + + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PSEUDOPORT_CMD_START; + start.port_id = port_id; + start.timing = 1; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed %d\n", + __func__, port_id, ret); + return -EINVAL; + } + return 0; +} + +int afe_start_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + ret = afe_q6_interface_prepare(); + if (ret != 0) + return ret; + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PSEUDOPORT_CMD_START; + start.port_id = port_id; + start.timing = 1; + + start.hdr.token = index; + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed %d\n", + __func__, port_id, ret); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +int afe_pseudo_port_stop_nowait(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PSEUDOPORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + stop.hdr.token = index; + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + if (ret < 0) { + pr_err("%s: AFE close failed %d\n", __func__, ret); + return -EINVAL; + } + + return 0; + +} + +int afe_stop_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + int index = 0; + + pr_debug("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + return -EINVAL; + } + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PSEUDOPORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + stop.hdr.token = index; + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + if (ret < 0) { + pr_err("%s: AFE close failed %d\n", __func__, ret); + return -EINVAL; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + + return 0; +} + +/*bharath, memory map handle needs to be stored by AFE client */ +int afe_cmd_memory_map(u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + int cmd_size = 0; + void *payload = NULL; + void *mmap_region_cmd = NULL; + struct afe_service_cmd_shared_mem_map_regions *mregion = NULL; + struct afe_service_shared_map_region_payload *mregion_pl = NULL; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + cmd_size = sizeof(struct afe_service_cmd_shared_mem_map_regions) \ + + sizeof(struct afe_service_shared_map_region_payload); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + + mregion = (struct afe_service_cmd_shared_mem_map_regions *) + mmap_region_cmd; + mregion->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion->hdr.pkt_size = sizeof(mregion); + mregion->hdr.src_port = 0; + mregion->hdr.dest_port = 0; + mregion->hdr.token = 0; + mregion->hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS; + mregion->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mregion->num_regions = 1; + mregion->property_flag = 0x00; + /* Todo */ + index = mregion->hdr.token = IDX_RSVD_2; + + payload = ((u8 *) mmap_region_cmd + + sizeof(struct afe_service_cmd_shared_mem_map_regions)); + + mregion_pl = (struct afe_service_shared_map_region_payload *)payload; + + mregion_pl->shm_addr_lsw = dma_addr_p; + mregion_pl->shm_addr_msw = 0x00; + mregion_pl->mem_size_bytes = dma_buf_sz; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + + return 0; +} + +int afe_cmd_memory_map_nowait(int port_id, u32 dma_addr_p, u32 dma_buf_sz) +{ + int ret = 0; + int cmd_size = 0; + void *payload = NULL; + void *mmap_region_cmd = NULL; + struct afe_service_cmd_shared_mem_map_regions *mregion = NULL; + struct afe_service_shared_map_region_payload *mregion_pl = NULL; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + cmd_size = sizeof(struct afe_service_cmd_shared_mem_map_regions) + + sizeof(struct afe_service_shared_map_region_payload); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + mregion = (struct afe_service_cmd_shared_mem_map_regions *) + mmap_region_cmd; + mregion->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion->hdr.pkt_size = sizeof(mregion); + mregion->hdr.src_port = 0; + mregion->hdr.dest_port = 0; + mregion->hdr.token = 0; + mregion->hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_MAP_REGIONS; + mregion->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mregion->num_regions = 1; + mregion->property_flag = 0x00; + + payload = ((u8 *) mmap_region_cmd + + sizeof(struct afe_service_cmd_shared_mem_map_regions)); + mregion_pl = (struct afe_service_shared_map_region_payload *)payload; + + mregion_pl->shm_addr_lsw = dma_addr_p; + mregion_pl->shm_addr_msw = 0x00; + mregion_pl->mem_size_bytes = dma_buf_sz; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: AFE memory map cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_cmd_memory_unmap(u32 mem_map_handle) +{ + int ret = 0; + struct afe_service_cmd_shared_mem_unmap_regions mregion; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS; + mregion.mem_map_handle = mem_map_handle; + + /* Todo */ + index = mregion.hdr.token = IDX_RSVD_2; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_cmd_memory_unmap_nowait(u32 mem_map_handle) +{ + int ret = 0; + struct afe_service_cmd_shared_mem_unmap_regions mregion; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + mregion.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mregion.hdr.pkt_size = sizeof(mregion); + mregion.hdr.src_port = 0; + mregion.hdr.dest_port = 0; + mregion.hdr.token = 0; + mregion.hdr.opcode = AFE_SERVICE_CMD_SHARED_MEM_UNMAP_REGIONS; + mregion.mem_map_handle = mem_map_handle; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &mregion); + if (ret < 0) { + pr_err("%s: AFE memory unmap cmd failed %d\n", + __func__, ret); + ret = -EINVAL; + } + return 0; +} + +int afe_register_get_events(u16 port_id, + void (*cb) (uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv), + void *private_data) +{ + int ret = 0; + struct afe_service_cmd_register_rt_port_driver rtproxy; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = cb; + this_afe.tx_private_data = private_data; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = cb; + this_afe.rx_private_data = private_data; + } + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 1; + rtproxy.hdr.dest_port = 1; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_REGISTER_RT_PORT_DRIVER; + rtproxy.port_id = port_id; + rtproxy.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE reg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_unregister_get_events(u16 port_id) +{ + int ret = 0; + struct afe_service_cmd_unregister_rt_port_driver rtproxy; + int index = 0; + + pr_debug("%s:\n", __func__); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_debug("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + if ((port_id == RT_PROXY_DAI_002_RX) || + (port_id == RT_PROXY_DAI_001_TX)) + port_id = VIRTUAL_ID_TO_PORTID(port_id); + else + return -EINVAL; + + rtproxy.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + rtproxy.hdr.pkt_size = sizeof(rtproxy); + rtproxy.hdr.src_port = 0; + rtproxy.hdr.dest_port = 0; + rtproxy.hdr.token = 0; + rtproxy.hdr.opcode = AFE_SERVICE_CMD_UNREGISTER_RT_PORT_DRIVER; + rtproxy.port_id = port_id; + rtproxy.reserved = 0; + + rtproxy.hdr.token = index; + + if (port_id == RT_PROXY_PORT_001_TX) { + this_afe.tx_cb = NULL; + this_afe.tx_private_data = NULL; + } else if (port_id == RT_PROXY_PORT_001_RX) { + this_afe.rx_cb = NULL; + this_afe.rx_private_data = NULL; + } + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &rtproxy); + if (ret < 0) { + pr_err("%s: AFE enable Unreg. rtproxy_event failed %d\n", + __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + return 0; +} + +int afe_rt_proxy_port_write(u32 buf_addr_p, u32 mem_map_handle, int bytes) +{ + int ret = 0; + struct afe_port_data_cmd_rt_proxy_port_write_v2 afecmd_wr; + + if (this_afe.apr == NULL) { + pr_err("%s:register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_wr.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_wr.hdr.pkt_size = sizeof(afecmd_wr); + afecmd_wr.hdr.src_port = 0; + afecmd_wr.hdr.dest_port = 0; + afecmd_wr.hdr.token = 0; + afecmd_wr.hdr.opcode = AFE_PORT_DATA_CMD_RT_PROXY_PORT_WRITE_V2; + afecmd_wr.port_id = RT_PROXY_PORT_001_TX; + afecmd_wr.buffer_address_lsw = (uint32_t)buf_addr_p; + afecmd_wr.buffer_address_msw = 0x00; + afecmd_wr.mem_map_handle = mem_map_handle; + afecmd_wr.available_bytes = bytes; + afecmd_wr.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_wr); + if (ret < 0) { + pr_err("%s: AFE rtproxy write to port 0x%x failed %d\n", + __func__, afecmd_wr.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; + +} + +int afe_rt_proxy_port_read(u32 buf_addr_p, u32 mem_map_handle, int bytes) +{ + int ret = 0; + struct afe_port_data_cmd_rt_proxy_port_read_v2 afecmd_rd; + + if (this_afe.apr == NULL) { + pr_err("%s: register to AFE is not done\n", __func__); + ret = -ENODEV; + return ret; + } + pr_debug("%s: buf_addr_p = 0x%08x bytes = %d\n", __func__, + buf_addr_p, bytes); + + afecmd_rd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + afecmd_rd.hdr.pkt_size = sizeof(afecmd_rd); + afecmd_rd.hdr.src_port = 0; + afecmd_rd.hdr.dest_port = 0; + afecmd_rd.hdr.token = 0; + afecmd_rd.hdr.opcode = AFE_PORT_DATA_CMD_RT_PROXY_PORT_READ_V2; + afecmd_rd.port_id = RT_PROXY_PORT_001_RX; + afecmd_rd.buffer_address_lsw = (uint32_t)buf_addr_p; + afecmd_rd.buffer_address_msw = 0x00; + afecmd_rd.available_bytes = bytes; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &afecmd_rd); + if (ret < 0) { + pr_err("%s: AFE rtproxy read cmd to port 0x%x failed %d\n", + __func__, afecmd_rd.port_id, ret); + ret = -EINVAL; + return ret; + } + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_afelb; +static struct dentry *debugfs_afelb_gain; + +static int afe_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_info("debug intf %s\n", (char *) file->private_data); + return 0; +} + +static int afe_get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } else + return -EINVAL; + } + return 0; +} +#define AFE_LOOPBACK_ON (1) +#define AFE_LOOPBACK_OFF (0) +static ssize_t afe_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char lbuf[32]; + int rc; + unsigned long param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strncmp(lb_str, "afe_loopback", 12)) { + rc = afe_get_parameters(lbuf, param, 3); + if (!rc) { + pr_info("%s %lu %lu %lu\n", lb_str, param[0], param[1], + param[2]); + + if ((param[0] != AFE_LOOPBACK_ON) && (param[0] != + AFE_LOOPBACK_OFF)) { + pr_err("%s: Error, parameter 0 incorrect\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + if ((q6audio_validate_port(param[1]) < 0) || + (q6audio_validate_port(param[2])) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + } + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback(param[0], param[1], param[2]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + + } else if (!strncmp(lb_str, "afe_loopback_gain", 17)) { + rc = afe_get_parameters(lbuf, param, 2); + if (!rc) { + pr_info("%s %lu %lu\n", lb_str, param[0], param[1]); + + if (q6audio_validate_port(param[0]) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + + if (param[1] < 0 || param[1] > 100) { + pr_err("%s: Error, volume shoud be 0 to 100" + " percentage param = %lu\n", + __func__, param[1]); + rc = -EINVAL; + goto afe_error; + } + + param[1] = (Q6AFE_MAX_VOLUME * param[1]) / 100; + + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback_gain(param[0], param[1]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + } + +afe_error: + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations afe_debug_fops = { + .open = afe_debug_open, + .write = afe_debug_write +}; + +static void config_debug_fs_init(void) +{ + debugfs_afelb = debugfs_create_file("afe_loopback", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback", + &afe_debug_fops); + + debugfs_afelb_gain = debugfs_create_file("afe_loopback_gain", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback_gain", + &afe_debug_fops); +} +static void config_debug_fs_exit(void) +{ + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_afelb_gain) + debugfs_remove(debugfs_afelb_gain); +} +#else +static void config_debug_fs_init(void) +{ + return; +} +static void config_debug_fs_exit(void) +{ + return; +} +#endif +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain) +{ + struct afe_loopback_cfg_v1 cmd_sidetone; + int ret = 0; + int index = 0; + + pr_info("%s: tx_port_id:%d rx_port_id:%d enable:%d gain:%d\n", __func__, + tx_port_id, rx_port_id, enable, gain); + index = q6audio_get_port_index(rx_port_id); + if (q6audio_validate_port(rx_port_id) < 0) + return -EINVAL; + + cmd_sidetone.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cmd_sidetone.hdr.pkt_size = sizeof(cmd_sidetone); + cmd_sidetone.hdr.src_port = 0; + cmd_sidetone.hdr.dest_port = 0; + cmd_sidetone.hdr.token = 0; + cmd_sidetone.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + /* should it be rx or tx port id ?? , bharath*/ + cmd_sidetone.param.port_id = tx_port_id; + /* size of data param & payload */ + cmd_sidetone.param.payload_size = (sizeof(cmd_sidetone) - + sizeof(struct apr_hdr) - + sizeof(struct afe_port_cmd_set_param_v2)); + cmd_sidetone.param.payload_address_lsw = 0x00; + cmd_sidetone.param.payload_address_msw = 0x00; + cmd_sidetone.param.mem_map_handle = 0x00; + cmd_sidetone.pdata.module_id = AFE_MODULE_LOOPBACK; + cmd_sidetone.pdata.param_id = AFE_PARAM_ID_LOOPBACK_CONFIG; + /* size of actual payload only */ + cmd_sidetone.pdata.param_size = cmd_sidetone.param.payload_size - + sizeof(struct afe_port_param_data_v2); + + cmd_sidetone.loopback_cfg_minor_version = + AFE_API_VERSION_LOOPBACK_CONFIG; + cmd_sidetone.dst_port_id = rx_port_id; + cmd_sidetone.routing_mode = LB_MODE_SIDETONE; + cmd_sidetone.enable = enable; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &cmd_sidetone); + if (ret < 0) { + pr_err("%s: AFE sidetone failed for tx_port:%d rx_port:%d\n", + __func__, tx_port_id, rx_port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_port_stop_nowait(int port_id) +{ + struct afe_port_cmd_device_stop stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + port_id = q6audio_convert_virtual_to_portid(port_id); + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + } + +fail_cmd: + return ret; + +} + +int afe_close(int port_id) +{ + struct afe_port_cmd_device_stop stop; + int ret = 0; + int index = 0; + + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_debug("%s: port_id=%d\n", __func__, port_id); + + index = q6audio_get_port_index(port_id); + if (q6audio_validate_port(port_id) < 0) + return -EINVAL; + + port_id = q6audio_convert_virtual_to_portid(port_id); + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = index; + stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop.port_id = q6audio_get_port_id(port_id); + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (ret < 0) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait[index], + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + return ret; +} + +static int __init afe_init(void) +{ + int i = 0; + atomic_set(&this_afe.state, 0); + atomic_set(&this_afe.status, 0); + this_afe.apr = NULL; + for (i = 0; i < AFE_MAX_PORTS; i++) + init_waitqueue_head(&this_afe.wait[i]); + + config_debug_fs_init(); + return 0; +} + +static void __exit afe_exit(void) +{ + int i; + + config_debug_fs_exit(); + for (i = 0; i < MAX_AUDPROC_TYPES; i++) { + if (afe_cal_addr[i].cal_paddr != 0) + afe_cmd_memory_unmap_nowait( + afe_cal_addr[i].cal_paddr); + } +} + +device_initcall(afe_init); +__exitcall(afe_exit); diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c new file mode 100644 index 000000000000..6cafd1d946d4 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6asm.c @@ -0,0 +1,3342 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TRUE 0x01 +#define FALSE 0x00 +#define READDONE_IDX_STATUS 0 +#define READDONE_IDX_BUFADD_LSW 1 +#define READDONE_IDX_BUFADD_MSW 2 +#define READDONE_IDX_MEMMAP_HDL 3 +#define READDONE_IDX_SIZE 4 +#define READDONE_IDX_OFFSET 5 +#define READDONE_IDX_LSW_TS 6 +#define READDONE_IDX_MSW_TS 7 +#define READDONE_IDX_FLAGS 8 +#define READDONE_IDX_NUMFRAMES 9 +#define READDONE_IDX_SEQ_ID 10 + +/* TODO, combine them together */ +static DEFINE_MUTEX(session_lock); +struct asm_mmap { + atomic_t ref_cnt; + void *apr; +}; + +static struct asm_mmap this_mmap; +/* session id: 0 reserved */ +static struct audio_client *session[SESSION_MAX+1]; + +struct asm_buffer_node { + struct list_head list; + uint32_t buf_addr_lsw; + uint32_t mmap_hdl; +}; +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv); +static int32_t q6asm_callback(struct apr_client_data *data, void *priv); +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); +static void q6asm_reset_buf_state(struct audio_client *ac); + + +#ifdef CONFIG_DEBUG_FS +#define OUT_BUFFER_SIZE 56 +#define IN_BUFFER_SIZE 24 + +static struct timeval out_cold_tv; +static struct timeval out_warm_tv; +static struct timeval out_cont_tv; +static struct timeval in_cont_tv; +static long out_enable_flag; +static long in_enable_flag; +static struct dentry *out_dentry; +static struct dentry *in_dentry; +static int in_cont_index; +/*This var is used to keep track of first write done for cold output latency */ +static int out_cold_index; +static char *out_buffer; +static char *in_buffer; +static int audio_output_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_output_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(out_buffer, OUT_BUFFER_SIZE, "%ld,%ld,%ld,%ld,%ld,%ld,",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec, out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec, out_cont_tv.tv_sec, out_cont_tv.tv_usec); + return simple_read_from_buffer(buf, OUT_BUFFER_SIZE, ppos, + out_buffer, OUT_BUFFER_SIZE); +} +static ssize_t audio_output_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + + out_cold_index = 0; + + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &out_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_output_latency_debug_fops = { + .open = audio_output_latency_dbgfs_open, + .read = audio_output_latency_dbgfs_read, + .write = audio_output_latency_dbgfs_write +}; +static int audio_input_latency_dbgfs_open(struct inode *inode, + struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t audio_input_latency_dbgfs_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + snprintf(in_buffer, IN_BUFFER_SIZE, "%ld,%ld,",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + return simple_read_from_buffer(buf, IN_BUFFER_SIZE, ppos, + in_buffer, IN_BUFFER_SIZE); +} +static ssize_t audio_input_latency_dbgfs_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char *temp; + + if (count > 2*sizeof(char)) + return -EINVAL; + else + temp = kmalloc(2*sizeof(char), GFP_KERNEL); + if (temp) { + if (copy_from_user(temp, buf, 2*sizeof(char))) { + kfree(temp); + return -EFAULT; + } + if (!strict_strtol(temp, 10, &in_enable_flag)) { + kfree(temp); + return count; + } + kfree(temp); + } + return -EINVAL; +} +static const struct file_operations audio_input_latency_debug_fops = { + .open = audio_input_latency_dbgfs_open, + .read = audio_input_latency_dbgfs_read, + .write = audio_input_latency_dbgfs_write +}; + +static void config_debug_fs_write_cb(void) +{ + if (out_enable_flag) { + /* For first Write done log the time and reset + out_cold_index*/ + if (out_cold_index != 1) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld" + "sec %ld microsec\n",\ + out_cold_tv.tv_sec,\ + out_cold_tv.tv_usec); + out_cold_index = 1; + } + pr_debug("out_enable_flag %ld",\ + out_enable_flag); + } +} +static void config_debug_fs_read_cb(void) +{ + if (in_enable_flag) { + /* when in_cont_index == 7, DSP would be + * writing into the 8th 512 byte buffer and this + * timestamp is tapped here.Once done it then writes + * to 9th 512 byte buffer.These two buffers(8th, 9th) + * reach the test application in 5th iteration and that + * timestamp is tapped at user level. The difference + * of these two timestamps gives us the time between + * the time at which dsp started filling the sample + * required and when it reached the test application. + * Hence continuous input latency + */ + if (in_cont_index == 7) { + do_gettimeofday(&in_cont_tv); + pr_err("In_CONT:previous read buffer done" + "at %ld sec %ld microsec\n",\ + in_cont_tv.tv_sec, in_cont_tv.tv_usec); + } + in_cont_index++; + } +} + +static void config_debug_fs_reset_index(void) +{ + in_cont_index = 0; +} + +static void config_debug_fs_run(void) +{ + if (out_enable_flag) { + do_gettimeofday(&out_cold_tv); + pr_debug("COLD: apr_send_pkt at %ld sec %ld microsec\n",\ + out_cold_tv.tv_sec, out_cold_tv.tv_usec); + } +} + +static void config_debug_fs_write(struct audio_buffer *ab) +{ + if (out_enable_flag) { + char zero_pattern[2] = {0x00, 0x00}; + /* If First two byte is non zero and last two byte + is zero then it is warm output pattern */ + if ((strncmp(((char *)ab->data), zero_pattern, 2)) && + (!strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_warm_tv); + pr_debug("WARM:apr_send_pkt at" + "%ld sec %ld microsec\n", out_warm_tv.tv_sec,\ + out_warm_tv.tv_usec); + pr_debug("Warm Pattern Matched"); + } + /* If First two byte is zero and last two byte is + non zero then it is cont ouput pattern */ + else if ((!strncmp(((char *)ab->data), zero_pattern, 2)) + && (strncmp(((char *)ab->data + 2), zero_pattern, 2))) { + do_gettimeofday(&out_cont_tv); + pr_debug("CONT:apr_send_pkt at" + "%ld sec %ld microsec\n", out_cont_tv.tv_sec,\ + out_cont_tv.tv_usec); + pr_debug("Cont Pattern Matched"); + } + } +} +static void config_debug_fs_init(void) +{ + out_buffer = kmalloc(OUT_BUFFER_SIZE, GFP_KERNEL); + out_dentry = debugfs_create_file("audio_out_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_output_latency_debug_fops); + if (IS_ERR(out_dentry)) + pr_err("debugfs_create_file failed\n"); + in_buffer = kmalloc(IN_BUFFER_SIZE, GFP_KERNEL); + in_dentry = debugfs_create_file("audio_in_latency_measurement_node",\ + S_IFREG | S_IRUGO | S_IWUGO,\ + NULL, NULL, &audio_input_latency_debug_fops); + if (IS_ERR(in_dentry)) + pr_err("debugfs_create_file failed\n"); +} +#else +static void config_debug_fs_write(struct audio_buffer *ab) +{ + return; +} +static void config_debug_fs_run(void) +{ + return; +} +static void config_debug_fs_reset_index(void) +{ + return; +} +static void config_debug_fs_read_cb(void) +{ + return; +} +static void config_debug_fs_write_cb(void) +{ + return; +} +static void config_debug_fs_init(void) +{ + return; +} +#endif + + +static int q6asm_session_alloc(struct audio_client *ac) +{ + int n; + mutex_lock(&session_lock); + for (n = 1; n <= SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void q6asm_session_free(struct audio_client *ac) +{ + pr_debug("%s: sessionid[%d]\n", __func__, ac->session); + rtac_remove_popp_from_adm_devices(ac->session); + mutex_lock(&session_lock); + session[ac->session] = 0; + mutex_unlock(&session_lock); + ac->session = 0; + return; +} + +int q6asm_audio_client_buf_free(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap_regions(ac, dir, + port->buf[0].size, + port->max_buf_cnt); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + while (cnt >= 0) { + if (port->buf[cnt].data) { + ion_unmap_kernel(port->buf[cnt].client, + port->buf[cnt].handle); + ion_free(port->buf[cnt].client, + port->buf[cnt].handle); + ion_client_destroy(port->buf[cnt].client); + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + --(port->max_buf_cnt); + } + --cnt; + } + kfree(port->buf); + port->buf = NULL; + } + mutex_unlock(&ac->cmd_lock); + return 0; +} + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap(ac, port->buf[0].phys, dir); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + if (port->buf[0].data) { + ion_unmap_kernel(port->buf[0].client, port->buf[0].handle); + ion_free(port->buf[0].client, port->buf[0].handle); + ion_client_destroy(port->buf[0].client); + pr_debug("%s:data[%p]phys[%p][%p]" + ", client[%p] handle[%p]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, + (void *)port->buf[0].client, + (void *)port->buf[0].handle); + } + + while (cnt >= 0) { + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + cnt--; + } + port->max_buf_cnt = 0; + kfree(port->buf); + port->buf = NULL; + mutex_unlock(&ac->cmd_lock); + return 0; +} + +int q6asm_mmap_apr_dereg(void) +{ + if (atomic_read(&this_mmap.ref_cnt) <= 0) { + pr_err("%s: APR Common Port Already Closed\n", __func__); + goto done; + } + atomic_dec(&this_mmap.ref_cnt); + if (atomic_read(&this_mmap.ref_cnt) == 0) { + apr_deregister(this_mmap.apr); + pr_debug("%s:APR De-Register common port\n", __func__); + } +done: + return 0; +} + + +void q6asm_audio_client_free(struct audio_client *ac) +{ + int loopcnt; + struct audio_port_data *port; + if (!ac || !ac->session) + return; + pr_debug("%s: Session id %d\n", __func__, ac->session); + if (ac->io_mode == SYNC_IO_MODE) { + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + if (!port->buf) + continue; + pr_debug("%s:loopcnt = %d\n", __func__, loopcnt); + q6asm_audio_client_buf_free(loopcnt, ac); + } + } + + apr_deregister(ac->apr); + ac->mmap_apr = NULL; + q6asm_session_free(ac); + q6asm_mmap_apr_dereg(); + + pr_debug("%s: APR De-Register\n", __func__); + +/*done:*/ + kfree(ac); + return; +} + +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode) +{ + if (ac == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + if ((mode == ASYNC_IO_MODE) || (mode == SYNC_IO_MODE)) { + ac->io_mode = mode; + pr_debug("%s:Set Mode to %d\n", __func__, ac->io_mode); + return 0; + } else { + pr_err("%s:Not an valid IO Mode:%d\n", __func__, ac->io_mode); + return -EINVAL; + } +} + +void *q6asm_mmap_apr_reg(void) +{ + if (atomic_read(&this_mmap.ref_cnt) == 0) { + this_mmap.apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_mmapcallback,\ + 0x0FFFFFFFF, &this_mmap); + if (this_mmap.apr == NULL) { + pr_debug("%s Unable to register" + "APR ASM common port\n", __func__); + goto fail; + } + } + atomic_inc(&this_mmap.ref_cnt); + return this_mmap.apr; +fail: + return NULL; +} + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv) +{ + struct audio_client *ac; + int n; + int lcnt = 0; + + ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL); + if (!ac) + return NULL; + n = q6asm_session_alloc(ac); + if (n <= 0) + goto fail_session; + ac->session = n; + ac->cb = cb; + ac->priv = priv; + ac->io_mode = SYNC_IO_MODE; + ac->apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_callback,\ + ((ac->session) << 8 | 0x0001),\ + ac); + + if (ac->apr == NULL) { + pr_err("%s Registration with APR failed\n", __func__); + goto fail; + } + rtac_set_asm_handle(n, ac->apr); + + pr_debug("%s Registering the common port with APR\n", __func__); + ac->mmap_apr = q6asm_mmap_apr_reg(); + if (ac->mmap_apr == NULL) + goto fail; + + init_waitqueue_head(&ac->cmd_wait); + INIT_LIST_HEAD(&ac->port[0].mem_map_handle); + INIT_LIST_HEAD(&ac->port[1].mem_map_handle); + pr_debug("%s: mem_map_handle list init'ed\n", __func__); + mutex_init(&ac->cmd_lock); + for (lcnt = 0; lcnt <= OUT; lcnt++) { + mutex_init(&ac->port[lcnt].lock); + spin_lock_init(&ac->port[lcnt].dsp_lock); + } + atomic_set(&ac->cmd_state, 0); + + pr_debug("%s: session[%d]\n", __func__, ac->session); + + return ac; +fail: + q6asm_audio_client_free(ac); + return NULL; +fail_session: + kfree(ac); + return NULL; +} + +struct audio_client *q6asm_get_audio_client(int session_id) +{ + if ((session_id <= 0) || (session_id > SESSION_MAX)) { + pr_err("%s: invalid session: %d\n", __func__, session_id); + goto err; + } + + if (!session[session_id]) { + pr_err("%s: session not active: %d\n", __func__, session_id); + goto err; + } + + return session[session_id]; +err: + return NULL; +} + +int q6asm_audio_client_buf_alloc(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; + int len; + + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->io_mode == SYNC_IO_MODE) { + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + + while (cnt < bufcnt) { + if (bufsz > 0) { + if (!buf[cnt].data) { + buf[cnt].client = msm_ion_client_create + (UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *) + buf[cnt].client)) { + pr_err("%s: ION create client" + " for AUDIO failed\n", + __func__); + goto fail; + } + buf[cnt].handle = ion_alloc + (buf[cnt].client, bufsz, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) + buf[cnt].handle)) { + pr_err("%s: ION memory" + " allocation for AUDIO failed\n", + __func__); + goto fail; + } + + rc = ion_phys(buf[cnt].client, + buf[cnt].handle, + (ion_phys_addr_t *) + &buf[cnt].phys, + (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical" + " for AUDIO failed, rc = %d\n", + __func__, rc); + goto fail; + } + + buf[cnt].data = ion_map_kernel + (buf[cnt].client, buf[cnt].handle, + 0); + if (IS_ERR_OR_NULL((void *) + buf[cnt].data)) { + pr_err("%s: ION memory" + " mapping for AUDIO failed\n", __func__); + goto fail; + } + memset((void *)buf[cnt].data, 0, bufsz); + buf[cnt].used = 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]\n", + __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys); + cnt++; + } + } + } + ac->port[dir].max_buf_cnt = cnt; + + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map_regions(ac, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + } + return 0; +fail: + q6asm_audio_client_buf_free(dir, ac); + return -EINVAL; +} + +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; + int len; + + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", + __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + + buf[0].client = msm_ion_client_create(UINT_MAX, "audio_client"); + if (IS_ERR_OR_NULL((void *)buf[0].client)) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + goto fail; + } + buf[0].handle = ion_alloc(buf[0].client, bufsz * bufcnt, SZ_4K, + (0x1 << ION_AUDIO_HEAP_ID)); + if (IS_ERR_OR_NULL((void *) buf[0].handle)) { + pr_err("%s: ION memory allocation for AUDIO failed\n", + __func__); + goto fail; + } + + rc = ion_phys(buf[0].client, buf[0].handle, + (ion_phys_addr_t *)&buf[0].phys, (size_t *)&len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto fail; + } + + buf[0].data = ion_map_kernel(buf[0].client, buf[0].handle, 0); + if (IS_ERR_OR_NULL((void *) buf[0].data)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + goto fail; + } + memset((void *)buf[0].data, 0, (bufsz * bufcnt)); + if (!buf[0].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + buf[0].used = dir ^ 1; + buf[0].size = bufsz; + buf[0].actual_size = bufsz; + cnt = 1; + while (cnt < bufcnt) { + if (bufsz > 0) { + buf[cnt].data = buf[0].data + (cnt * bufsz); + buf[cnt].phys = buf[0].phys + (cnt * bufsz); + if (!buf[cnt].data) { + pr_err("%s Buf alloc failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].used = dir ^ 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]\n", __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys); + } + cnt++; + } + ac->port[dir].max_buf_cnt = cnt; + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map_regions(ac, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + return 0; +fail: + q6asm_audio_client_buf_free_contiguous(dir, ac); + return -EINVAL; +} + +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv) +{ + uint32_t sid = 0; + uint32_t dir = 0; + uint32_t *payload = data->payload; + unsigned long dsp_flags; + + struct audio_client *ac = NULL; + struct audio_port_data *port; + + if (!data) { + pr_err("%s: Invalid CB\n", __func__); + return 0; + } + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event is received: %d %d apr[%p]\n", + __func__, + data->reset_event, + data->reset_proc, + this_mmap.apr); + apr_reset(this_mmap.apr); + atomic_set(&this_mmap.ref_cnt, 0); + this_mmap.apr = NULL; + return 0; + } + sid = (data->token >> 8) & 0x0F; + ac = q6asm_get_audio_client(sid); + pr_debug("%s:ptr0[0x%x]ptr1[0x%x]opcode[0x%x]" + "token[0x%x]payload_s[%d] src[%d] dest[%d]sid[%d]dir[%d]\n", + __func__, payload[0], payload[1], data->opcode, data->token, + data->payload_size, data->src_port, data->dest_port, sid, dir); + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + return 0; + } + + dir = (data->token & 0x0F); + port = &ac->port[dir]; + + switch (data->opcode) { + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS:{ + pr_debug("%s:PL#0[0x%x]PL#1 [0x%x] dir=%x s_id=%x\n", + __func__, payload[0], payload[1], dir, sid); + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (atomic_read(&ac->cmd_state)) { + ac->port[dir].tmp_hdl = payload[0]; + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + break; + } + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:{ + pr_debug("%s:PL#0[0x%x]PL#1 [0x%x]\n", + __func__, payload[0], payload[1]); + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + + break; + } + default: + pr_debug("%s:command[0x%x]success [0x%x]\n", + __func__, payload[0], payload[1]); + } + if (ac->cb) + ac->cb(data->opcode, data->token, + data->payload, ac->priv); + return 0; +} + + +static int32_t q6asm_callback(struct apr_client_data *data, void *priv) +{ + int i = 0; + struct audio_client *ac = (struct audio_client *)priv; + uint32_t token; + unsigned long dsp_flags; + uint32_t *payload; + + + if ((ac == NULL) || (data == NULL)) { + pr_err("ac or priv NULL\n"); + return -EINVAL; + } + if (ac->session <= 0 || ac->session > 8) { + pr_err("%s:Session ID is invalid, session = %d\n", __func__, + ac->session); + return -EINVAL; + } + + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("q6asm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, ac->apr); + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + apr_reset(ac->apr); + return 0; + } + + pr_debug("%s: session[%d]opcode[0x%x]" + "token[0x%x]payload_s[%d] src[%d] dest[%d]\n", __func__, + ac->session, data->opcode, + data->token, data->payload_size, data->src_port, + data->dest_port); + if ((data->opcode != ASM_DATA_EVENT_RENDERED_EOS) && + (data->opcode != ASM_DATA_EVENT_EOS)) + pr_debug("%s:Payload = [0x%x] status[0x%x]\n", + __func__, payload[0], payload[1]); + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_STREAM_CMD_SET_PP_PARAMS_V2: + if (rtac_make_asm_callback(ac->session, payload, + data->payload_size)) + break; + case ASM_SESSION_CMD_PAUSE: + case ASM_DATA_CMD_EOS: + case ASM_STREAM_CMD_CLOSE: + case ASM_STREAM_CMD_FLUSH: + case ASM_SESSION_CMD_RUN_V2: + case ASM_SESSION_CMD_REGISTER_FORX_OVERFLOW_EVENTS: + case ASM_STREAM_CMD_FLUSH_READBUFS: + pr_debug("%s:Payload = [0x%x]\n", __func__, payload[0]); + if (token != ac->session) { + pr_err("%s:Invalid session[%d] rxed expected[%d]", + __func__, token, ac->session); + return -EINVAL; + } + case ASM_STREAM_CMD_OPEN_READ_V2: + case ASM_STREAM_CMD_OPEN_WRITE_V2: + case ASM_STREAM_CMD_OPEN_READWRITE_V2: + case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + pr_debug("%s:Payload = [0x%x]stat[0x%x]\n", + __func__, payload[0], payload[1]); + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2:{ + struct audio_port_data *port = &ac->port[IN]; + pr_debug("%s: Rxed opcode[0x%x] status[0x%x] token[%d]", + __func__, payload[0], payload[1], + data->token); + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", + __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (port->buf[data->token].phys != + payload[0]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[data->token].phys,\ + (void *)payload[0]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + return -EINVAL; + } + token = data->token; + port->buf[token].used = 1; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + + config_debug_fs_write_cb(); + + for (i = 0; i < port->max_buf_cnt; i++) + pr_debug("%d ", port->buf[i].used); + + } + break; + } + case ASM_STREAM_CMDRSP_GET_PP_PARAMS_V2: + rtac_make_asm_callback(ac->session, payload, + data->payload_size); + break; + case ASM_DATA_EVENT_READ_DONE_V2:{ + + struct audio_port_data *port = &ac->port[OUT]; + + config_debug_fs_read_cb(); + + pr_debug("%s:R-D: status=%d buff_add=%x act_size=%d offset=%d\n", + __func__, payload[READDONE_IDX_STATUS], + payload[READDONE_IDX_BUFADD_LSW], + payload[READDONE_IDX_SIZE], + payload[READDONE_IDX_OFFSET]); + + pr_debug("%s:R-D:msw_ts=%d lsw_ts=%d memmap_hdl=%x flags=%d id=%d num=%d\n", + __func__, payload[READDONE_IDX_MSW_TS], + payload[READDONE_IDX_LSW_TS], + payload[READDONE_IDX_MEMMAP_HDL], + payload[READDONE_IDX_FLAGS], + payload[READDONE_IDX_SEQ_ID], + payload[READDONE_IDX_NUMFRAMES]); + + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + token = data->token; + port->buf[token].used = 0; + if (port->buf[token].phys != + payload[READDONE_IDX_BUFADD_LSW]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[token].phys,\ + (void *)payload[READDONE_IDX_BUFADD_LSW]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + break; + } + port->buf[token].actual_size = + payload[READDONE_IDX_SIZE]; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + } + break; + } + case ASM_DATA_EVENT_EOS: + case ASM_DATA_EVENT_RENDERED_EOS: + pr_debug("%s:EOS ACK received: rxed opcode[0x%x]\n", + __func__, data->opcode); + break; + case ASM_SESSION_EVENTX_OVERFLOW: + pr_err("ASM_SESSION_EVENTX_OVERFLOW\n"); + break; + case ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3: + pr_debug("%s: ASM_SESSION_CMDRSP_GET_SESSIONTIME_V3, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d\n", __func__, + payload[0], payload[1], payload[2]); + ac->time_stamp = (uint64_t)(((uint64_t)payload[1] << 32) | + payload[2]); + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + break; + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + case ASM_DATA_EVENT_ENC_SR_CM_CHANGE_NOTIFY: + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + payload[0], payload[1], payload[2], + payload[3]); + break; + } + if (ac->cb) + ac->cb(data->opcode, data->token, + data->payload, ac->priv); + + return 0; +} + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, uint32_t *size, + uint32_t *index) +{ + void *data; + unsigned char idx; + struct audio_port_data *port; + + if (!ac || ((dir != IN) && (dir != OUT))) + return NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->cpu_buf; + if (port->buf == NULL) { + pr_debug("%s:Buffer pointer null\n", __func__); + mutex_unlock(&port->lock); + return NULL; + } + /* dir 0: used = 0 means buf in use + dir 1: used = 1 means buf in use */ + if (port->buf[idx].used == dir) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_debug("%s:Next buf idx[0x%x] not available," + "dir[%d]\n", __func__, idx, dir); + mutex_unlock(&port->lock); + return NULL; + } + *size = port->buf[idx].actual_size; + *index = port->cpu_buf; + data = port->buf[idx].data; + pr_debug("%s:session[%d]index[%d] data[%p]size[%d]\n", + __func__, + ac->session, + port->cpu_buf, + data, *size); + /* By default increase the cpu_buf cnt + user accesses this function,increase cpu + buf(to avoid another api)*/ + port->buf[idx].used = dir; + port->cpu_buf = ((port->cpu_buf + 1) & (port->max_buf_cnt - 1)); + mutex_unlock(&port->lock); + return data; + } + return NULL; +} + +void *q6asm_is_cpu_buf_avail_nolock(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *index) +{ + void *data; + unsigned char idx; + struct audio_port_data *port; + + if (!ac || ((dir != IN) && (dir != OUT))) + return NULL; + + port = &ac->port[dir]; + + idx = port->cpu_buf; + if (port->buf == NULL) { + pr_debug("%s:Buffer pointer null\n", __func__); + return NULL; + } + /* + * dir 0: used = 0 means buf in use + * dir 1: used = 1 means buf in use + */ + if (port->buf[idx].used == dir) { + /* + * To make it more robust, we could loop and get the + * next avail buf, its risky though + */ + pr_debug("%s:Next buf idx[0x%x] not available," + "dir[%d]\n", __func__, idx, dir); + return NULL; + } + *size = port->buf[idx].actual_size; + *index = port->cpu_buf; + data = port->buf[idx].data; + pr_debug("%s:session[%d]index[%d] data[%p]size[%d]\n", + __func__, ac->session, port->cpu_buf, + data, *size); + /* + * By default increase the cpu_buf cnt + * user accesses this function,increase cpu + * buf(to avoid another api) + */ + port->buf[idx].used = dir; + port->cpu_buf = ((port->cpu_buf + 1) & (port->max_buf_cnt - 1)); + return data; +} + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac) +{ + int ret = -1; + struct audio_port_data *port; + uint32_t idx; + + if (!ac || (dir != OUT)) + return ret; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->dsp_buf; + + if (port->buf[idx].used == (dir ^ 1)) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_err("Next buf idx[0x%x] not available, dir[%d]\n", + idx, dir); + mutex_unlock(&port->lock); + return ret; + } + pr_debug("%s: session[%d]dsp_buf=%d cpu_buf=%d\n", __func__, + ac->session, port->dsp_buf, port->cpu_buf); + ret = ((port->dsp_buf != port->cpu_buf) ? 0 : -1); + mutex_unlock(&port->lock); + } + return ret; +} + +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("%s:pkt_size=%d cmd_flg=%d session=%d\n", __func__, pkt_size, + cmd_flg, ac->session); + mutex_lock(&ac->cmd_lock); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + mutex_unlock(&ac->cmd_lock); + return; +} + +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("pkt_size = %d, cmd_flg = %d, session = %d\n", + pkt_size, cmd_flg, ac->session); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} + +static void q6asm_add_mmaphdr(struct audio_client *ac, struct apr_hdr *hdr, + u32 pkt_size, u32 cmd_flg, u32 token) +{ + pr_debug("%s:pkt size=%d cmd_flg=%d\n", __func__, pkt_size, cmd_flg); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr->src_port = 0; + hdr->dest_port = 0; + if (cmd_flg) { + hdr->token = token; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} +int q6asm_open_read(struct audio_client *ac, + uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_read_v2 open; + + uint16_t bits_per_sample = 16; + + + config_debug_fs_reset_index(); + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s:session[%d]", __func__, ac->session); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READ_V2; + /* Stream prio : High, provide meta info with encoded frames */ + open.src_endpointype = ASM_END_POINT_DEVICE_MATRIX; + + open.preprocopo_id = get_asm_topology(); + if (open.preprocopo_id == 0) + open.preprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + open.bits_per_sample = bits_per_sample; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.mode_flags = 0x00; + open.enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_V13K: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.mode_flags = BUFFER_META_ENABLE; + open.enc_cfg_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_AMRNB: + open.mode_flags = BUFFER_META_ENABLE ; + open.enc_cfg_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.mode_flags = BUFFER_META_ENABLE ; + open.enc_cfg_id = ASM_MEDIA_FMT_AMRWB_FS; + break; + default: + pr_err("Invalid format[%d]\n", format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for open read rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} +int q6asm_open_write(struct audio_client *ac, uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_write_v2 open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s: session[%d] wr_format[0x%x]", __func__, ac->session, + format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V2; + open.mode_flags = 0x00; + /* source endpoint : matrix */ + open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open.bits_per_sample = 16; + + open.postprocopo_id = get_asm_topology(); + if (open.postprocopo_id == 0) + open.postprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_DOLBY_AAC; + break; + case FORMAT_WMA_V9: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V9_V2; + break; + case FORMAT_WMA_V10PRO: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V10PRO_V2; + break; + case FORMAT_MP3: + open.dec_fmt_id = ASM_MEDIA_FMT_MP3; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("%s: open failed op[0x%x]rc[%d]\n", \ + __func__, open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for open write rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_readwrite_v2 open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d]", __func__, ac->session); + pr_debug("wr_format[0x%x]rd_format[0x%x]", + wr_format, rd_format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READWRITE_V2; + + open.mode_flags = BUFFER_META_ENABLE; + open.bits_per_sample = 16; + /* source endpoint : matrix */ + open.postprocopo_id = get_asm_topology(); + if (open.postprocopo_id == 0) + open.postprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_DEFAULT; + + switch (wr_format) { + case FORMAT_LINEAR_PCM: + open.dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_MPEG4_MULTI_AAC: + open.dec_fmt_id = ASM_MEDIA_FMT_DOLBY_AAC; + break; + case FORMAT_WMA_V9: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V9_V2; + break; + case FORMAT_WMA_V10PRO: + open.dec_fmt_id = ASM_MEDIA_FMT_WMA_V10PRO_V2; + break; + case FORMAT_AMRNB: + open.dec_fmt_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.dec_fmt_id = ASM_MEDIA_FMT_AMRWB_FS; + break; + case FORMAT_V13K: + open.dec_fmt_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_EVRCB: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRCB_FS; + break; + case FORMAT_EVRCWB: + open.dec_fmt_id = ASM_MEDIA_FMT_EVRCWB_FS; + break; + case FORMAT_MP3: + open.dec_fmt_id = ASM_MEDIA_FMT_MP3; + break; + default: + pr_err("Invalid format[%d]\n", wr_format); + goto fail_cmd; + } + + switch (rd_format) { + case FORMAT_LINEAR_PCM: + open.enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case FORMAT_MPEG4_AAC: + open.enc_cfg_id = ASM_MEDIA_FMT_AAC_V2; + break; + case FORMAT_V13K: + open.enc_cfg_id = ASM_MEDIA_FMT_V13K_FS; + break; + case FORMAT_EVRC: + open.enc_cfg_id = ASM_MEDIA_FMT_EVRC_FS; + break; + case FORMAT_AMRNB: + open.enc_cfg_id = ASM_MEDIA_FMT_AMRNB_FS; + break; + case FORMAT_AMRWB: + open.enc_cfg_id = ASM_MEDIA_FMT_AMRWB_FS; + break; + default: + pr_err("Invalid format[%d]\n", rd_format); + goto fail_cmd; + } + pr_debug("%s:rdformat[0x%x]wrformat[0x%x]\n", __func__, + open.enc_cfg_id, open.dec_fmt_id); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for open read-write rc[%d]\n", rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_session_cmd_run_v2 run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s session[%d]", __func__, ac->session); + q6asm_add_hdr(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_ts; + + config_debug_fs_run(); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("Commmand run failed[%d]", rc); + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for run success rc[%d]", rc); + goto fail_cmd; + } + + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_session_cmd_run_v2 run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("session[%d]", ac->session); + q6asm_add_hdr_async(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_ts; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("%s:Commmand run failed[%d]", __func__, rc); + return -EINVAL; + } + return 0; +} + + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, uint32_t mode, uint32_t format) +{ + struct asm_aac_enc_cfg_v2 enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]SR[%d]ch[%d]bitrate[%d]mode[%d]" + "format[%d]", __func__, ac->session, frames_per_buf, + sample_rate, channels, bit_rate, mode, format); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_aac_enc_cfg_v2) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + enc_cfg.bit_rate = bit_rate; + enc_cfg.enc_mode = mode; + enc_cfg.aac_fmt_flag = format; + enc_cfg.channel_cfg = channels; + enc_cfg.sample_rate = sample_rate; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_set_encdec_chan_map(struct audio_client *ac, + uint32_t num_channels) +{ + /* Todo: */ + return 0; +} + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_multi_channel_pcm_enc_cfg_v2 enc_cfg; + u8 *channel_mapping; + u32 frames_per_buf = 0; + + int rc = 0; + + pr_debug("%s: Session %d, rate = %d, channels = %d\n", __func__, + ac->session, rate, channels); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(enc_cfg) - sizeof(enc_cfg.hdr) - + sizeof(enc_cfg.encdec); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.num_channels = channels; + enc_cfg.bits_per_sample = 16; + enc_cfg.sample_rate = rate; + enc_cfg.is_signed = 1; + channel_mapping = enc_cfg.channel_mapping; /* ??? PHANI */ + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd open failed\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", enc_cfg.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps_enable) +{ + struct asm_aac_sbr_ps_flag_param sbrps; + u32 frames_per_buf = 0; + + int rc = 0; + + pr_debug("%s: Session %d\n", __func__, ac->session); + + q6asm_add_hdr(ac, &sbrps.hdr, sizeof(sbrps), TRUE); + + sbrps.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + sbrps.encdec.param_id = ASM_PARAM_ID_AAC_SBR_PS_FLAG; + sbrps.encdec.param_size = sizeof(struct asm_aac_sbr_ps_flag_param) - + sizeof(struct asm_stream_cmd_set_encdec_param); + sbrps.encblk.frames_per_buf = frames_per_buf; + sbrps.encblk.enc_cfg_blk_size = sbrps.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + sbrps.sbr_ps_flag = sbr_ps_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &sbrps); + if (rc < 0) { + pr_err("Command opcode[0x%x]paramid[0x%x] failed\n", + ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_PARAM_ID_AAC_SBR_PS_FLAG); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", sbrps.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_cfg_dual_mono_aac(struct audio_client *ac, + uint16_t sce_left, uint16_t sce_right) +{ + struct asm_aac_dual_mono_mapping_param dual_mono; + u32 frames_per_buf = 0; + + int rc = 0; + + pr_debug("%s: Session %d, sce_left = %d, sce_right = %d\n", + __func__, ac->session, sce_left, sce_right); + + q6asm_add_hdr(ac, &dual_mono.hdr, sizeof(dual_mono), TRUE); + + dual_mono.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + dual_mono.encdec.param_id = ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING; + dual_mono.encdec.param_size = sizeof(struct asm_aac_enc_cfg_v2) - + sizeof(struct asm_stream_cmd_set_encdec_param); + dual_mono.encblk.frames_per_buf = frames_per_buf; + dual_mono.encblk.enc_cfg_blk_size = dual_mono.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + dual_mono.left_channel_sce = sce_left; + dual_mono.right_channel_sce = sce_right; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &dual_mono); + if (rc < 0) { + pr_err("%s:Command opcode[0x%x]paramid[0x%x] failed\n", + __func__, ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_PARAM_ID_AAC_DUAL_MONO_MAPPING); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout opcode[0x%x]\n", __func__, + dual_mono.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd) +{ + struct asm_v13k_enc_cfg enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x]" + "reduced_rate_level[0x%4x]rate_modulation_cmd[0x%4x]", __func__, + ac->session, frames_per_buf, min_rate, max_rate, + reduced_rate_level, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_v13k_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.min_rate = min_rate; + enc_cfg.max_rate = max_rate; + enc_cfg.reduced_rate_cmd = reduced_rate_level; + enc_cfg.rate_mod_cmd = rate_modulation_cmd; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for setencdec v13k resp\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd) +{ + struct asm_evrc_enc_cfg enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x]" + "rate_modulation_cmd[0x%4x]", __func__, ac->session, + frames_per_buf, min_rate, max_rate, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_evrc_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.min_rate = min_rate; + enc_cfg.max_rate = max_rate; + enc_cfg.rate_mod_cmd = rate_modulation_cmd; + enc_cfg.reserved = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for encdec evrc\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_amrnb_enc_cfg enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]band_mode[0x%4x]dtx_enable[0x%4x]", + __func__, ac->session, frames_per_buf, band_mode, dtx_enable); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_amrnb_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.enc_mode = band_mode; + enc_cfg.dtx_mode = dtx_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for set encdec amrnb\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrwb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_amrwb_enc_cfg enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]band_mode[0x%4x]dtx_enable[0x%4x]", + __func__, ac->session, frames_per_buf, band_mode, dtx_enable); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg.encdec.param_size = sizeof(struct asm_amrwb_enc_cfg) - + sizeof(struct asm_stream_cmd_set_encdec_param); + enc_cfg.encblk.frames_per_buf = frames_per_buf; + enc_cfg.encblk.enc_cfg_blk_size = enc_cfg.encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg.enc_mode = band_mode; + enc_cfg.dtx_mode = dtx_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + return q6asm_media_format_block_multi_aac(ac, cfg); +} + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 fmt; + u8 *channel_mapping; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, rate, + channels); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt.fmt_blk.fmt_blk_size = sizeof(fmt) - sizeof(fmt.hdr) - + sizeof(fmt.fmt_blk); + fmt.num_channels = channels; + fmt.bits_per_sample = 16; + fmt.sample_rate = rate; + fmt.is_signed = 1; + + channel_mapping = fmt.channel_mapping; + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (channels == 1) { + channel_mapping[0] = PCM_CHANNEL_FL; + } else if (channels == 2) { + channel_mapping[0] = PCM_CHANNEL_FL; + channel_mapping[1] = PCM_CHANNEL_FR; + } else if (channels == 6) { + channel_mapping[0] = PCM_CHANNEL_FC; + channel_mapping[1] = PCM_CHANNEL_FL; + channel_mapping[2] = PCM_CHANNEL_FR; + channel_mapping[3] = PCM_CHANNEL_LB; + channel_mapping[4] = PCM_CHANNEL_RB; + channel_mapping[5] = PCM_CHANNEL_LFE; + } else { + pr_err("%s: ERROR.unsupported num_ch = %u\n", __func__, + channels); + return -EINVAL; + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for format update\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_multi_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + struct asm_aac_fmt_blk_v2 fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, + cfg->sample_rate, cfg->ch_cfg); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt.fmt_blk.fmt_blk_size = sizeof(fmt) - sizeof(fmt.hdr) - + sizeof(fmt.fmt_blk); + fmt.aac_fmt_flag = cfg->format; + fmt.audio_objype = cfg->aot; + /* If zero, PCE is assumed to be available in bitstream*/ + fmt.total_size_of_PCE_bits = 0; + fmt.channel_config = cfg->ch_cfg; + fmt.sample_rate = cfg->sample_rate; + + pr_info("%s:format=%x cfg_size=%d aac-cfg=%x aot=%d ch=%d sr=%d\n", + __func__, fmt.aac_fmt_flag, fmt.fmt_blk.fmt_blk_size, + fmt.aac_fmt_flag, + fmt.audio_objype, + fmt.channel_config, + fmt.sample_rate); + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg) +{ + struct asm_wmastdv9_fmt_blk_v2 fmt; + struct asm_wma_cfg *wma_cfg = (struct asm_wma_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d]," + "balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x]\n", + ac->session, wma_cfg->format_tag, wma_cfg->sample_rate, + wma_cfg->ch_cfg, wma_cfg->avg_bytes_per_sec, + wma_cfg->block_align, wma_cfg->valid_bits_per_sample, + wma_cfg->ch_mask, wma_cfg->encode_opt); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_MEDIA_FMT_WMA_V9_V2; + + fmt.fmtag = wma_cfg->format_tag; + fmt.num_channels = wma_cfg->ch_cfg; + fmt.sample_rate = wma_cfg->sample_rate; + fmt.avg_bytes_per_sec = wma_cfg->avg_bytes_per_sec; + fmt.blk_align = wma_cfg->block_align; + fmt.bits_per_sample = + wma_cfg->valid_bits_per_sample; + fmt.channel_mask = wma_cfg->ch_mask; + fmt.enc_options = wma_cfg->encode_opt; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg) +{ + struct asm_wmaprov10_fmt_blk_v2 fmt; + struct asm_wmapro_cfg *wmapro_cfg = (struct asm_wmapro_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d]," + "balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x]," + "adv_enc_opt[0x%4x], adv_enc_opt2[0x%8x]\n", + ac->session, wmapro_cfg->format_tag, wmapro_cfg->sample_rate, + wmapro_cfg->ch_cfg, wmapro_cfg->avg_bytes_per_sec, + wmapro_cfg->block_align, wmapro_cfg->valid_bits_per_sample, + wmapro_cfg->ch_mask, wmapro_cfg->encode_opt, + wmapro_cfg->adv_encode_opt, wmapro_cfg->adv_encode_opt2); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_MEDIA_FMT_WMA_V10PRO_V2; + + fmt.fmtag = wmapro_cfg->format_tag; + fmt.num_channels = wmapro_cfg->ch_cfg; + fmt.sample_rate = wmapro_cfg->sample_rate; + fmt.avg_bytes_per_sec = + wmapro_cfg->avg_bytes_per_sec; + fmt.blk_align = wmapro_cfg->block_align; + fmt.bits_per_sample = wmapro_cfg->valid_bits_per_sample; + fmt.channel_mask = wmapro_cfg->ch_mask; + fmt.enc_options = wmapro_cfg->encode_opt; + fmt.usAdvancedEncodeOpt = wmapro_cfg->adv_encode_opt; + fmt.advanced_enc_options2 = wmapro_cfg->adv_encode_opt2; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + struct asm_buffer_node *buffer_node = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + buffer_node = kmalloc(sizeof(struct asm_buffer_node), GFP_KERNEL); + if (!buffer_node) + return -ENOMEM; + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + sizeof(struct avs_shared_map_region_payload) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (mmap_region_cmd == NULL) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + mmap_regions = (struct avs_cmd_shared_mem_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, + TRUE, ((ac->session << 8) | dir)); + mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mmap_regions->num_regions = bufcnt & 0x00ff; + mmap_regions->property_flag = 0x00; + pr_debug("map_regions->nregions = %d\n", mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + ac->port[dir].tmp_hdl = 0; + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->shm_addr_lsw = ab->phys; + /* Using only 32 bit address */ + mregions->shm_addr_msw = 0; + mregions->mem_size_bytes = ab->size; + ++mregions; + } + + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) mmap_region_cmd); + if (rc < 0) { + pr_err("mmap op[0x%x]rc[%d]\n", + mmap_regions->hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0 && + ac->port[dir].tmp_hdl), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + buffer_node->buf_addr_lsw = buf_add; + buffer_node->mmap_hdl = ac->port[dir].tmp_hdl; + list_add_tail(&buffer_node->list, &ac->port[dir].mem_map_handle); + ac->port[dir].tmp_hdl = 0; + rc = 0; + +fail_cmd: + kfree(mmap_region_cmd); + return rc; +} + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, int dir) +{ + struct avs_cmd_shared_mem_unmap_regions mem_unmap; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + + int rc = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + q6asm_add_mmaphdr(ac, &mem_unmap.hdr, + sizeof(struct avs_cmd_shared_mem_unmap_regions), + TRUE, ((ac->session << 8) | dir)); + + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + pr_info("%s: Found the element\n", __func__); + mem_unmap.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + pr_debug("%s: mem_unmap-mem_map_handle: 0x%x", + __func__, mem_unmap.mem_map_handle); + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) &mem_unmap); + if (rc < 0) { + pr_err("mem_unmap op[0x%x]rc[%d]\n", + mem_unmap.hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + list_del(&buf_node->list); + kfree(buf_node); + } + } + + rc = 0; +fail_cmd: + return rc; +} + + +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + struct asm_buffer_node *buffer_node = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct avs_cmd_shared_mem_map_regions) + + (sizeof(struct avs_shared_map_region_payload)); + + buffer_node = kzalloc(sizeof(struct asm_buffer_node) * bufcnt, + GFP_KERNEL); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if ((mmap_region_cmd == NULL) || (buffer_node == NULL)) { + pr_err("%s: Mem alloc failed\n", __func__); + rc = -EINVAL; + return rc; + } + mmap_regions = (struct avs_cmd_shared_mem_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, TRUE, + ((ac->session << 8) | dir)); + pr_debug("mmap_region=0x%p token=0x%x\n", + mmap_regions, ((ac->session << 8) | dir)); + + mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_EBI_POOL; + mmap_regions->num_regions = 1; /*bufcnt & 0x00ff; */ + mmap_regions->property_flag = 0x00; + pr_debug("map_regions->nregions = %d\n", mmap_regions->num_regions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + mregions = (struct avs_shared_map_region_payload *)payload; + + ac->port[dir].tmp_hdl = 0; + port = &ac->port[dir]; + ab = &port->buf[0]; + mregions->shm_addr_lsw = ab->phys; + /* Using only 32 bit address */ + mregions->shm_addr_msw = 0; + mregions->mem_size_bytes = (bufsz * bufcnt); + + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) mmap_region_cmd); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + mmap_regions->hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0) + , 5*HZ); + /*ac->port[dir].tmp_hdl), 5*HZ);*/ + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + mutex_lock(&ac->cmd_lock); + + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + buffer_node[i].buf_addr_lsw = ab->phys; + buffer_node[i].mmap_hdl = ac->port[dir].tmp_hdl; + list_add_tail(&buffer_node[i].list, + &ac->port[dir].mem_map_handle); + pr_debug("%s: i=%d, bufadd[i] = 0x%x, maphdl[i] = 0x%x\n", + __func__, i, buffer_node[i].buf_addr_lsw, + buffer_node[i].mmap_hdl); + } + ac->port[dir].tmp_hdl = 0; + mutex_unlock(&ac->cmd_lock); + rc = 0; + pr_debug("%s: exit\n", __func__); +fail_cmd: + kfree(mmap_region_cmd); + return rc; +} + +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct avs_cmd_shared_mem_unmap_regions mem_unmap; + struct audio_port_data *port = NULL; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + uint32_t buf_add; + int rc = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || ac->mmap_apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct avs_cmd_shared_mem_unmap_regions); + q6asm_add_mmaphdr(ac, &mem_unmap.hdr, cmd_size, + TRUE, ((ac->session << 8) | dir)); + port = &ac->port[dir]; + buf_add = (uint32_t)port->buf->phys; + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + pr_debug("%s: Found the element\n", __func__); + mem_unmap.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + + pr_debug("%s: mem_unmap-mem_map_handle: 0x%x", + __func__, mem_unmap.mem_map_handle); + rc = apr_send_pkt(ac->mmap_apr, (uint32_t *) &mem_unmap); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + mem_unmap.hdr.opcode, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_unmap\n"); + goto fail_cmd; + } + list_for_each_safe(ptr, next, &ac->port[dir].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == buf_add) { + list_del(&buf_node->list); + kfree(buf_node); + } + } + rc = 0; + +fail_cmd: + return rc; +} + +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain) +{ + struct asm_volume_ctrl_lr_chan_gain lrgain; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_lr_chan_gain); + q6asm_add_hdr_async(ac, &lrgain.hdr, sz, TRUE); + lrgain.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + lrgain.param.data_payload_addr_lsw = 0; + lrgain.param.data_payload_addr_msw = 0; + lrgain.param.mem_map_handle = 0; + lrgain.param.data_payload_size = sizeof(lrgain) - + sizeof(lrgain.hdr) - sizeof(lrgain.param); + lrgain.data.module_id = ASM_MODULE_ID_VOL_CTRL; + lrgain.data.param_id = ASM_PARAM_ID_VOL_CTRL_LR_CHANNEL_GAIN; + lrgain.data.param_size = lrgain.param.data_payload_size - + sizeof(lrgain.data); + lrgain.data.reserved = 0; + lrgain.l_chan_gain = left_gain; + lrgain.r_chan_gain = right_gain; + rc = apr_send_pkt(ac->apr, (uint32_t *) &lrgain); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + lrgain.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + lrgain.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_mute(struct audio_client *ac, int muteflag) +{ + struct asm_volume_ctrl_mute_config mute; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_mute_config); + q6asm_add_hdr_async(ac, &mute.hdr, sz, TRUE); + mute.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + mute.param.data_payload_addr_lsw = 0; + mute.param.data_payload_addr_msw = 0; + mute.param.mem_map_handle = 0; + mute.param.data_payload_size = sizeof(mute) - + sizeof(mute.hdr) - sizeof(mute.param); + mute.data.module_id = ASM_MODULE_ID_VOL_CTRL; + mute.data.param_id = ASM_PARAM_ID_VOL_CTRL_MUTE_CONFIG; + mute.data.param_size = mute.param.data_payload_size - sizeof(mute.data); + mute.data.reserved = 0; + mute.mute_flag = muteflag; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &mute); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + mute.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + mute.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_volume(struct audio_client *ac, int volume) +{ + struct asm_volume_ctrl_master_gain vol; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_volume_ctrl_master_gain); + q6asm_add_hdr_async(ac, &vol.hdr, sz, TRUE); + vol.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + vol.param.data_payload_addr_lsw = 0; + vol.param.data_payload_addr_msw = 0; + + + vol.param.mem_map_handle = 0; + vol.param.data_payload_size = sizeof(vol) - + sizeof(vol.hdr) - sizeof(vol.param); + vol.data.module_id = ASM_MODULE_ID_VOL_CTRL; + vol.data.param_id = ASM_PARAM_ID_VOL_CTRL_MASTER_GAIN; + vol.data.param_size = vol.param.data_payload_size - sizeof(vol.data); + vol.data.reserved = 0; + vol.master_gain = volume; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &vol); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + vol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + vol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *pause_param) +{ + struct asm_soft_pause_params softpause; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_soft_pause_params); + q6asm_add_hdr_async(ac, &softpause.hdr, sz, TRUE); + softpause.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + + softpause.param.data_payload_addr_lsw = 0; + softpause.param.data_payload_addr_msw = 0; + softpause.param.mem_map_handle = 0; + softpause.param.data_payload_size = sizeof(softpause) - + sizeof(softpause.hdr) - sizeof(softpause.param); + softpause.data.module_id = ASM_MODULE_ID_VOL_CTRL; + softpause.data.param_id = ASM_PARAM_ID_SOFT_PAUSE_PARAMETERS; + softpause.data.param_size = softpause.param.data_payload_size - + sizeof(softpause.data); + softpause.data.reserved = 0; + softpause.enable_flag = pause_param->enable; + softpause.period = pause_param->period; + softpause.step = pause_param->step; + softpause.ramping_curve = pause_param->rampingcurve; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &softpause); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + softpause.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + softpause.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_softvolume(struct audio_client *ac, + struct asm_softvolume_params *softvol_param) +{ + struct asm_soft_step_volume_params softvol; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_soft_step_volume_params); + q6asm_add_hdr_async(ac, &softvol.hdr, sz, TRUE); + softvol.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + softvol.param.data_payload_addr_lsw = 0; + softvol.param.data_payload_addr_msw = 0; + softvol.param.mem_map_handle = 0; + softvol.param.data_payload_size = sizeof(softvol) - + sizeof(softvol.hdr) - sizeof(softvol.param); + softvol.data.module_id = ASM_MODULE_ID_VOL_CTRL; + softvol.data.param_id = ASM_PARAM_ID_SOFT_VOL_STEPPING_PARAMETERS; + softvol.data.param_size = softvol.param.data_payload_size - + sizeof(softvol.data); + softvol.data.reserved = 0; + softvol.period = softvol_param->period; + softvol.step = softvol_param->step; + softvol.ramping_curve = softvol_param->rampingcurve; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &softvol); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + softvol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + softvol.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_equalizer(struct audio_client *ac, void *eq_p) +{ + struct asm_eq_params eq; + struct msm_audio_eq_stream_config *eq_params = NULL; + int i = 0; + int sz = 0; + int rc = 0; + + if (eq_p == NULL) { + pr_err("%s[%d]: Invalid Eq param\n", __func__, ac->session); + rc = -EINVAL; + goto fail_cmd; + } + sz = sizeof(struct asm_eq_params); + eq_params = (struct msm_audio_eq_stream_config *) eq_p; + q6asm_add_hdr(ac, &eq.hdr, sz, TRUE); + + eq.hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS_V2; + eq.param.data_payload_addr_lsw = 0; + eq.param.data_payload_addr_msw = 0; + eq.param.mem_map_handle = 0; + eq.param.data_payload_size = sizeof(eq) - + sizeof(eq.hdr) - sizeof(eq.param); + eq.data.module_id = ASM_MODULE_ID_EQUALIZER; + eq.data.param_id = ASM_PARAM_ID_EQUALIZER_PARAMETERS; + eq.data.param_size = eq.param.data_payload_size - sizeof(eq.data); + eq.enable_flag = eq_params->enable; + eq.num_bands = eq_params->num_bands; + + pr_debug("%s: enable:%d numbands:%d\n", __func__, eq_params->enable, + eq_params->num_bands); + for (i = 0; i < eq_params->num_bands; i++) { + eq.eq_bands[i].band_idx = + eq_params->eq_bands[i].band_idx; + eq.eq_bands[i].filterype = + eq_params->eq_bands[i].filter_type; + eq.eq_bands[i].center_freq_hz = + eq_params->eq_bands[i].center_freq_hz; + eq.eq_bands[i].filter_gain = + eq_params->eq_bands[i].filter_gain; + eq.eq_bands[i].q_factor = + eq_params->eq_bands[i].q_factor; + pr_debug("%s: filter_type:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_type, i); + pr_debug("%s: center_freq_hz:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].center_freq_hz, i); + pr_debug("%s: filter_gain:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_gain, i); + pr_debug("%s: q_factor:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].q_factor, i); + } + rc = apr_send_pkt(ac->apr, (uint32_t *)&eq); + if (rc < 0) { + pr_err("%s: set-params send failed paramid[0x%x]\n", __func__, + eq.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout, set-params paramid[0x%x]\n", __func__, + eq.data.param_id); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_read(struct audio_client *ac) +{ + struct asm_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr(ac, &read.hdr, sizeof(read), FALSE); + + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ_V2; + read.buf_addr_lsw = ab->phys; + read.buf_addr_msw = 0; + + list_for_each_safe(ptr, next, &ac->port[OUT].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t) ab->phys) + read.mem_map_handle = buf_node->mmap_hdl; + } + pr_debug("memory_map handle in q6asm_read: [%0x]:", + read.mem_map_handle); + read.buf_size = ab->size; + read.seq_id = port->dsp_buf; + read.hdr.token = port->dsp_buf; + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + mutex_unlock(&port->lock); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_addr_lsw, + read.hdr.token, + read.seq_id); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_read_nolock(struct audio_client *ac) +{ + struct asm_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ_V2; + read.buf_addr_lsw = ab->phys; + read.buf_addr_msw = 0; + read.buf_size = ab->size; + read.seq_id = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + list_for_each_safe(ptr, next, &ac->port[OUT].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t)ab->phys) { + read.mem_map_handle = buf_node->mmap_hdl; + break; + } + } + + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_addr_lsw, + read.hdr.token, + read.seq_id); + pr_debug("q6asm_read_nolock mem-map handle is %x", + read.mem_map_handle); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param) +{ + int rc = 0; + struct asm_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + struct audio_buffer *ab; + struct audio_port_data *port; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), FALSE); + + port = &ac->port[IN]; + ab = &port->buf[port->dsp_buf]; + + /* Pass physical address as token for AIO scheme */ + write.hdr.token = param->uid; + write.hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write.buf_addr_lsw = param->paddr; + write.buf_addr_msw = 0x00; + write.buf_size = param->len; + write.timestamp_msw = param->msw_ts; + write.timestamp_lsw = param->lsw_ts; + pr_debug("%s: token[0x%x], buf_addr_lsw[0x%x], buf_size[0x%x]," + "ts_msw[0x%x], ts_lsw[0x%x]\n", + __func__, write.hdr.token, write.buf_addr_lsw, + write.buf_size, write.timestamp_msw, + write.timestamp_lsw); + /* Use 0xFF00 for disabling timestamps */ + if (param->flags == 0xFF00) + write.flags = (0x00000000 | (param->flags & 0x800000FF)); + else + write.flags = (0x80000000 | param->flags); + + write.seq_id = param->uid; + list_for_each_safe(ptr, next, &ac->port[IN].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == (uint32_t)write.buf_addr_lsw) { + write.mem_map_handle = buf_node->mmap_hdl; + pr_debug("%s:buf_node->mmap_hdl = 0x%x," + "write.mem_map_handle = 0x%x\n", + __func__, + buf_node->mmap_hdl, + (uint32_t)write.mem_map_handle); + break; + } + } + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]," + "mem_map_handle[0x%x]\n", __func__, ac->session, + write.buf_addr_lsw, write.buf_size, write.mem_map_handle); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_debug("[%s] write op[0x%x]rc[%d]\n", __func__, + write.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param) +{ + int rc = 0; + struct asm_data_cmd_read_v2 read; + struct asm_buffer_node *buf_node = NULL; + struct list_head *ptr, *next; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + /* Pass physical address as token for AIO scheme */ + read.hdr.token = param->paddr; + read.hdr.opcode = ASM_DATA_CMD_READ_V2; + read.buf_addr_lsw = param->paddr; + read.buf_addr_msw = 0; + read.buf_size = param->len; + read.seq_id = param->uid; + + list_for_each_safe(ptr, next, &ac->port[IN].mem_map_handle) { + buf_node = list_entry(ptr, struct asm_buffer_node, + list); + if (buf_node->buf_addr_lsw == param->paddr) + read.mem_map_handle = buf_node->mmap_hdl; + } + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + read.buf_addr_lsw, read.buf_size); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_debug("[%s] read op[0x%x]rc[%d]\n", __func__, + read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr(ac, &write.hdr, sizeof(write), + FALSE); + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write.buf_addr_lsw = ab->phys; + write.buf_addr_msw = 0; + write.buf_size = len; + write.seq_id = port->dsp_buf; + write.timestamp_lsw = lsw_ts; + write.timestamp_msw = msw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.flags = (0x00000000 | (flags & 0x800000FF)); + else + write.flags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + buf_node = list_first_entry(&ac->port[IN].mem_map_handle, + struct asm_buffer_node, + list); + write.mem_map_handle = buf_node->mmap_hdl; + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]" + "token[0x%x]buf_id[0x%x]buf_size[0x%x]mmaphdl[0x%x]" + , __func__, + ab->phys, + write.buf_addr_lsw, + write.hdr.token, + write.seq_id, + write.buf_size, + write.mem_map_handle); + mutex_unlock(&port->lock); + + config_debug_fs_write(ab); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_data_cmd_write_v2 write; + struct asm_buffer_node *buf_node = NULL; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), + FALSE); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write.buf_addr_lsw = ab->phys; + write.buf_addr_msw = 0; + write.buf_size = len; + write.seq_id = port->dsp_buf; + write.timestamp_lsw = lsw_ts; + write.timestamp_msw = msw_ts; + buf_node = list_first_entry(&ac->port[IN].mem_map_handle, + struct asm_buffer_node, + list); + write.mem_map_handle = buf_node->mmap_hdl; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.flags = (0x00000000 | (flags & 0x800000FF)); + else + write.flags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_err("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]" + "buf_id[0x%x]buf_size[0x%x]mmaphdl[0x%x]" + , __func__, + ab->phys, + write.buf_addr_lsw, + write.hdr.token, + write.seq_id, + write.buf_size, + write.mem_map_handle); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +uint64_t q6asm_get_session_time(struct audio_client *ac) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + hdr.opcode = ASM_SESSION_CMD_GET_SESSIONTIME_V3; + atomic_set(&ac->cmd_state, 1); + + pr_debug("%s: session[%d]opcode[0x%x]\n", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in getting session time from DSP\n", + __func__); + goto fail_cmd; + } + return ac->time_stamp; + +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + atomic_t *state; + int cnt = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + state = &ac->cmd_state; + break; + case CMD_FLUSH: + pr_debug("%s:CMD_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH; + state = &ac->cmd_state; + break; + case CMD_OUT_FLUSH: + pr_debug("%s:CMD_OUT_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + state = &ac->cmd_state; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + atomic_set(&ac->cmd_state, 0); + state = &ac->cmd_state; + break; + case CMD_CLOSE: + pr_debug("%s:CMD_CLOSE\n", __func__); + hdr.opcode = ASM_STREAM_CMD_CLOSE; + state = &ac->cmd_state; + break; + default: + pr_err("Invalid format[%d]\n", cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, (atomic_read(state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for response opcode[0x%x]\n", + hdr.opcode); + goto fail_cmd; + } + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + if (cmd == CMD_CLOSE) { + /* check if DSP return all buffers */ + if (ac->port[IN].buf) { + for (cnt = 0; cnt < ac->port[IN].max_buf_cnt; + cnt++) { + if (ac->port[IN].buf[cnt].used == IN) { + pr_debug("Write Buf[%d] not returned\n", + cnt); + } + } + } + if (ac->port[OUT].buf) { + for (cnt = 0; cnt < ac->port[OUT].max_buf_cnt; cnt++) { + if (ac->port[OUT].buf[cnt].used == OUT) { + pr_debug("Read Buf[%d] not returned\n", + cnt); + } + } + } + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + q6asm_add_hdr_async(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + break; + default: + pr_err("%s:Invalid format[%d]\n", __func__, cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("%s:Commmand 0x%x failed\n", __func__, hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + int cnt = 0; + int loopcnt = 0; + struct audio_port_data *port = NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + mutex_lock(&ac->cmd_lock); + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + cnt = port->max_buf_cnt - 1; + port->dsp_buf = 0; + port->cpu_buf = 0; + while (cnt >= 0) { + if (!port->buf) + continue; + port->buf[cnt].used = 1; + cnt--; + } + } + mutex_unlock(&ac->cmd_lock); + } +} + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable) +{ + struct asm_session_cmd_regx_overflow tx_overflow; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s:session[%d]enable[%d]\n", __func__, + ac->session, enable); + q6asm_add_hdr(ac, &tx_overflow.hdr, sizeof(tx_overflow), TRUE); + + tx_overflow.hdr.opcode = \ + ASM_SESSION_CMD_REGISTER_FORX_OVERFLOW_EVENTS; + /* tx overflow event: enable */ + tx_overflow.enable_flag = enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &tx_overflow); + if (rc < 0) { + pr_err("tx overflow op[0x%x]rc[%d]\n", \ + tx_overflow.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for tx overflow\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_get_apr_service_id(int session_id) +{ + pr_debug("%s\n", __func__); + + if (session_id < 0 || session_id > SESSION_MAX) { + pr_err("%s: invalid session_id = %d\n", __func__, session_id); + return -EINVAL; + } + + return ((struct apr_svc *)session[session_id]->apr)->id; +} + + +static int __init q6asm_init(void) +{ + pr_debug("%s\n", __func__); + memset(session, 0, sizeof(session)); + + config_debug_fs_init(); + + return 0; +} + +device_initcall(q6asm_init); diff --git a/sound/soc/msm/qdsp6v2/q6audio-v2.c b/sound/soc/msm/qdsp6v2/q6audio-v2.c new file mode 100644 index 000000000000..775af95caafb --- /dev/null +++ b/sound/soc/msm/qdsp6v2/q6audio-v2.c @@ -0,0 +1,151 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int q6audio_get_port_index(u16 port_id) +{ + switch (port_id) { + case PRIMARY_I2S_RX: return IDX_PRIMARY_I2S_RX; + case PRIMARY_I2S_TX: return IDX_PRIMARY_I2S_TX; + case PCM_RX: return IDX_PCM_RX; + case PCM_TX: return IDX_PCM_TX; + case SECONDARY_I2S_RX: return IDX_SECONDARY_I2S_RX; + case SECONDARY_I2S_TX: return IDX_SECONDARY_I2S_TX; + case MI2S_RX: return IDX_MI2S_RX; + case MI2S_TX: return IDX_MI2S_TX; + case HDMI_RX: return IDX_HDMI_RX; + case RSVD_2: return IDX_RSVD_2; + case RSVD_3: return IDX_RSVD_3; + case DIGI_MIC_TX: return IDX_DIGI_MIC_TX; + case VOICE_RECORD_RX: return IDX_VOICE_RECORD_RX; + case VOICE_RECORD_TX: return IDX_VOICE_RECORD_TX; + case VOICE_PLAYBACK_TX: return IDX_VOICE_PLAYBACK_TX; + case SLIMBUS_0_RX: return IDX_SLIMBUS_0_RX; + case SLIMBUS_0_TX: return IDX_SLIMBUS_0_TX; + case SLIMBUS_1_RX: return IDX_SLIMBUS_1_RX; + case SLIMBUS_1_TX: return IDX_SLIMBUS_1_TX; + case INT_BT_SCO_RX: return IDX_INT_BT_SCO_RX; + case INT_BT_SCO_TX: return IDX_INT_BT_SCO_TX; + case INT_BT_A2DP_RX: return IDX_INT_BT_A2DP_RX; + case INT_FM_RX: return IDX_INT_FM_RX; + case INT_FM_TX: return IDX_INT_FM_TX; + case RT_PROXY_PORT_001_RX: return IDX_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return IDX_RT_PROXY_PORT_001_TX; + + default: return -EINVAL; + } +} + +int q6audio_get_port_id(u16 port_id) +{ + switch (port_id) { + case PRIMARY_I2S_RX: return AFE_PORT_ID_PRIMARY_MI2S_RX; + case PRIMARY_I2S_TX: return AFE_PORT_ID_PRIMARY_MI2S_TX; + case PCM_RX: return AFE_PORT_ID_PRIMARY_PCM_RX; + case PCM_TX: return AFE_PORT_ID_PRIMARY_PCM_TX; + case SECONDARY_I2S_RX: return AFE_PORT_ID_SECONDARY_MI2S_RX; + case SECONDARY_I2S_TX: return AFE_PORT_ID_SECONDARY_MI2S_TX; + case MI2S_RX: return AFE_PORT_ID_PRIMARY_MI2S_RX; + case MI2S_TX: return AFE_PORT_ID_PRIMARY_MI2S_TX; + case HDMI_RX: return AFE_PORT_ID_MULTICHAN_HDMI_RX; + case RSVD_2: return IDX_RSVD_2; + case RSVD_3: return IDX_RSVD_3; + case DIGI_MIC_TX: return AFE_PORT_ID_DIGITAL_MIC_TX; + case VOICE_RECORD_RX: return AFE_PORT_ID_VOICE_RECORD_RX; + case VOICE_RECORD_TX: return AFE_PORT_ID_VOICE_RECORD_TX; + case VOICE_PLAYBACK_TX: return AFE_PORT_ID_VOICE_PLAYBACK_TX; + case SLIMBUS_0_RX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX; + case SLIMBUS_0_TX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX; + case SLIMBUS_1_RX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX; + case SLIMBUS_1_TX: return AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX; + case INT_BT_SCO_RX: return AFE_PORT_ID_INTERNAL_BT_SCO_RX; + case INT_BT_SCO_TX: return AFE_PORT_ID_INTERNAL_BT_SCO_TX; + case INT_BT_A2DP_RX: return AFE_PORT_ID_INTERNAL_BT_A2DP_RX; + case INT_FM_RX: return AFE_PORT_ID_INTERNAL_FM_RX; + case INT_FM_TX: return AFE_PORT_ID_INTERNAL_FM_TX; + case RT_PROXY_PORT_001_RX: return AFE_PORT_ID_RT_PROXY_PORT_001_RX; + case RT_PROXY_PORT_001_TX: return AFE_PORT_ID_RT_PROXY_PORT_001_TX; + + default: return -EINVAL; + } +} +int q6audio_convert_virtual_to_portid(u16 port_id) +{ + int ret; + + /* if port_id is virtual, convert to physical.. + * if port_id is already physical, return physical + */ + if (q6audio_validate_port(port_id) < 0) { + if (port_id == RT_PROXY_DAI_001_RX || + port_id == RT_PROXY_DAI_001_TX || + port_id == RT_PROXY_DAI_002_RX || + port_id == RT_PROXY_DAI_002_TX) + ret = VIRTUAL_ID_TO_PORTID(port_id); + else + ret = -EINVAL; + } else + ret = port_id; + + return ret; +} + +int q6audio_validate_port(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case PCM_RX: + case PCM_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + case HDMI_RX: + case RSVD_2: + case RSVD_3: + case DIGI_MIC_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + case VOICE_PLAYBACK_TX: + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case SLIMBUS_1_RX: + case SLIMBUS_1_TX: + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case INT_FM_TX: + case RT_PROXY_PORT_001_RX: + case RT_PROXY_PORT_001_TX: + { + ret = 0; + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} -- GitLab