sound: Add MSM sound drivers

Signed-off-by: Patrick Lai <plai@codeaurora.org>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
This commit is contained in:
Patrick Lai
2013-01-17 14:30:07 -08:00
committed by Stephen Boyd
parent eca1f9adfe
commit dcb77e20ae
103 changed files with 81308 additions and 10 deletions

View File

@@ -8,3 +8,6 @@ header-y += sb16_csp.h
header-y += sfnt_info.h
header-y += compress_params.h
header-y += compress_offload.h
header-y += tlv.h
header-y += compress_params.h
header-y += compress_offload.h

6172
include/sound/apr_audio-v2.h Normal file

File diff suppressed because it is too large Load Diff

1538
include/sound/apr_audio.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 */

49
include/sound/dai.h Normal file
View File

@@ -0,0 +1,49 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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

View File

@@ -0,0 +1,42 @@
/* Copyright (c) 2012, Code Aurora Forum. 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

View File

@@ -0,0 +1,45 @@
/* Copyright (c) 2011, Code Aurora Forum. 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

View File

@@ -0,0 +1,19 @@
/*
* omap-aess -- OMAP4 ABE DSP
*
* Author: Liam Girdwood <lrg@slimlogic.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#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

50
include/sound/q6adm-v2.h Normal file
View File

@@ -0,0 +1,50 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <sound/q6audio-v2.h>
#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__ */

49
include/sound/q6adm.h Normal file
View File

@@ -0,0 +1,49 @@
/* Copyright (c) 2010-2012, Code Aurora Forum. 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 <sound/q6afe.h>
#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__ */

107
include/sound/q6afe-v2.h Normal file
View File

@@ -0,0 +1,107 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <sound/apr_audio-v2.h>
#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__ */

111
include/sound/q6afe.h Normal file
View File

@@ -0,0 +1,111 @@
/* Copyright (c) 2010-2012, Code Aurora Forum. 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 <sound/apr_audio.h>
#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__ */

303
include/sound/q6asm-v2.h Normal file
View File

@@ -0,0 +1,303 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <mach/qdsp6v2/apr.h>
#include <mach/msm_subsystem_map.h>
#include <sound/apr_audio-v2.h>
#include <linux/list.h>
#include <linux/ion.h>
#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__ */

320
include/sound/q6asm.h Normal file
View File

@@ -0,0 +1,320 @@
/* Copyright (c) 2010-2012, Code Aurora Forum. 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 <mach/qdsp6v2/apr.h>
#include <sound/apr_audio.h>
#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION
#include <linux/ion.h>
#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__ */

View File

@@ -0,0 +1,26 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <mach/qdsp6v2/apr.h>
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

View File

@@ -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

View File

@@ -41,6 +41,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"

View File

@@ -15,6 +15,7 @@ obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += imx/
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/

View File

@@ -40,7 +40,6 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MAX98088 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_PCM3008
select SND_SOC_RT5631 if I2C
@@ -63,7 +62,6 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WL1273 if MFD_WL1273_CORE
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_WM8350 if MFD_WM8350
select SND_SOC_WM8400 if MFD_WM8400
@@ -107,6 +105,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
@@ -284,6 +283,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
@@ -429,11 +437,11 @@ config SND_SOC_WM9713
config SND_SOC_LM4857
tristate
config SND_SOC_MAX9768
tristate
config SND_SOC_MAX9877
tristate
config SND_SOC_TPA6130A2
tristate
config SND_SOC_MSM_STUB
tristate

View File

