blob: fbf0abb454ddb2b049429e04ad8dd0680753971c [file] [log] [blame]
/*
* kailimba audio system PCM drive
*
* Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company.
*
* Licensed under GPLv2 or later.
*/
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "dsp.h"
#include "i2s.h"
#include "iacc.h"
#include "kcm.h"
#include "usp-pcm.h"
bool enable_2mic_cvc = false;
module_param(enable_2mic_cvc, bool, 0);
#define KAS_PCM_COUNT 12
struct kas_pcm_data {
struct snd_pcm_substream *substream;
u16 kalimba_notify_ep_id;
struct endpoint_handle *sw_ep_handle;
u32 sw_ep_handle_phy_addr;
u32 pos;
snd_pcm_uframes_t last_appl_ptr;
void *action_id;
bool kas_started;
struct components_chain *components_chain;
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
};
struct kas_priv_data {
struct kas_pcm_data pcm[KAS_PCM_COUNT][2];
struct kcm_t *kcm;
int pre_channel_volume[4];
int stream_volume[MIXER_SUPPORT_STREAMS * 2];
int stream_ramp[2][MIXER_SUPPORT_STREAMS * 2];
int stream_mute[MIXER_SUPPORT_STREAMS * 2];
u16 pre_gain;
u16 peq_switch_mode[PEQ_NUM_MAX];
u16 peq_params_array[PEQ_NUM_MAX][PEQ_PARAMS_ARRAY_LEN_16B];
u16 dbe_switch_mode;
u16 dbe_params_array[DBE_PARAMS_ARRAY_LEN_16B];
u16 delay_params_array[DELAY_PARAMS_ARRAY_LEN_16B];
};
static int i2s_master;
static int bt_usp_port;
static const struct snd_pcm_hardware kas_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = 32,
.period_bytes_max = 256 * 1024,
.periods_min = 2,
.periods_max = 128,
.buffer_bytes_max = 512 * 1024, /* 512 kbytes */
};
static int kas_playback_delay_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int cntl = mc->reg & DELAY_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (cntl) {
case DELAY_PARAM_CHAN1_DELAY:
case DELAY_PARAM_CHAN2_DELAY:
case DELAY_PARAM_CHAN3_DELAY:
case DELAY_PARAM_CHAN4_DELAY:
pos = cntl;
val = get24bit((u8 *)pdata->delay_params_array, pos);
ucontrol->value.integer.value[0] = val;
break;
case DELAY_PARAM_CHAN5_DELAY:
case DELAY_PARAM_CHAN6_DELAY:
case DELAY_PARAM_CHAN7_DELAY:
case DELAY_PARAM_CHAN8_DELAY:
/* not support currently */
return -EINVAL;
}
return 0;
}
static int kas_playback_delay_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int cntl = mc->reg & DBE_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (cntl) {
case DELAY_PARAM_CHAN1_DELAY:
case DELAY_PARAM_CHAN2_DELAY:
case DELAY_PARAM_CHAN3_DELAY:
case DELAY_PARAM_CHAN4_DELAY:
pos = cntl;
val = ucontrol->value.integer.value[0] & Q24_MASK;
put24bit((u8 *)pdata->delay_params_array, pos, val);
kalimba_set_delay_params(pos, val);
break;
case DELAY_PARAM_CHAN5_DELAY:
case DELAY_PARAM_CHAN6_DELAY:
case DELAY_PARAM_CHAN7_DELAY:
case DELAY_PARAM_CHAN8_DELAY:
/* not support currently */
return -EINVAL;
}
return 0;
}
#define MIN_DBE_GAIN_DB -32
/* TLV used by dbe gain */
static const DECLARE_TLV_DB_SCALE(kas_dbe_gain_tlv,
MIN_DBE_GAIN_DB * 100, 100, 0);
static int kas_playback_dbe_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int cntl = mc->reg & DBE_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (mc->reg) {
case DBE_CNTL_SWITCH:
ucontrol->value.integer.value[0] =
pdata->dbe_switch_mode;
break;
case DBE_PARAM_MIX_BALANCE:
case DBE_PARAM_EFFECT_STRENGTH:
case DBE_PARAM_HARM_CONTENT:
pos = cntl;
val = get24bit((u8 *)pdata->dbe_params_array, pos);
ucontrol->value.integer.value[0] = val;
break;
case DBE_PARAM_AMP_LIMIT:
pos = cntl;
/* 0~32 <- -32~0 <- Q24:12.N */
val = get24bit((u8 *)pdata->dbe_params_array, pos);
if (val & 0x00800000) {
ucontrol->value.integer.value[0] =
((val >> 12) | 0xFFFFF000) - MIN_DBE_GAIN_DB;
} else
ucontrol->value.integer.value[0] =
(val >> 12) - MIN_DBE_GAIN_DB;
break;
case DBE_PARAM_XOVER_FC:
case DBE_PARAM_LP_FC:
case DBE_PARAM_HP_FC:
pos = cntl;
/* 40~1000/50~300/30~300 <- Q24:20.N */
val = get24bit((u8 *)pdata->dbe_params_array, pos);
ucontrol->value.integer.value[0] = val >> 4;
break;
}
return 0;
}
static int kas_playback_dbe_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int cntl = mc->reg & DBE_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (cntl) {
case DBE_CNTL_SWITCH:
pdata->dbe_switch_mode =
ucontrol->value.integer.value[0];
/* 0~2 -> 1~3 */
val = ucontrol->value.integer.value[0] + 1;
kalimba_set_dbe_control(val);
break;
case DBE_PARAM_MIX_BALANCE:
case DBE_PARAM_EFFECT_STRENGTH:
case DBE_PARAM_HARM_CONTENT:
pos = cntl;
val = ucontrol->value.integer.value[0] & Q24_MASK;
put24bit((u8 *)pdata->dbe_params_array, pos, val);
kalimba_set_dbe_params(pos, val);
break;
case DBE_PARAM_AMP_LIMIT:
pos = cntl;
/* 0~32 -> -32~0 -> Q24:12.N */
val = ((ucontrol->value.integer.value[0] + MIN_DBE_GAIN_DB)
<< 12) & Q24_MASK;
put24bit((u8 *)pdata->dbe_params_array, pos, val);
kalimba_set_dbe_params(pos, val);
break;
case DBE_PARAM_XOVER_FC:
case DBE_PARAM_LP_FC:
case DBE_PARAM_HP_FC:
pos = cntl;
/* 40~1000/50~300/30~300 -> Q24:20.N */
if (ucontrol->value.integer.value[0] < mc->shift)
return -EINVAL;
val = (ucontrol->value.integer.value[0] << 4) & Q24_MASK;
put24bit((u8 *)pdata->dbe_params_array, pos, val);
kalimba_set_dbe_params(pos, val);
break;
}
return 0;
}
static int kas_playback_peq_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
static int kas_playback_peq_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
#define MIN_PEQ_GAIN_DB -60
/* TLV used by peq gain */
static const DECLARE_TLV_DB_SCALE(kas_peq_gain_tlv,
MIN_PEQ_GAIN_DB * 100, 100, 0);
/* Convenience kcontrol builders for PEQ */
#define KAS_PEQ_PER_BAND_CONTROLS(name, base, band) \
SOC_SINGLE_EXT(name " Band" #band " FC", \
base + band * 0x100 + PEQ_PARAM_BAND_FC, 20, 24000, 0, \
kas_playback_peq_get, kas_playback_peq_put), \
SOC_SINGLE_EXT_TLV(name " Band" #band " Gain", \
base + band * 0x100 + PEQ_PARAM_BAND_GAIN, 0, 80, 0, \
kas_playback_peq_get, kas_playback_peq_put, \
kas_peq_gain_tlv)
#define KAS_PEQ_ALL_BANDS_CONTROLS(name, base) \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 1), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 2), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 3), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 4), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 5), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 6), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 7), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 8), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 9), \
KAS_PEQ_PER_BAND_CONTROLS(name, base, 10)
#define KAS_PEQ_CONTROLS(name, base) \
SOC_SINGLE_EXT(name " Switch Mode", \
base + PEQ_CNTL_SWITCH, 0, 2, 0, \
kas_playback_peq_get, kas_playback_peq_put), \
SOC_SINGLE_EXT(name " Core Type", \
base + PEQ_PARAM_CORE_TYPE, 0, 2, 0, \
kas_playback_peq_get, kas_playback_peq_put), \
SOC_SINGLE_EXT(name " Bands Num", \
base + PEQ_PARAM_BANDS_NUM, 0, 10, 0, \
kas_playback_peq_get, kas_playback_peq_put), \
SOC_SINGLE_EXT_TLV(name " Master Gain", \
base + PEQ_PARAM_MASTER_GAIN, 0, 80, 0, \
kas_playback_peq_get, kas_playback_peq_put, \
kas_peq_gain_tlv), \
KAS_PEQ_ALL_BANDS_CONTROLS(name, base)
static int kas_playback_peq_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
u16 index = (mc->reg & PEQ_BASE_MASK) >> PEQ_BASE_SHIFT;
int band = (mc->reg & PEQ_BAND_MASK) >> PEQ_BAND_SHIFT;
int cntl = mc->reg & PEQ_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (cntl) {
case PEQ_CNTL_SWITCH:
ucontrol->value.integer.value[0] =
pdata->peq_switch_mode[index];
break;
case PEQ_PARAM_CORE_TYPE:
case PEQ_PARAM_BANDS_NUM:
pos = cntl;
val = get24bit((u8 *)pdata->peq_params_array[index], pos);
ucontrol->value.integer.value[0] = val;
break;
case PEQ_PARAM_MASTER_GAIN:
pos = cntl;
/* 0~80 <- -60~20 <- Q24:12.N */
val = get24bit((u8 *)pdata->peq_params_array[index], pos);
if (val & 0x00800000) {
ucontrol->value.integer.value[0] =
((val >> 12) | 0xFFFFF000) - MIN_PEQ_GAIN_DB;
} else
ucontrol->value.integer.value[0] =
(val >> 12) - MIN_PEQ_GAIN_DB;
break;
case PEQ_PARAM_BAND_FC:
pos = (band - 1) * 4 + cntl;
/* 20~24000 <- Q24:20.N */
val = get24bit((u8 *)pdata->peq_params_array[index], pos);
ucontrol->value.integer.value[0] = val >> 4;
break;
case PEQ_PARAM_BAND_GAIN:
pos = (band - 1) * 4 + cntl;
/* 0~80 <- -60~20 <- Q24:12.N */
val = get24bit((u8 *)pdata->peq_params_array[index], pos);
if (val & 0x00800000) {
ucontrol->value.integer.value[0] =
((val >> 12) | 0xFFFFF000) - MIN_PEQ_GAIN_DB;
} else
ucontrol->value.integer.value[0] =
(val >> 12) - MIN_PEQ_GAIN_DB;
break;
case PEQ_PARAM_CONFIG:
case PEQ_PARAM_BAND_FILTER:
case PEQ_PARAM_BAND_Q:
/* not support currently */
return -EINVAL;
}
return 0;
}
static int kas_playback_peq_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
u16 index = (mc->reg & PEQ_BASE_MASK) >> PEQ_BASE_SHIFT;
int band = (mc->reg & PEQ_BAND_MASK) >> PEQ_BAND_SHIFT;
int cntl = mc->reg & PEQ_CNTL_MASK;
u16 pos = 0;
int val = 0;
switch (cntl) {
case PEQ_CNTL_SWITCH:
pdata->peq_switch_mode[index] =
ucontrol->value.integer.value[0];
/* 0~2 -> 1~3 */
val = ucontrol->value.integer.value[0] + 1;
kalimba_set_peq_control(index, val);
break;
case PEQ_PARAM_CORE_TYPE:
case PEQ_PARAM_BANDS_NUM:
pos = cntl;
val = ucontrol->value.integer.value[0] & Q24_MASK;
put24bit((u8 *)pdata->peq_params_array[index], pos, val);
kalimba_set_peq_params(index, pos, val);
break;
case PEQ_PARAM_MASTER_GAIN:
pos = cntl;
/* 0~80 -> -60~20 -> Q24:12.N */
val = ((ucontrol->value.integer.value[0] + MIN_PEQ_GAIN_DB)
<< 12) & Q24_MASK;
put24bit((u8 *)pdata->peq_params_array[index], pos, val);
kalimba_set_peq_params(index, pos, val);
break;
case PEQ_PARAM_BAND_FC:
pos = (band - 1) * 4 + cntl;
/* 20~24000 -> Q24:20.N */
if (ucontrol->value.integer.value[0] < mc->shift)
return -EINVAL;
val = (ucontrol->value.integer.value[0] << 4) & Q24_MASK;
put24bit((u8 *)pdata->peq_params_array[index], pos, val);
kalimba_set_peq_params(index, pos, val);
break;
case PEQ_PARAM_BAND_GAIN:
pos = (band - 1) * 4 + cntl;
/* 0~80 -> -60~20 -> Q24:12.N */
val = ((ucontrol->value.integer.value[0] + MIN_PEQ_GAIN_DB)
<< 12) & Q24_MASK;
put24bit((u8 *)pdata->peq_params_array[index], pos, val);
kalimba_set_peq_params(index, pos, val);
break;
case PEQ_PARAM_CONFIG:
case PEQ_PARAM_BAND_FILTER:
case PEQ_PARAM_BAND_Q:
/* not support currently */
return -EINVAL;
}
return 0;
}
#define MIN_CHANNEL_GAIN_DB -120
#define MIN_MUSIC_PREGAIN_DB -60
#define MIN_STREAM_GAIN_DB -96
#define MIXER_GAIN_REG 4
#define PREGAIN_REG 9
#define MIXER_MUTE_REG 10
#define MIXER_RAMP_REG 15
#define MAX_RAMP_NUM_SAMPLES 0x00ffffff
static int kas_playback_ramp_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
ucontrol->value.integer.value[0] =
pdata->stream_ramp[0][mc->reg - MIXER_RAMP_REG];
ucontrol->value.integer.value[1] =
pdata->stream_ramp[1][mc->reg - MIXER_RAMP_REG];
return 0;
}
static int kas_playback_ramp_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
pdata->stream_ramp[0][mc->reg - MIXER_RAMP_REG] =
ucontrol->value.integer.value[0];
pdata->stream_ramp[1][mc->reg - MIXER_RAMP_REG] =
ucontrol->value.integer.value[1];
return 0;
}
static int kas_playback_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
ucontrol->value.integer.value[0] =
pdata->stream_mute[mc->reg - MIXER_MUTE_REG];
return 0;
}
static int kas_playback_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int mute = ucontrol->value.integer.value[0];
pdata->stream_mute[mc->reg - MIXER_MUTE_REG] = mute;
if (mute)
kalimba_set_stream_volume(mc->reg - MIXER_MUTE_REG,
MIN_STREAM_GAIN_DB,
pdata->stream_ramp[1][mc->reg - MIXER_MUTE_REG]);
else
kalimba_set_stream_volume(mc->reg - MIXER_MUTE_REG,
MIN_STREAM_GAIN_DB +
pdata->stream_volume[mc->reg - MIXER_MUTE_REG],
pdata->stream_ramp[1][mc->reg - MIXER_MUTE_REG]);
return 0;
}
static int kas_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
if (mc->reg < MIXER_GAIN_REG)
ucontrol->value.integer.value[0] =
pdata->pre_channel_volume[mc->reg];
else if (mc->reg == PREGAIN_REG)
ucontrol->value.integer.value[0] = pdata->pre_gain;
else
ucontrol->value.integer.value[0] =
pdata->stream_volume[mc->reg - MIXER_GAIN_REG];
return 0;
}
static int kas_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct kas_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int mute = 0;
if (mc->reg < MIXER_GAIN_REG) {
pdata->pre_channel_volume[mc->reg] =
ucontrol->value.integer.value[0];
kalimba_set_channel_volume(mc->reg, MIN_CHANNEL_GAIN_DB +
pdata->pre_channel_volume[mc->reg]);
} else if (mc->reg == PREGAIN_REG) {
pdata->pre_gain = ucontrol->value.integer.value[0];
kalimba_set_music_passthrough_volume(
MIN_MUSIC_PREGAIN_DB + pdata->pre_gain);
} else {
pdata->stream_volume[mc->reg - MIXER_GAIN_REG] =
ucontrol->value.integer.value[0];
mute = pdata->stream_mute[mc->reg - MIXER_GAIN_REG];
if (mute == 0)
kalimba_set_stream_volume(mc->reg - MIXER_GAIN_REG,
MIN_STREAM_GAIN_DB +
pdata->stream_volume[mc->reg - MIXER_GAIN_REG],
pdata->stream_ramp[0][mc->reg - MIXER_GAIN_REG]
);
}
return 0;
}
/* TLV used by volume control volumes */
static const DECLARE_TLV_DB_SCALE(kas_channel_vol_tlv,
MIN_CHANNEL_GAIN_DB * 100, 100, 0);
/* TLV used by stream volumes */
static const DECLARE_TLV_DB_SCALE(kas_stream_vol_tlv,
MIN_STREAM_GAIN_DB * 100, 100, 0);
/* TLV used by music pregain */
static const DECLARE_TLV_DB_SCALE(kas_music_pregain_tlv,
MIN_MUSIC_PREGAIN_DB * 100, 100, 0);
static const struct snd_kcontrol_new kas_controls[] = {
SOC_SINGLE_EXT_TLV("Front Left Playback Volume", 0, 0, 129, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_channel_vol_tlv),
SOC_SINGLE_EXT_TLV("Front Right Playback Volume", 1, 0, 129, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_channel_vol_tlv),
SOC_SINGLE_EXT_TLV("Rear Left Playback Volume", 2, 0, 129, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_channel_vol_tlv),
SOC_SINGLE_EXT_TLV("Rear Right Playback Volume", 3, 0, 129, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_channel_vol_tlv),
SOC_SINGLE_EXT_TLV("Music Stream Playback Volume", 4, 0, 96, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_stream_vol_tlv),
SOC_SINGLE_EXT_TLV("Navigation Stream Playback Volume", 5, 0, 96, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_stream_vol_tlv),
SOC_SINGLE_EXT_TLV("Alarm Stream Playback Volume", 6, 0, 96, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_stream_vol_tlv),
SOC_SINGLE_EXT_TLV("Multimedia Volume", 7, 0, 96, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_stream_vol_tlv),
SOC_SINGLE_EXT_TLV("Voicecall Volume", 8, 0, 96, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_stream_vol_tlv),
SOC_SINGLE_EXT_TLV("Music pregain Volume", 9, 0, 60, 0,
kas_playback_volume_get, kas_playback_volume_put,
kas_music_pregain_tlv),
SOC_SINGLE_EXT("Music Stream Mute", 10, 0, 1, 0,
kas_playback_mute_get, kas_playback_mute_put),
SOC_SINGLE_EXT("Navigation Stream Mute", 11, 0, 1, 0,
kas_playback_mute_get, kas_playback_mute_put),
SOC_SINGLE_EXT("Alarm Stream Mute", 12, 0, 1, 0,
kas_playback_mute_get, kas_playback_mute_put),
SOC_SINGLE_EXT("Multimedia Mute", 13, 0, 1, 0,
kas_playback_mute_get, kas_playback_mute_put),
SOC_SINGLE_EXT("Voicecall Mute", 14, 0, 1, 0,
kas_playback_mute_get, kas_playback_mute_put),
SOC_DOUBLE_EXT("Music Stream Ramp",
15, 0, 1, MAX_RAMP_NUM_SAMPLES, 0,
kas_playback_ramp_get, kas_playback_ramp_put),
SOC_DOUBLE_EXT("Navigation Stream Ramp",
16, 0, 1, MAX_RAMP_NUM_SAMPLES, 0,
kas_playback_ramp_get, kas_playback_ramp_put),
SOC_DOUBLE_EXT("Alarm Stream Ramp",
17, 0, 1, MAX_RAMP_NUM_SAMPLES, 0,
kas_playback_ramp_get, kas_playback_ramp_put),
SOC_DOUBLE_EXT("Multimedia Ramp",
18, 0, 1, MAX_RAMP_NUM_SAMPLES, 0,
kas_playback_ramp_get, kas_playback_ramp_put),
SOC_DOUBLE_EXT("Voicecall Ramp",
19, 0, 1, MAX_RAMP_NUM_SAMPLES, 0,
kas_playback_ramp_get, kas_playback_ramp_put),
/* peq */
KAS_PEQ_CONTROLS("User PEQ", USER_PEQ_BASE),
KAS_PEQ_CONTROLS("Spk1 PEQ", SPK1_PEQ_BASE),
KAS_PEQ_CONTROLS("Spk2 PEQ", SPK2_PEQ_BASE),
KAS_PEQ_CONTROLS("Spk3 PEQ", SPK3_PEQ_BASE),
KAS_PEQ_CONTROLS("Spk4 PEQ", SPK4_PEQ_BASE),
/* dbe */
SOC_SINGLE_EXT("DBE Switch Mode", DBE_CNTL_SWITCH, 0, 2, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT("DBE Xover FC", DBE_PARAM_XOVER_FC, 40, 1000, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT("DBE Mix Balance", DBE_PARAM_MIX_BALANCE, 0, 100, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT("DBE Effect Strength",
DBE_PARAM_EFFECT_STRENGTH, 0, 100, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT_TLV("DBE Amp Limit", DBE_PARAM_AMP_LIMIT, 0, 32, 0,
kas_playback_dbe_get, kas_playback_dbe_put,
kas_dbe_gain_tlv),
SOC_SINGLE_EXT("DBE LP FC", DBE_PARAM_LP_FC, 50, 300, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT("DBE HP FC", DBE_PARAM_HP_FC, 50, 300, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
SOC_SINGLE_EXT("DBE Harm Content", DBE_PARAM_HARM_CONTENT, 0, 100, 0,
kas_playback_dbe_get, kas_playback_dbe_put),
/* delay */
SOC_SINGLE_EXT("Delay Chan1 Delay", DELAY_PARAM_CHAN1_DELAY, 0, 768, 0,
kas_playback_delay_get, kas_playback_delay_put),
SOC_SINGLE_EXT("Delay Chan2 Delay", DELAY_PARAM_CHAN2_DELAY, 0, 768, 0,
kas_playback_delay_get, kas_playback_delay_put),
SOC_SINGLE_EXT("Delay Chan3 Delay", DELAY_PARAM_CHAN3_DELAY, 0, 768, 0,
kas_playback_delay_get, kas_playback_delay_put),
SOC_SINGLE_EXT("Delay Chan4 Delay", DELAY_PARAM_CHAN4_DELAY, 0, 768, 0,
kas_playback_delay_get, kas_playback_delay_put)
};
static int kas_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
if (open_stream(rtd->cpu_dai->id) == PIPELINE_BUSY)
return -EBUSY;
pcm_data->substream = substream;
snd_soc_set_runtime_hwparams(substream, &kas_pcm_hardware);
return snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
}
static int kas_pcm_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
close_stream(rtd->cpu_dai->id);
return 0;
}
static int kas_data_notify(u16 message, void *priv_data, u16 *message_data)
{
struct kas_pcm_data *pcm_data = (struct kas_pcm_data *)priv_data;
if (message_data[0] == pcm_data->kalimba_notify_ep_id) {
pcm_data->pos = (message_data[1] << 16 | message_data[2]) * 4;
snd_pcm_period_elapsed(pcm_data->substream);
return ACTION_HANDLED;
} else
return ACTION_NONE;
}
static int kas_pcm_generic_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct snd_dma_buffer *dmab;
int stream = rtd->cpu_dai->id;
if (stream == MUSIC_STREAM) {
switch (params_channels(params)) {
case 1:
stream = MUSIC_MONO_STREAM;
break;
case 2:
stream = MUSIC_STEREO_STREAM;
break;
case 4:
stream = MUSIC_4CHANNELS_STREAM;
break;
default:
break;
}
}
if (stream == ANALOG_CAPTURE_STREAM) {
switch (params_channels(params)) {
case 1:
stream = CAPTURE_MONO_STREAM;
break;
case 2:
stream = CAPTURE_STEREO_STREAM;
break;
default:
break;
}
}
dmab = snd_pcm_get_dma_buf(substream);
pcm_data->sw_ep_handle->buff_addr = dmab->addr;
pcm_data->sw_ep_handle->buff_length =
params_buffer_bytes(params) / 4;
pcm_data->kalimba_notify_ep_id = prepare_stream(stream,
params_channels(params),
pcm_data->sw_ep_handle_phy_addr, params_rate(params),
1, params_period_bytes(params) / 4);
if (playback)
pcm_data->action_id = register_kalimba_msg_action(
DATA_CONSUMED, kas_data_notify, pcm_data);
else
pcm_data->action_id = register_kalimba_msg_action(
DATA_PRODUCED, kas_data_notify, pcm_data);
return 0;
}
static int kas_pcm_voicecall_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
if (playback) {
memset(kcm->playback_usp_sco_ep.buff, 0,
kcm->playback_usp_sco_ep.buff_bytes);
sirf_usp_pcm_params(bt_usp_port, playback,
kcm->playback_usp_sco_ep.channels,
kcm->playback_usp_sco_ep.sample_rate);
} else {
memset(kcm->capture_usp_sco_ep.buff, 0,
kcm->capture_usp_sco_ep.buff_bytes);
sirf_usp_pcm_params(bt_usp_port, playback,
kcm->capture_usp_sco_ep.channels,
kcm->capture_usp_sco_ep.sample_rate);
}
prepare_stream(rtd->cpu_dai->id, params_channels(params), 0,
params_rate(params), 1, params_period_bytes(params) / 4);
return 0;
}
static int kas_pcm_a2dp_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
memset(kcm->capture_usp_a2dp_ep.buff, 0,
kcm->capture_usp_a2dp_ep.buff_bytes);
sirf_usp_pcm_params(bt_usp_port, 0, kcm->capture_usp_a2dp_ep.channels,
kcm->capture_usp_a2dp_ep.sample_rate);
prepare_stream(rtd->cpu_dai->id, params_channels(params), 0,
params_rate(params), 1, params_period_bytes(params) / 4);
return 0;
}
static int kas_pcm_iacc_loopback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
memset(kcm->playback_iacc_ep.buff, 0,
kcm->playback_iacc_ep.buff_bytes);
prepare_stream(rtd->cpu_dai->id, params_channels(params), 0,
params_rate(params), 1,
params_period_bytes(params) / 4);
} else
memset(kcm->capture_iacc_stereo_ep.buff, 0,
kcm->capture_iacc_stereo_ep.buff_bytes);
return 0;
}
static int kas_pcm_i2s_to_iacc_loopback_hw_params(
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
memset(kcm->playback_iacc_ep.buff, 0,
kcm->playback_iacc_ep.buff_bytes);
memset(kcm->capture_i2s_stereo_ep.buff, 0,
kcm->capture_i2s_stereo_ep.buff_bytes);
sirf_i2s_params(params_channels(params), params_rate(params),
!i2s_master);
prepare_stream(rtd->cpu_dai->id, params_channels(params), 0,
params_rate(params), i2s_master,
params_period_bytes(params) / 4);
return 0;
}
static int kas_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
int ret;
pcm_data->pos = 0;
pcm_data->last_appl_ptr = 0;
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0) {
dev_err(rtd->dev, "allocate %d bytes for PCM failed: %d\n",
params_buffer_bytes(params), ret);
return ret;
}
if (pcm_data->hw_params) {
ret = pcm_data->hw_params(substream, params);
if (ret < 0)
goto failed;
}
pcm_data->kas_started = true;
return 0;
failed:
snd_pcm_lib_free_pages(substream);
return ret;
}
static int kas_pcm_generic_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
int stream = rtd->cpu_dai->id;
if (stream == MUSIC_STREAM) {
switch (substream->runtime->channels) {
case 1:
stream = MUSIC_MONO_STREAM;
break;
case 2:
stream = MUSIC_STEREO_STREAM;
break;
case 4:
stream = MUSIC_4CHANNELS_STREAM;
break;
default:
break;
}
}
if (stream == ANALOG_CAPTURE_STREAM) {
switch (substream->runtime->channels) {
case 1:
stream = CAPTURE_MONO_STREAM;
break;
case 2:
stream = CAPTURE_STEREO_STREAM;
break;
default:
break;
}
}
stop_stream(stream);
destroy_stream(stream);
unregister_kalimba_msg_action(pcm_data->action_id);
return 0;
}
static int kas_pcm_voicecall_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
stop_stream(rtd->cpu_dai->id);
destroy_stream(rtd->cpu_dai->id);
return 0;
}
static int kas_pcm_a2dp_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
stop_stream(rtd->cpu_dai->id);
destroy_stream(rtd->cpu_dai->id);
return 0;
}
static int kas_pcm_iacc_loopback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stop_stream(rtd->cpu_dai->id);
destroy_stream(rtd->cpu_dai->id);
}
return 0;
}
static int kas_pcm_i2s_to_iacc_loopback_hw_free(
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
stop_stream(rtd->cpu_dai->id);
destroy_stream(rtd->cpu_dai->id);
return 0;
}
static int kas_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
if (!pcm_data->kas_started)
return 0;
if (pcm_data->hw_free)
pcm_data->hw_free(substream);
snd_pcm_lib_free_pages(substream);
pcm_data->kas_started = false;
return 0;
}
static int kas_pcm_generic_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct kcm_t *kcm = pdata->kcm;
int stream = rtd->cpu_dai->id;
if (stream == MUSIC_STREAM) {
switch (substream->runtime->channels) {
case 1:
stream = MUSIC_MONO_STREAM;
break;
case 2:
stream = MUSIC_STEREO_STREAM;
break;
case 4:
stream = MUSIC_4CHANNELS_STREAM;
break;
default:
break;
}
}
if (stream == ANALOG_CAPTURE_STREAM) {
switch (substream->runtime->channels) {
case 1:
stream = CAPTURE_MONO_STREAM;
break;
case 2:
stream = CAPTURE_STEREO_STREAM;
break;
default:
break;
}
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (playback)
iacc_start(playback, kcm->playback_iacc_ep.channels);
else
iacc_start(playback, substream->runtime->channels);
start_stream(stream,
!!atomic_read(&substream->mmap_count));
if (playback)
data_produced(pcm_data->kalimba_notify_ep_id);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (playback) {
iacc_stop(playback);
/* Buffer pointer must be reset */
pcm_data->pos = 0;
} else
iacc_stop(playback);
break;
default:
return -EINVAL;
}
return 0;
}
static int kas_pcm_voicecall_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct kcm_t *kcm = pdata->kcm;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (playback) {
sirf_usp_pcm_start(bt_usp_port, 0);
iacc_start(playback, kcm->playback_iacc_ep.channels);
} else {
iacc_start(playback, kcm->capture_iacc_sco_ep.channels);
sirf_usp_pcm_start(bt_usp_port, 1);
}
start_stream(rtd->cpu_dai->id, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (playback) {
sirf_usp_pcm_stop(bt_usp_port, 0);
iacc_stop(1);
} else {
iacc_stop(0);
sirf_usp_pcm_stop(bt_usp_port, 1);
}
break;
}
return 0;
}
static int kas_pcm_a2dp_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
memset(kcm->capture_usp_a2dp_ep.buff, 0,
kcm->capture_usp_a2dp_ep.buff_bytes);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
iacc_start(1, kcm->playback_iacc_ep.channels);
sirf_usp_pcm_start(bt_usp_port, 0);
start_stream(rtd->cpu_dai->id, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
iacc_stop(1);
sirf_usp_pcm_stop(bt_usp_port, 0);
break;
}
return 0;
}
static int kas_pcm_iacc_loopback_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int channels;
if (playback)
channels = kcm->playback_iacc_ep.channels;
else
channels = kcm->capture_iacc_stereo_ep.channels;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
iacc_start(playback, channels);
if (playback)
start_stream(rtd->cpu_dai->id, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
iacc_stop(playback);
break;
}
return 0;
}
static int kas_pcm_i2s_to_iacc_loopback_trigger(
struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kcm_t *kcm = pdata->kcm;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
iacc_start(1, kcm->playback_iacc_ep.channels);
sirf_i2s_start(0);
start_stream(rtd->cpu_dai->id, i2s_master);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
iacc_stop(0);
sirf_i2s_stop(0);
}
return 0;
}
static int kas_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
int ret = 0;
if (pcm_data->trigger)
ret = pcm_data->trigger(substream, cmd);
return ret;
}
static snd_pcm_uframes_t kas_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
return bytes_to_frames(substream->runtime, pcm_data->pos);
}
static int kas_pcm_ack(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data =
&pdata->pcm[rtd->cpu_dai->id][substream->stream];
if (runtime->status->state != SNDRV_PCM_STATE_RUNNING)
return 0;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
return 0;
if (runtime->control->appl_ptr - pcm_data->last_appl_ptr >=
runtime->period_size)
pcm_data->last_appl_ptr = runtime->control->appl_ptr;
else
return 0;
pcm_data->sw_ep_handle->write_pointer = frames_to_bytes(runtime,
pcm_data->last_appl_ptr % runtime->buffer_size) / 4;
data_produced(pcm_data->kalimba_notify_ep_id);
return 0;
}
static struct snd_pcm_ops kas_pcm_ops = {
.open = kas_pcm_open,
.close = kas_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = kas_pcm_hw_params,
.hw_free = kas_pcm_hw_free,
.trigger = kas_pcm_trigger,
.pointer = kas_pcm_pointer,
.ack = kas_pcm_ack,
};
static int kas_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm *pcm = rtd->pcm;
struct snd_card *card = rtd->card->snd_card;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct kas_priv_data *pdata =
snd_soc_platform_get_drvdata(rtd->platform);
struct kas_pcm_data *pcm_data;
struct snd_pcm_substream *substream;
int ret = 0;
int stream;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
/* Enable PCM operations are in non-atomic context */
pcm->nonatomic = true;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream ||
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_DEV_IRAM,
dev,
kas_pcm_hardware.buffer_bytes_max,
kas_pcm_hardware.buffer_bytes_max);
if (ret) {
dev_err(rtd->dev, "dma buffer allocation failed %d\n",
ret);
return ret;
}
}
for (stream = 0; stream < 2; stream++) {
const char *stream_name = rtd->dai_link->stream_name;
substream = pcm->streams[stream].substream;
if (!substream)
continue;
pcm_data = &pdata->pcm[rtd->cpu_dai->id][substream->stream];
pcm_data->sw_ep_handle = dma_alloc_coherent(rtd->platform->dev,
sizeof(struct endpoint_handle),
&pcm_data->sw_ep_handle_phy_addr, GFP_KERNEL);
if (!(strcmp(stream_name, "Voicecall-bt-to-iacc") &&
strcmp(stream_name, "Voicecall-iacc-to-bt"))) {
pcm_data->hw_params = kas_pcm_voicecall_hw_params;
pcm_data->hw_free = kas_pcm_voicecall_hw_free;
pcm_data->trigger = kas_pcm_voicecall_trigger;
} else if (!strcmp(stream_name, "A2DP Playback")) {
pcm_data->hw_params = kas_pcm_a2dp_hw_params;
pcm_data->hw_free = kas_pcm_a2dp_hw_free;
pcm_data->trigger = kas_pcm_a2dp_trigger;
} else if (!(strcmp(stream_name, "Iacc-loopback-playback") &&
strcmp(stream_name, "Iacc-loopback-capture"))) {
pcm_data->hw_params = kas_pcm_iacc_loopback_hw_params;
pcm_data->hw_free = kas_pcm_iacc_loopback_hw_free;
pcm_data->trigger = kas_pcm_iacc_loopback_trigger;
} else if (!strcmp(stream_name, "I2S-to-iacc-loopback")) {
pcm_data->hw_params =
kas_pcm_i2s_to_iacc_loopback_hw_params;
pcm_data->hw_free =
kas_pcm_i2s_to_iacc_loopback_hw_free;
pcm_data->trigger =
kas_pcm_i2s_to_iacc_loopback_trigger;
} else {
pcm_data->hw_params = kas_pcm_generic_hw_params;
pcm_data->hw_free = kas_pcm_generic_hw_free;
pcm_data->trigger = kas_pcm_generic_trigger;
}
}
return ret;
}
static void kas_pcm_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_soc_pcm_runtime *rtd;
struct kas_priv_data *pdata;
struct kas_pcm_data *pcm_data;
int stream;
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
rtd = substream->private_data;
pdata = snd_soc_platform_get_drvdata(rtd->platform);
pcm_data = &pdata->pcm[rtd->cpu_dai->id][substream->stream];
dma_free_coherent(rtd->platform->dev,
sizeof(struct endpoint_handle),
pcm_data->sw_ep_handle,
pcm_data->sw_ep_handle_phy_addr);
}
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static int kas_pcm_probe(struct snd_soc_platform *platform)
{
struct kas_priv_data *priv_data;
priv_data = devm_kzalloc(platform->dev, sizeof(*priv_data), GFP_KERNEL);
if (priv_data == NULL)
return -ENOMEM;
snd_soc_platform_set_drvdata(platform, priv_data);
priv_data->kcm = kcm_init(bt_usp_port, platform->dev);
if (IS_ERR(priv_data->kcm))
return PTR_ERR(priv_data->kcm);
return 0;
}
static int kas_pcm_remove(struct snd_soc_platform *platform)
{
kcm_deinit(platform->dev);
return 0;
}
static struct snd_soc_platform_driver kas_soc_platform = {
.probe = kas_pcm_probe,
.remove = kas_pcm_remove,
.ops = &kas_pcm_ops,
.pcm_new = kas_pcm_new,
.pcm_free = kas_pcm_free,
};
#define KAS_RATES (SNDRV_PCM_RATE_CONTINUOUS | \
SNDRV_PCM_RATE_8000_192000)
#define KAS_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_dai_driver kas_dais[] = {
{
.name = "Music Pin",
.playback = {
.stream_name = "Music Playback",
.channels_min = 1,
.channels_max = 4,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Navigation Pin",
.playback = {
.stream_name = "Navigation Playback",
.channels_min = 1,
.channels_max = 4,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Alarm Pin",
.playback = {
.stream_name = "Alarm Playback",
.channels_min = 1,
.channels_max = 4,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "A2DP Pin",
.playback = {
.stream_name = "A2DP Playback",
.channels_min = 1,
.channels_max = 4,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Voicecall-bt-to-iacc Pin",
.playback = {
.stream_name = "Voicecall-bt-to-iacc",
.channels_min = 1,
.channels_max = 4,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Voicecall-playback Pin",
.playback = {
.stream_name = "Voicecall-playback",
.channels_min = 1,
.channels_max = 1,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Iacc-loopback-playback Pin",
.playback = {
.stream_name = "Iacc-loopback-playback",
.channels_min = 2,
.channels_max = 2,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "I2S-to-iacc-loopback Pin",
.playback = {
.stream_name = "I2S-to-iacc-loopback",
.channels_min = 2,
.channels_max = 2,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Capture Pin",
.capture = {
.stream_name = "Analog Capture",
.channels_min = 1,
.channels_max = 2,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Voicecall-iacc-to-bt Pin",
.capture = {
.stream_name = "Voicecall-iacc-to-bt",
.channels_min = 1,
.channels_max = 1,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Voicecall-capture Pin",
.capture = {
.stream_name = "Voicecall-capture",
.channels_min = 1,
.channels_max = 1,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
},
{
.name = "Iacc-loopback-capture Pin",
.capture = {
.stream_name = "Iacc-loopback-capture",
.channels_min = 2,
.channels_max = 2,
.rates = KAS_RATES,
.formats = KAS_FORMATS,
},
}
};
static const struct snd_soc_dapm_widget widgets[] = {
/* Backend DAIs */
SND_SOC_DAPM_AIF_IN("Codec IN", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Codec OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
/* Global Playback Mixer */
SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
};
static const struct snd_soc_dapm_route graph[] = {
/* Playback Mixer */
{"Playback VMixer", NULL, "Music Playback"},
{"Playback VMixer", NULL, "Navigation Playback"},
{"Playback VMixer", NULL, "Alarm Playback"},
{"Playback VMixer", NULL, "A2DP Playback"},
{"Playback VMixer", NULL, "Voicecall-bt-to-iacc"},
{"Playback VMixer", NULL, "Voicecall-playback"},
{"Playback VMixer", NULL, "Iacc-loopback-playback"},
{"Playback VMixer", NULL, "I2S-to-iacc-loopback"},
{"Codec OUT", NULL, "Playback VMixer"},
{"Analog Capture", NULL, "Codec IN"},
{"Voicecall-iacc-to-bt", NULL, "Codec IN"},
{"Voicecall-capture", NULL, "Codec IN"},
{"Iacc-loopback-capture", NULL, "Codec IN"},
};
static const struct snd_soc_component_driver kas_dai_component = {
.name = "kas-dai",
.controls = kas_controls,
.num_controls = ARRAY_SIZE(kas_controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = graph,
.num_dapm_routes = ARRAY_SIZE(graph),
};
static int kas_pcm_dev_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
if (of_get_property(np, "i2s-master", NULL))
i2s_master = 1;
of_property_read_u32(pdev->dev.of_node, "bt-usp-port", &bt_usp_port);
ret = devm_snd_soc_register_platform(&pdev->dev, &kas_soc_platform);
if (ret < 0)
return ret;
ret = devm_snd_soc_register_component(&pdev->dev, &kas_dai_component,
kas_dais, ARRAY_SIZE(kas_dais));
return ret;
}
static const struct of_device_id kas_pcm_of_match[] = {
{ .compatible = "csr,kas-pcm", },
{}
};
MODULE_DEVICE_TABLE(of, kas_pcm_of_match);
static struct platform_driver kas_pcm_driver = {
.driver = {
.name = "kas-pcm-audio",
.owner = THIS_MODULE,
.of_match_table = kas_pcm_of_match,
},
.probe = kas_pcm_dev_probe,
};
module_platform_driver(kas_pcm_driver);
MODULE_DESCRIPTION("SiRF Kalimba pcm audio driver");
MODULE_AUTHOR("RongJun Ying <Rongjun.Ying@csr.com>");