@@ -25,7 +25,6 @@ snd-soc-dmic-objs := dmic.o
snd-soc-jz4740-codec-objs := jz4740.o
snd-soc-l3-objs := l3.o
snd-soc-lm4857-objs := lm4857.o
snd-soc-max9768-objs := max9768.o
snd-soc-max98088-objs := max98088.o
snd-soc-max98095-objs := max98095.o
snd-soc-max9850-objs := max9850.o
@@ -49,10 +48,12 @@ 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-wm1250-ev1-objs := wm1250-ev1.o
snd-soc-wm2000-objs := wm2000.o
snd-soc-wm2200-objs := wm2200.o
snd-soc-wm5100-objs := wm5100.o wm5100-tables.o
snd-soc-wm8350-objs := wm8350.o
snd-soc-wm8400-objs := wm8400.o
@@ -98,6 +99,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
@@ -131,7 +134,6 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o
obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o
obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
@@ -153,10 +155,12 @@ 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_WM1250_EV1) += snd-soc-wm1250-ev1.o
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
obj-$(CONFIG_SND_SOC_WM2200) += snd-soc-wm2200.o
obj-$(CONFIG_SND_SOC_WM5100) += snd-soc-wm5100.o
obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
@@ -201,6 +205,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.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

904
sound/soc/codecs/cs8427.c Normal file
View File

@@ -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 <perex@perex.cz>
* Copyright (c) 2012, Code Aurora Forum. 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 <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/bitrev.h>
#include <linux/bitops.h>
#include <linux/module.h>
//#include <linux/export.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <asm/unaligned.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/cs8427.h>
#include <sound/asoundef.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#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 = &reg_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 __devinit 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 __devexit 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 = __devexit_p(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");

View File

@@ -0,0 +1,80 @@
/* Copyright (c) 2011, Code Aurora Forum. 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 <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
/* 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 __devinit 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 __devexit 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 = __devexit_p(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");

482
sound/soc/codecs/timpani.c Normal file
View File

@@ -0,0 +1,482 @@
/* Copyright (c) 2010, Code Aurora Forum. 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 <linux/module.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/mfd/marimba.h>
#include <linux/mfd/timpani-audio.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/soc-dapm.h>
/* Debug purpose */
#include <linux/gpio.h>
#include <linux/clk.h>
#include <mach/mpp.h>
/* 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");

View File

@@ -0,0 +1,15 @@
/* Copyright (c) 2010, Code Aurora Forum. 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;

View File

@@ -0,0 +1,722 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/mfd/wcd9xxx/wcd9xxx_registers.h>
#include <linux/mfd/wcd9xxx/wcd9304_registers.h>
#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,
};

5013
sound/soc/codecs/wcd9304.c Normal file

File diff suppressed because it is too large Load Diff

251
sound/soc/codecs/wcd9304.h Normal file
View File

@@ -0,0 +1,251 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <sound/soc.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-slimslave.h>
#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]))))

File diff suppressed because it is too large Load Diff

7792
sound/soc/codecs/wcd9310.c Normal file

File diff suppressed because it is too large Load Diff

253
sound/soc/codecs/wcd9310.h Normal file
View File

@@ -0,0 +1,253 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <sound/soc.h>
#include <sound/jack.h>
#include <linux/mfd/wcd9xxx/wcd9xxx-slimslave.h>
#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]))))

View File

@@ -643,6 +643,8 @@ SND_SOC_DAPM_INPUT("IN2RP:VXRP"),
SND_SOC_DAPM_SUPPLY("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0, NULL, 0),
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,
@@ -867,9 +869,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" },
@@ -886,9 +890,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" },

158
sound/soc/msm/Kconfig Normal file
View File

@@ -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

86
sound/soc/msm/Makefile Normal file
View File

@@ -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

2031
sound/soc/msm/apq8064.c Normal file

File diff suppressed because it is too large Load Diff

488
sound/soc/msm/lpass-dma.c Normal file
View File

@@ -0,0 +1,488 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/android_pmem.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/msm_audio.h>
#include <linux/clk.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <mach/msm_iomap-8x60.h>
#include <mach/audio_dma_msm8k.h>
#include <sound/dai.h>
#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 __devinit 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");

139
sound/soc/msm/lpass-i2s.c Normal file
View File

@@ -0,0 +1,139 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dai.h>
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");

369
sound/soc/msm/lpass-pcm.c Normal file
View File

@@ -0,0 +1,369 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/dma-mapping.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <mach/audio_dma_msm8k.h>
#include <sound/dai.h>
#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");

45
sound/soc/msm/lpass-pcm.h Normal file
View File

@@ -0,0 +1,45 @@
/* Copyright (c) 2010, Code Aurora Forum. 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*/

2203
sound/soc/msm/mdm9615.c Normal file

File diff suppressed because it is too large Load Diff

1470
sound/soc/msm/mpq8064.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,753 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/q6asm.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/timer.h>
#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 *)&param;
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,
&param) < 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,
&param) < 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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2011, Code Aurora Forum. 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 <sound/apr_audio.h>
#include <sound/q6asm.h>
#include <sound/compress_params.h>
#include <sound/compress_offload.h>
#include <sound/compress_driver.h>
#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*/

412
sound/soc/msm/msm-dai-fe.c Normal file
View File

@@ -0,0 +1,412 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
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 __devinit 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 __devexit 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");

View File

@@ -0,0 +1,326 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/bitops.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/apr_audio.h>
#include <sound/q6afe.h>
#include <sound/q6adm.h>
#include <sound/msm-dai-q6.h>
#include <mach/msm_hdmi_audio.h>
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 __devinit 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 __devexit 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");

2024
sound/soc/msm/msm-dai-q6.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
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 __devinit 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 __devexit 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");

150
sound/soc/msm/msm-dai.c Normal file
View File

@@ -0,0 +1,150 @@
/* sound/soc/msm/msm-dai.c
*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2008-2011, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#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 __devinit 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 __devexit asoc_msm_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_dai(&pdev->dev);
return 0;
}
static __devinit int asoc_pcm_cpu_probe(struct platform_device *pdev)
{
return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais);
}
static int __devexit 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 = __devexit_p(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 = __devexit_p(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");

View File

@@ -0,0 +1,804 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <asm/dma.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#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 __devinit 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 = __devexit_p(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");

936
sound/soc/msm/msm-mvs.c Normal file
View File

@@ -0,0 +1,936 @@
/* Copyright (c) 2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <linux/kthread.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/wakelock.h>
#include <mach/msm_rpcrouter.h>
#include <mach/debug_mm.h>
#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 __devinit 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 = __devexit_p(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");

621
sound/soc/msm/msm-pcm-afe.c Normal file
View File

@@ -0,0 +1,621 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/q6adm.h>
#include <asm/dma.h>
#include <linux/memory_alloc.h>
#include <mach/msm_subsystem_map.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,46 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <sound/apr_audio.h>
#include <sound/q6afe.h>
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*/

View File

@@ -0,0 +1,61 @@
/* Copyright (c) 2011, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
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 __devinit 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 = __devexit_p(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");

610
sound/soc/msm/msm-pcm-lpa.c Normal file
View File

@@ -0,0 +1,610 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/compress_params.h>
#include <sound/compress_offload.h>
#include <sound/compress_driver.h>
#include <sound/timer.h>
#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 *)&param;
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,
&param) < 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,
&param) < 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 __devinit 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 = __devexit_p(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");

740
sound/soc/msm/msm-pcm-q6.c Normal file
View File

@@ -0,0 +1,740 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2008-2009,2011 Code Aurora Forum. 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 <sound/apr_audio.h>
#include <sound/q6asm.h>
/* 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*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <sound/apr_audio.h>
#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*/

View File

@@ -0,0 +1,558 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,37 @@
/* Copyright (c) 2011, Code Aurora Forum. 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 <sound/apr_audio.h>
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*/

1169
sound/soc/msm/msm-pcm-voip.c Normal file

File diff suppressed because it is too large Load Diff

646
sound/soc/msm/msm-pcm.c Normal file
View File

@@ -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 Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#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);

204
sound/soc/msm/msm-pcm.h Normal file
View File

@@ -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 Code Aurora Forum. 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 <mach/qdsp5/qdsp5audppcmdi.h>
#include <mach/qdsp5/qdsp5audppmsg.h>
#include <mach/qdsp5/qdsp5audreccmdi.h>
#include <mach/qdsp5/qdsp5audrecmsg.h>
#include <mach/qdsp5/qdsp5audpreproccmdi.h>
#include <mach/qdsp5/qdsp5audpreprocmsg.h>
#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*/

610
sound/soc/msm/msm-voip.c Normal file
View File

@@ -0,0 +1,610 @@
/* Copyright (c) 2011, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/wakelock.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/q6asm.h>
#include <sound/apr_audio.h>
#include <mach/msm_rpcrouter.h>
#include <mach/qdsp6v2/q6voice.h>
#include <mach/qdsp6v2/audio_dev_ctl.h>
#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 __devinit 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 = __devexit_p(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");

424
sound/soc/msm/msm7201.c Normal file
View File

@@ -0,0 +1,424 @@
/* linux/sound/soc/msm/msm7201.c
*
* Copyright (c) 2008-2009, 2011, 2012 Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include "msm-pcm.h"
#include <asm/mach-types.h>
#include <mach/msm_rpcrouter.h>
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");

699
sound/soc/msm/msm7k-pcm.c Normal file
View File

@@ -0,0 +1,699 @@
/* linux/sound/soc/msm/msm7k-pcm.c
*
* Copyright (c) 2008-2009, 2012 Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#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 __devinit 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 = __devexit_p(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");

149
sound/soc/msm/msm7kv2-dai.c Normal file
View File

@@ -0,0 +1,149 @@
/* sound/soc/msm/msm-dai.c
*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2008-2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <linux/slab.h>
#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 __devinit 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 __devexit asoc_msm_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_dai(&pdev->dev);
return 0;
}
static __devinit 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 __devexit 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 = __devexit_p(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 = __devexit_p(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");

633
sound/soc/msm/msm7kv2-dsp.c Normal file
View File

@@ -0,0 +1,633 @@
/* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2008-2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <mach/qdsp5v2/audio_dev_ctl.h>
#include <mach/debug_mm.h>
#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);
}
}

774
sound/soc/msm/msm7kv2-pcm.c Normal file
View File

@@ -0,0 +1,774 @@
/* Copyright (c) 2008-2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <linux/slab.h>
#include "msm7kv2-pcm.h"
#include <mach/qdsp5v2/audio_dev_ctl.h>
#include <mach/debug_mm.h>
#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 __devinit 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 = __devexit_p(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");

207
sound/soc/msm/msm7kv2-pcm.h Normal file
View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2008-2010, Code Aurora Forum. 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 <mach/qdsp5v2/qdsp5audppcmdi.h>
#include <mach/qdsp5v2/qdsp5audppmsg.h>
#include <mach/qdsp5v2/qdsp5audplaycmdi.h>
#include <mach/qdsp5v2/qdsp5audplaymsg.h>
#include <mach/qdsp5v2/audpp.h>
#include <mach/msm_adsp.h>
#include <mach/qdsp5v2/qdsp5audreccmdi.h>
#include <mach/qdsp5v2/qdsp5audrecmsg.h>
#include <mach/qdsp5v2/audpreproc.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 \
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*/

1004
sound/soc/msm/msm7x30.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,725 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <linux/clk.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/mfd/pmic8058.h>
#include <linux/mfd/pmic8901.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <mach/mpp.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/soc-dsp.h>
#include <sound/pcm.h>
#include <asm/mach-types.h>
#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");

342
sound/soc/msm/msm8660.c Normal file
View File

@@ -0,0 +1,342 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/clk.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/mfd/pmic8058.h>
#include <linux/mfd/pmic8901.h>
#include <linux/platform_device.h>
#include <mach/board.h>
#include <mach/mpp.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/dai.h>
#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");

1274
sound/soc/msm/msm8930.c Normal file

File diff suppressed because it is too large Load Diff

1740
sound/soc/msm/msm8960.c Normal file

File diff suppressed because it is too large Load Diff

752
sound/soc/msm/msm8974.c Normal file
View File

@@ -0,0 +1,752 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/mfd/pm8xxx/pm8921.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/mfd/pm8xxx/pm8921.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/soc-dsp.h>
#include <sound/pcm.h>
#include <sound/jack.h>
#include <asm/mach-types.h>
#include <mach/socinfo.h>
#include <qdsp6v2/msm-pcm-routing-v2.h>
#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, &param);
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, &param);
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");

148
sound/soc/msm/msm8x60-dai.c Normal file
View File

@@ -0,0 +1,148 @@
/* sound/soc/msm/msm-dai.c
*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#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 __devinit 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 __devexit asoc_msm_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_dai(&pdev->dev);
return 0;
}
static __devinit 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 __devexit 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 = __devexit_p(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 = __devexit_p(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");

806
sound/soc/msm/msm8x60-pcm.c Normal file
View File

@@ -0,0 +1,806 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/android_pmem.h>
#include <mach/qdsp6v2/audio_dev_ctl.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2010-2011, Code Aurora Forum. 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 <sound/apr_audio.h>
#include <sound/q6asm.h>
#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*/

1231
sound/soc/msm/msm8x60.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,369 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. 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 <linux/msm_audio.h>
#include <linux/wakelock.h>
#include <linux/pm_qos.h>
#include <mach/msm_rpcrouter.h>
#include <mach/debug_mm.h>
#include <linux/slab.h>
#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 */

141
sound/soc/msm/mvs-dai.c Normal file
View File

@@ -0,0 +1,141 @@
/* Copyright (c) 2010, Code Aurora Forum. 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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#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 __devinit 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 __devexit asoc_mvs_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_dai(&pdev->dev);
return 0;
}
static __devinit 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 __devexit 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 = __devexit_p(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 = __devexit_p(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");

View File

@@ -0,0 +1,2 @@
obj-y := q6asm.o q6adm.o q6afe.o
obj-$(CONFIG_SND_SOC_VOICE) += q6voice.o

1088
sound/soc/msm/qdsp6/q6adm.c Normal file

File diff suppressed because it is too large Load Diff

1738
sound/soc/msm/qdsp6/q6afe.c Normal file

File diff suppressed because it is too large Load Diff

3533
sound/soc/msm/qdsp6/q6asm.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,997 @@
/* Copyright (c) 2011-2012, Code Aurora Forum. 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 <mach/qdsp6v2/apr.h>
#include <linux/ion.h>
#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

View File

@@ -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

View File

@@ -0,0 +1,666 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#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 *)&param;
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,
&param) < 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,
&param) < 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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2012, Code Aurora Forum. 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 <sound/apr_audio-v2.h>
#include <sound/q6asm-v2.h>
#include <sound/snd_compress_params.h>
#include <sound/compress_offload.h>
#include <sound/compress_driver.h>
#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*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,777 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <asm/dma.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,581 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/q6adm-v2.h>
#include <asm/dma.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,45 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <sound/apr_audio-v2.h>
#include <sound/q6afe-v2.h>
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*/

View File

@@ -0,0 +1,609 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#include <sound/snd_compress_params.h>
#include <sound/compress_offload.h>
#include <sound/compress_driver.h>
#include <sound/timer.h>
#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 *)&param;
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,
&param) < 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,
&param) < 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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,725 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/init.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <linux/android_pmem.h>
#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 __devinit 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 = __devexit_p(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");

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2008 Google, Inc.
* Copyright (C) 2008 HTC Corporation
* Copyright (c) 2012 Code Aurora Forum. 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 <sound/apr_audio-v2.h>
#include <sound/q6asm-v2.h>
/* 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*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <sound/apr_audio-v2.h>
#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*/

View File

@@ -0,0 +1,621 @@
/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <mach/qdsp6v2/audio_acdb.h>
#include <mach/qdsp6v2/rtac.h>
#include <sound/apr_audio-v2.h>
#include <mach/qdsp6v2/apr.h>
#include <sound/q6adm-v2.h>
#include <sound/q6audio-v2.h>
#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);

Some files were not shown because too many files have changed in this diff Show More