blob: 116b19fc80c4e8bec86ae6ca51e25afa30a32f2f [file] [log] [blame]
/*
* pacific_arizona.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <sound/tlv.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/wakelock.h>
#if defined(CONFIG_SWITCH_ANTENNA_EARJACK) \
|| defined(CONFIG_SWITCH_ANTENNA_EARJACK_IF)
#include <linux/antenna_switch.h>
#endif
#include <linux/mfd/arizona/registers.h>
#include <linux/mfd/arizona/core.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include "i2s.h"
#include "i2s-regs.h"
#include "../codecs/florida.h"
#include "../codecs/clearwater.h"
#if defined(CONFIG_SND_SOC_MAXIM_DSM_A)
#include <sound/maxim_dsm_a.h>
#endif
#if defined(CONFIG_SND_SOC_MAXIM_DSM_CAL)
#include <sound/maxim_dsm_cal.h>
#endif
#if defined(CONFIG_SND_ESA_SA_EFFECT)
#include "esa_sa_effect.h"
#endif
/* PACIFIC use CLKOUT from AP */
#define PACIFIC_MCLK_FREQ 26000000
#define PACIFIC_AUD_PLL_FREQ 393216018
#define PACIFIC_DEFAULT_MCLK1 26000000
#define PACIFIC_DEFAULT_MCLK2 32768
#define PACIFIC_RUN_MAINMIC 2
#define PACIFIC_RUN_EARMIC 1
/* XXX_FLLn is CODEC_FLLn */
#define PACIFIC_FLL1 1
#define PACIFIC_FLL2 2
#define PACIFIC_FLL1_REFCLK 3
#define PACIFIC_FLL2_REFCLK 4
#define PACIFIC_FLL3 5
#define PACIFIC_FLL3_REFCLK 6
#define DSP6_XM_BASE 0x320000
#define DSP6_ZM_BASE 0x360000
#define EZ2CTRL_WORDID_OFF 0x3f
#define SENSORY_PID_SVSCORE 0x44
#define SENSORY_PID_FINALSCORE 0x45
#define PID_NOISEFLOOR 0x49
#if defined(CONFIG_MFD_CLEARWATER)
#define PACIFIC_AIF_MAX 4
#else
#define PACIFIC_AIF_MAX 3
#endif
static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0);
enum {
PACIFIC_PLAYBACK_DAI,
PACIFIC_VOICECALL_DAI,
PACIFIC_BT_SCO_DAI,
PACIFIC_MULTIMEDIA_DAI,
};
#if defined(CONFIG_SND_SOC_MAXIM_DSM_A)
enum {
BYTELOGINFO,
INTLOGINFO,
AFTERBYTELOGINFO,
AFTERINTLOGINFO,
};
#endif
struct arizona_machine_priv {
int mic_bias_gpio;
struct snd_soc_codec *codec;
struct snd_soc_dai *aif[PACIFIC_AIF_MAX];
int voice_uevent;
int seamless_voicewakeup;
int voicetriginfo;
struct input_dev *input;
int aif2mode;
int voice_key_event;
struct clk *mclk;
struct wake_lock wake_lock;
unsigned int hp_impedance_step;
bool ear_mic;
int sysclk_rate;
int dspclk_rate;
int asyncclk_rate;
int word_id_addr;
int sv_score_addr;
int final_score_addr;
int noise_floor_addr;
int energy_l_addr;
int energy_r_addr;
struct regmap *regmap_dsp;
u32 aif_format[3];
u32 aif_format_tdm[3];
int (*external_amp)(int onoff);
#if defined(CONFIG_SND_SOC_MAXIM_DSM_CAL)
int dsm_cal_read_cnt;
int dsm_cal_rdc;
int dsm_cal_temp;
#endif
};
static struct class *svoice_class;
static struct device *keyword_dev;
static unsigned int keyword_type;
static struct arizona_machine_priv pacific_wm5110_priv = {
.sysclk_rate = 147456000,
.asyncclk_rate = 49152000,
.word_id_addr = 0x39007b,
.sv_score_addr = 0x390085,
.final_score_addr = 0x390087,
.noise_floor_addr = 0x39008F,
.energy_l_addr = ARIZONA_DSP3_SCRATCH_1,
.energy_r_addr = ARIZONA_DSP3_SCRATCH_3,
};
static struct arizona_machine_priv pacific_wm1840_priv = {
.sysclk_rate = 98304000,
.dspclk_rate = 147456000,
.asyncclk_rate = 98304000,
.word_id_addr = DSP6_XM_BASE + (EZ2CTRL_WORDID_OFF * 2),
.sv_score_addr = DSP6_XM_BASE + (SENSORY_PID_SVSCORE * 2),
.final_score_addr = DSP6_XM_BASE + (SENSORY_PID_FINALSCORE * 2),
.noise_floor_addr = DSP6_XM_BASE + (PID_NOISEFLOOR * 2),
.energy_l_addr = CLEARWATER_DSP3_SCRATCH_0_1,
.energy_r_addr = CLEARWATER_DSP3_SCRATCH_2_3,
};
const char *aif2_mode_text[] = {
"Slave", "Master", "CLKOnly"
};
static const char * const voicecontrol_mode_text[] = {
"Normal", "LPSD"
};
static const struct soc_enum aif2_mode_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text),
};
static const struct soc_enum voicecontrol_mode_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(voicecontrol_mode_text),
voicecontrol_mode_text),
};
static const struct snd_soc_component_driver pacific_cmpnt = {
.name = "pacific-audio",
};
static struct {
int min; /* Minimum impedance */
int max; /* Maximum impedance */
unsigned int gain; /* Register value to set for this measurement */
} hp_gain_table[] = {
{ 0, 13, 0 },
{ 14, 42, 0 },
{ 43, 100, 4 },
{ 101, 200, 8 },
{ 201, 450, 12 },
{ 451, 1000, 14 },
{ 1001, INT_MAX, 0 },
};
#if defined(CONFIG_SND_SOC_MAXIM_DSM_A)
static struct {
unsigned int start; /* start address */
unsigned int size; /* word size */
} dsm_log_info[] = {
{ 0x49007C, 44 },
{ 0x4900AA, 44 },
{ 0x4900DA, 80 },
{ 0x49012A, 90 },
};
static struct {
unsigned int start; /* start address */
unsigned int size; /* word size */
} dsm_param_info[] = {
{ 0x490052, 40 },
};
#endif
static struct snd_soc_codec *the_codec;
void pacific_arizona_hpdet_cb(unsigned int meas)
{
struct arizona_machine_priv *priv;
int jack_det;
int i, num_hp_gain_table;
WARN_ON(!the_codec);
if (!the_codec)
return;
priv = the_codec->component.card->drvdata;
if (meas == ARIZONA_HP_Z_OPEN)
jack_det = 0;
else
jack_det = 1;
dev_info(the_codec->dev, "%s(%d) meas(%d)\n", __func__, jack_det, meas);
#if defined(CONFIG_SWITCH_ANTENNA_EARJACK) \
|| defined(CONFIG_SWITCH_ANTENNA_EARJACK_IF)
/* Notify jack condition to other devices */
antenna_switch_work_earjack(jack_det);
#endif
num_hp_gain_table = (int) ARRAY_SIZE(hp_gain_table);
for (i = 0; i < num_hp_gain_table; i++) {
if (meas < hp_gain_table[i].min || meas > hp_gain_table[i].max)
continue;
dev_info(the_codec->dev, "SET GAIN %d step for %d ohms\n",
hp_gain_table[i].gain, meas);
priv->hp_impedance_step = hp_gain_table[i].gain;
}
}
void pacific_arizona_micd_cb(bool mic)
{
struct arizona_machine_priv *priv;
WARN_ON(!the_codec);
if (!the_codec)
return;
priv = the_codec->component.card->drvdata;
priv->ear_mic = mic;
dev_info(the_codec->dev, "%s: ear_mic = %d\n", __func__, priv->ear_mic);
}
void pacific_update_impedance_table(struct device_node *np)
{
int len = ARRAY_SIZE(hp_gain_table);
u32 data[len * 3];
int i, shift;
WARN_ON(!the_codec);
if (!the_codec)
return;
if (!of_property_read_u32_array(np, "imp_table", data, (len * 3))) {
dev_info(the_codec->dev, "%s: data from DT\n", __func__);
for (i = 0; i < len; i++) {
hp_gain_table[i].min = data[i * 3];
hp_gain_table[i].max = data[(i * 3) + 1];
hp_gain_table[i].gain = data[(i * 3) + 2];
}
}
if (!of_property_read_u32(np, "imp_shift", &shift)) {
dev_info(the_codec->dev, "%s: shift = %d\n", __func__, shift);
for (i = 0; i < len; i++) {
if (hp_gain_table[i].min != 0)
hp_gain_table[i].min += shift;
if ((hp_gain_table[i].max + shift) < INT_MAX)
hp_gain_table[i].max += shift;
}
}
}
static int arizona_put_impedance_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int err;
unsigned int val, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
val += priv->hp_impedance_step;
dev_info(codec->dev,
"SET GAIN %d according to impedance, moved %d step\n",
val, priv->hp_impedance_step);
if (invert)
val = max - val;
val_mask = mask << shift;
val = val << shift;
err = snd_soc_update_bits(codec, reg, val_mask, val);
if (err < 0)
return err;
return err;
}
static int get_aif2_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
ucontrol->value.integer.value[0] = priv->aif2mode;
return 0;
}
static int set_aif2_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
if (ucontrol->value.integer.value[0] > ARRAY_SIZE(aif2_mode_text)
|| ucontrol->value.integer.value[0] < 0) {
dev_err(codec->dev, "set_aif2_mode is invalid value\n");
return -EINVAL;
}
priv->aif2mode = ucontrol->value.integer.value[0];
dev_info(codec->dev, "set aif2 mode: %s\n",
aif2_mode_text[priv->aif2mode]);
return 0;
}
static int get_voicecontrol_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
if (priv->seamless_voicewakeup) {
ucontrol->value.integer.value[0] = priv->voice_uevent;
} else {
if (priv->voice_key_event == KEY_VOICE_WAKEUP)
ucontrol->value.integer.value[0] = 0;
else
ucontrol->value.integer.value[0] = 1;
}
return 0;
}
static int set_voicecontrol_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
int voicecontrol_mode;
voicecontrol_mode = ucontrol->value.integer.value[0];
if (priv->seamless_voicewakeup) {
priv->voice_uevent = voicecontrol_mode;
dev_info(codec->dev, "set voice control mode: %s\n",
voicecontrol_mode_text[priv->voice_uevent]);
} else {
if (voicecontrol_mode == 0) {
priv->voice_key_event = KEY_VOICE_WAKEUP;
} else if (voicecontrol_mode == 1) {
priv->voice_key_event = KEY_LPSD_WAKEUP;
} else {
dev_err(the_codec->component.card->dev,
"Invalid voice control mode =%d", voicecontrol_mode);
return 0;
}
dev_info(codec->dev, "set voice control mode: %s key_event = %d\n",
voicecontrol_mode_text[voicecontrol_mode],
priv->voice_key_event);
}
return 0;
}
static int get_voice_tracking_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
unsigned int energy_l, energy_r;
regmap_read(priv->regmap_dsp, priv->energy_l_addr, &energy_l);
energy_l &= 0xFFFF0000;
regmap_read(priv->regmap_dsp, priv->energy_r_addr, &energy_r);
energy_r = (energy_r >> 16);
ucontrol->value.enumerated.item[0] = (energy_l | energy_r);
dev_info(codec->dev, "get voice tracking info :0x%08x\n",
ucontrol->value.enumerated.item[0]);
return 0;
}
static int set_voice_tracking_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
dev_info(codec->dev, "set voice tracking info\n");
return 0;
}
static int get_voice_trigger_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
ucontrol->value.integer.value[0] = priv->voicetriginfo;
return 0;
}
static int set_voice_trigger_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct arizona_machine_priv *priv = codec->component.card->drvdata;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
int offset_value;
unsigned int val, val1, val2;
priv->voicetriginfo = ucontrol->value.integer.value[0];
offset_value = priv->voicetriginfo - 250;
if (offset_value == 0)
val = 0;
else if (offset_value > 0)
val = (offset_value / 15) + 1;
else
val = 0xFFFFFF + (offset_value / 15);
val1 = val & 0xFFFF;
val2 = (val >> 16) & 0xFFFF;
snd_soc_write(codec, reg + 1, val1);
snd_soc_write(codec, reg, val2);
dev_info(codec->dev, "set voice trigger info: %d, val=0x%x\n",
priv->voicetriginfo, val);
return 0;
}
#if defined(CONFIG_SND_SOC_MAXIM_DSM_A)
static void create_dsm_log_dump(struct snd_soc_codec *codec)
{
int i;
unsigned int reg, val1, val2;
unsigned int byte_buf[dsm_log_info[BYTELOGINFO].size];
unsigned int int_buf[dsm_log_info[INTLOGINFO].size];
unsigned int after_byte_buf[dsm_log_info[AFTERBYTELOGINFO].size];
unsigned int after_int_buf[dsm_log_info[AFTERINTLOGINFO].size];
dev_info(codec->dev, "%s: ++\n", __func__);
memset(byte_buf, 0, sizeof(byte_buf));
memset(int_buf, 0, sizeof(int_buf));
memset(after_byte_buf, 0, sizeof(after_byte_buf));
memset(after_int_buf, 0, sizeof(after_int_buf));
for (i = 0; i < dsm_log_info[BYTELOGINFO].size; i++) {
reg = dsm_log_info[BYTELOGINFO].start + i*2;
val1 = (snd_soc_read(codec, reg)) & 0xFFFF;
val2 = (snd_soc_read(codec, reg + 1)) & 0xFFFF;
byte_buf[i] = ((val1 << 16) | val2);
}
for (i = 0; i < dsm_log_info[INTLOGINFO].size; i++) {
reg = dsm_log_info[INTLOGINFO].start + i*2;
val1 = (snd_soc_read(codec, reg)) & 0xFFFF;
val2 = (snd_soc_read(codec, reg + 1)) & 0xFFFF;
int_buf[i] = ((val1 << 16) | val2);
}
for (i = 0; i < dsm_log_info[AFTERBYTELOGINFO].size; i++) {
reg = dsm_log_info[AFTERBYTELOGINFO].start + i*2;
val1 = (snd_soc_read(codec, reg)) & 0xFFFF;
val2 = (snd_soc_read(codec, reg + 1)) & 0xFFFF;
after_byte_buf[i] = ((val1 << 16) | val2);
}
for (i = 0; i < dsm_log_info[AFTERINTLOGINFO].size; i++) {
reg = dsm_log_info[AFTERINTLOGINFO].start + i*2;
val1 = (snd_soc_read(codec, reg)) & 0xFFFF;
val2 = (snd_soc_read(codec, reg + 1)) & 0xFFFF;
after_int_buf[i] = ((val1 << 16) | val2);
}
maxdsm_a_log_update(byte_buf, int_buf, after_byte_buf, after_int_buf);
dev_info(codec->dev, "%s: --\n", __func__);
}
static int max9850x_get_dump_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
unsigned int val1, val2;
val1 = (snd_soc_read(codec, 0x49007c)) & 0xFFFF;
val2 = (snd_soc_read(codec, 0x49007d)) & 0xFFFF;
ucontrol->value.enumerated.item[0] = ((val1 << 16) | val2);
dev_info(codec->dev, "%s: dsm dump status 0x%08x(0x%x,0x%x)\n",
__func__, ucontrol->value.enumerated.item[0],
val1, val2);
return 0;
}
static int max9850x_set_dump_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
unsigned int log_available =
snd_soc_read(codec, ARIZONA_DSP4_CONTROL_1);
dev_info(codec->dev, "%s: log_available = 0x%x\n",
__func__, log_available);
if (log_available & 0x3)
create_dsm_log_dump(codec);
return 0;
}
static void create_dsm_param_dump(struct snd_soc_codec *codec)
{
int i;
unsigned int reg, val1, val2;
unsigned int param_buf[dsm_param_info[0].size];
dev_info(codec->dev, "%s: ++\n", __func__);
memset(param_buf, 0, sizeof(param_buf));
for (i = 0; i < dsm_param_info[0].size; i++) {
reg = dsm_param_info[0].start + i*2;
val1 = (snd_soc_read(codec, reg)) & 0xFFFF;
val2 = (snd_soc_read(codec, reg + 1)) & 0xFFFF;
param_buf[i] = ((val1 << 16) | val2);
}
maxdsm_a_param_update(param_buf);
dev_info(codec->dev, "%s: --\n", __func__);
}
static int max9850x_get_param_dump_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return 0;
}
static int max9850x_set_param_dump_status(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
unsigned int param_available =
snd_soc_read(codec, ARIZONA_DSP4_CONTROL_1);
if (param_available & 0x3)
create_dsm_param_dump(codec);
return 0;
}
#endif
#if defined(CONFIG_SND_SOC_MAXIM_DSM_CAL)
#define DSM_RDC_ROOM_TEMP 0x2A005C
#define DSM_AMBIENT_TEMP 0x2A0182
static int pacific_get_dsm_cal_info(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
int ret;
ret = maxdsm_cal_get_temp(&priv->dsm_cal_temp);
dev_info(card->dev, "temp = 0x%x, ret = %d\n", priv->dsm_cal_temp, ret);
if (ret < 0)
return ret;
ret = maxdsm_cal_get_rdc(&priv->dsm_cal_rdc);
dev_info(card->dev, "rdc = 0x%x, ret = %d\n", priv->dsm_cal_rdc, ret);
if (ret < 0)
return ret;
if (ret == 0) {
priv->dsm_cal_rdc = -1;
priv->dsm_cal_temp = -1;
}
priv->dsm_cal_read_cnt = 1;
return ret;
}
static int pacific_set_dsm_cal_info(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
int ret = 0;
dev_info(card->dev, "rdc=%d, temp = %d\n",
priv->dsm_cal_rdc, priv->dsm_cal_temp);
if (priv->dsm_cal_temp >= 0 && priv->dsm_cal_rdc >= 0) {
dev_info(card->dev, "write dsm_cal values properly\n");
regmap_write(priv->regmap_dsp, DSM_AMBIENT_TEMP,
(unsigned int)priv->dsm_cal_temp);
regmap_write(priv->regmap_dsp, DSM_RDC_ROOM_TEMP,
(unsigned int)priv->dsm_cal_rdc);
}
return ret;
}
#endif
static int pacific_external_amp(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct arizona_machine_priv *priv = card->drvdata;
struct snd_soc_codec *codec = priv->codec;
dev_dbg(codec->dev, "%s: %d\n", __func__, event);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
if (priv->external_amp)
priv->external_amp(1);
#if defined(CONFIG_SND_SOC_MAXIM_DSM_CAL)
if (priv->dsm_cal_read_cnt == 0)
pacific_get_dsm_cal_info(card);
if (priv->dsm_cal_read_cnt == 1)
pacific_set_dsm_cal_info(card);
#endif
break;
case SND_SOC_DAPM_PRE_PMD:
if (priv->external_amp)
priv->external_amp(0);
break;
}
return 0;
}
static int pacific_ext_mainmicbias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct arizona_machine_priv *priv = card->drvdata;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (gpio_is_valid(priv->mic_bias_gpio))
gpio_set_value(priv->mic_bias_gpio, 1);
break;
case SND_SOC_DAPM_POST_PMD:
if (gpio_is_valid(priv->mic_bias_gpio))
gpio_set_value(priv->mic_bias_gpio, 0);
break;
}
dev_err(w->dapm->dev, "Main Mic BIAS: %d\n", event);
return 0;
}
static const struct snd_kcontrol_new pacific_codec_controls[] = {
SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0],
get_aif2_mode, set_aif2_mode),
SOC_SINGLE_EXT_TLV("HPOUT1L Impedance Volume",
ARIZONA_DAC_DIGITAL_VOLUME_1L,
ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0,
snd_soc_get_volsw, arizona_put_impedance_volsw,
digital_tlv),
SOC_SINGLE_EXT_TLV("HPOUT1R Impedance Volume",
ARIZONA_DAC_DIGITAL_VOLUME_1R,
ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0,
snd_soc_get_volsw, arizona_put_impedance_volsw,
digital_tlv),
SOC_ENUM_EXT("VoiceControl Mode", voicecontrol_mode_enum[0],
get_voicecontrol_mode, set_voicecontrol_mode),
SOC_SINGLE_EXT("VoiceTrackInfo",
SND_SOC_NOPM,
0, 0xffffffff, 0,
get_voice_tracking_info, set_voice_tracking_info),
SOC_SINGLE_EXT("VoiceTrigInfo",
0x380006, 0, 0xffff, 0,
get_voice_trigger_info, set_voice_trigger_info),
#if defined(CONFIG_SND_SOC_MAXIM_DSM_A)
SOC_SINGLE_EXT("DSM LOG",
SND_SOC_NOPM,
0, 0xff, 0,
max9850x_get_dump_status, max9850x_set_dump_status),
SOC_SINGLE_EXT("DSM PARAM",
SND_SOC_NOPM,
0, 0xff, 0,
max9850x_get_param_dump_status, max9850x_set_param_dump_status),
#endif
};
static const struct snd_kcontrol_new pacific_controls[] = {
SOC_DAPM_PIN_SWITCH("HP"),
SOC_DAPM_PIN_SWITCH("SPK"),
SOC_DAPM_PIN_SWITCH("RCV"),
SOC_DAPM_PIN_SWITCH("Main Mic"),
SOC_DAPM_PIN_SWITCH("Sub Mic"),
SOC_DAPM_PIN_SWITCH("Third Mic"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("VI SENSING"),
SOC_DAPM_PIN_SWITCH("FM In"),
};
const struct snd_soc_dapm_widget pacific_dapm_widgets[] = {
SND_SOC_DAPM_HP("HP", NULL),
SND_SOC_DAPM_SPK("SPK", pacific_external_amp),
SND_SOC_DAPM_SPK("RCV", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Main Mic", pacific_ext_mainmicbias),
SND_SOC_DAPM_MIC("Sub Mic", NULL),
SND_SOC_DAPM_MIC("Third Mic", NULL),
SND_SOC_DAPM_MIC("VI SENSING", NULL),
SND_SOC_DAPM_MIC("FM In", NULL),
};
const struct snd_soc_dapm_route pacific_wm1840_dapm_routes[] = {
{ "HP", NULL, "HPOUT1L" },
{ "HP", NULL, "HPOUT1R" },
{ "SPK", NULL, "HiFi Playback" },
{ "RCV", NULL, "HPOUT3L" },
{ "RCV", NULL, "HPOUT3R" },
{ "IN1AL", NULL, "Main Mic" },
{ "Main Mic", NULL, "MICBIAS1" },
{ "Headset Mic", NULL, "MICBIAS2" },
{ "IN2BL", NULL, "Headset Mic" },
{ "Sub Mic", NULL, "MICBIAS3" },
{ "IN3L", NULL, "Sub Mic" },
{ "Third Mic", NULL, "MICBIAS3" },
{ "IN4L", NULL, "Third Mic" },
/* "HiFi Capture" is capture stream of max98505 dai */
{ "HiFi Capture", NULL, "VI SENSING" },
{ "IN3L", NULL, "FM In" },
{ "IN3R", NULL, "FM In" },
};
int pacific_set_media_clocking(struct arizona_machine_priv *priv)
{
struct snd_soc_codec *codec = priv->codec;
struct snd_soc_card *card = codec->component.card;
int ret;
ret = snd_soc_codec_set_sysclk(codec,
ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
priv->sysclk_rate,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev, "Failed to set SYSCLK to FLL1: %d\n", ret);
if (priv->dspclk_rate > 0) {
ret = snd_soc_codec_set_sysclk(codec,
ARIZONA_CLK_DSPCLK,
ARIZONA_CLK_SRC_FLL1,
priv->dspclk_rate,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev,
"Failed to set DSPCLK to FLL1: %d\n", ret);
}
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
priv->asyncclk_rate,
SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev,
"Unable to set ASYNCCLK to FLL2: %d\n", ret);
/* AIF1 from SYSCLK, AIF2 and 3 from ASYNCCLK */
if (priv->aif[0]) {
ret = snd_soc_dai_set_sysclk(priv->aif[0], ARIZONA_CLK_SYSCLK, 0, 0);
if (ret < 0)
dev_err(card->dev, "Can't set AIF1 to SYSCLK: %d\n", ret);
}
if (priv->aif[1]) {
ret = snd_soc_dai_set_sysclk(priv->aif[1], ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret < 0)
dev_err(card->dev, "Can't set AIF2 to ASYNCCLK: %d\n", ret);
}
if (priv->aif[2]) {
ret = snd_soc_dai_set_sysclk(priv->aif[2],
ARIZONA_CLK_SYSCLK_2, 0, 0);
if (ret < 0)
dev_err(card->dev,
"Can't set AIF3 to ASYNCCLK_2/SYSCLK_2: %d\n", ret);
}
return 0;
}
static int pacific_aif_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
dev_info(card->dev, "%s-%d startup ++, playback=%d, capture=%d\n",
rtd->dai_link->name, substream->stream,
codec_dai->playback_active, codec_dai->capture_active);
return 0;
}
static void pacific_aif_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
dev_info(card->dev, "%s-%d shutdown++ codec_dai[%s]:playback=%d\n",
rtd->dai_link->name, substream->stream,
codec_dai->name, codec_dai->playback_active);
return;
}
static int pacific_aif1_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_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct arizona_machine_priv *priv = card->drvdata;
int ret;
dev_info(card->dev, "%s-%d %dch, %dHz, %dbytes\n",
rtd->dai_link->name, substream->stream,
params_channels(params), params_rate(params),
params_buffer_bytes(params));
pacific_set_media_clocking(priv);
/* Set Codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, priv->aif_format[0]);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif1 codec fmt: %d\n", ret);
return ret;
}
/* Set CPU DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, priv->aif_format[0]);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif1 cpu fmt: %d\n", ret);
return ret;
}
if (priv->aif_format_tdm[0]) {
ret = snd_soc_dai_set_tdm_slot(codec_dai,
priv->aif_format_tdm[0] & 0x000F,
priv->aif_format_tdm[0] & 0x000F,
(priv->aif_format_tdm[0] & 0x00F0) >> 4,
(priv->aif_format_tdm[0] & 0xFF00) >> 8);
if (ret < 0) {
dev_err(card->dev,
"Failed to set aif1 tdm slot: %d\n", ret);
return ret;
}
}
if ((priv->aif_format[0] & SND_SOC_DAIFMT_MASTER_MASK) ==
SND_SOC_DAIFMT_CBS_CFS) {
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
64, SND_SOC_CLOCK_OUT);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_CDCL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_OPCL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_RCLKSRC_0: %d\n",
ret);
return ret;
}
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, 64);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_DIV_BCLK: %d\n",
ret);
return ret;
}
} else {
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_IN);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_CDCL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
0, MOD_OPCLK_PCLK);
if (ret < 0) {
dev_err(card->dev,
"Failed to set SAMSUNG_I2S_OPCL: %d\n", ret);
return ret;
}
}
return ret;
}
static int pacific_aif1_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s-%d\n hw_free",
rtd->dai_link->name, substream->stream);
return 0;
}
static int pacific_aif1_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s-%d prepare\n",
rtd->dai_link->name, substream->stream);
return 0;
}
static int pacific_aif1_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
dev_dbg(card->dev, "%s-%d trigger\n",
rtd->dai_link->name, substream->stream);
return 0;
}
static struct snd_soc_ops pacific_aif1_ops = {
.startup = pacific_aif_startup,
.shutdown = pacific_aif_shutdown,
.hw_params = pacific_aif1_hw_params,
.hw_free = pacific_aif1_hw_free,
.prepare = pacific_aif1_prepare,
.trigger = pacific_aif1_trigger,
};
static int pacific_aif2_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_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct arizona_machine_priv *priv = rtd->card->drvdata;
int ret;
int prate, bclk;
dev_info(card->dev, "%s-%d %dch, %dHz\n",
rtd->dai_link->name, substream->stream,
params_channels(params), params_rate(params));
prate = params_rate(params);
switch (prate) {
case 8000:
bclk = 256000;
break;
case 16000:
bclk = 512000;
break;
default:
dev_warn(card->dev,
"Unsupported LRCLK %d, falling back to 8000Hz\n",
(int)params_rate(params));
bclk = 256000;
}
/* Set the codec DAI configuration, aif2_mode:0 is slave */
if (priv->aif2mode == 0) {
priv->aif_format[1] &= ~SND_SOC_DAIFMT_MASTER_MASK;
priv->aif_format[1] |= SND_SOC_DAIFMT_CBS_CFS;
} else {
priv->aif_format[1] &= ~SND_SOC_DAIFMT_MASTER_MASK;
priv->aif_format[1] |= SND_SOC_DAIFMT_CBM_CFM;
}
/* Set Codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, priv->aif_format[1]);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif2 codec fmt: %d\n", ret);
return ret;
}
if (priv->aif_format_tdm[1]) {
ret = snd_soc_dai_set_tdm_slot(codec_dai,
priv->aif_format_tdm[1] & 0x000F,
priv->aif_format_tdm[1] & 0x000F,
(priv->aif_format_tdm[1] & 0x00F0) >> 4,
(priv->aif_format_tdm[1] & 0xFF00) >> 8);
if (ret < 0) {
dev_err(card->dev,
"Failed to set aif2 tdm slot: %d\n", ret);
return ret;
}
}
if (priv->aif2mode == 0) {
ret = snd_soc_dai_set_pll(codec_dai, PACIFIC_FLL2_REFCLK,
ARIZONA_FLL_SRC_MCLK1,
PACIFIC_DEFAULT_MCLK1,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2 REF: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, PACIFIC_FLL2,
ARIZONA_FLL_SRC_AIF2BCLK,
bclk,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2%d\n", ret);
return ret;
}
} else {
ret = snd_soc_dai_set_pll(codec_dai, PACIFIC_FLL2, 0, 0, 0);
if (ret != 0)
dev_err(card->dev,
"Failed to stop FLL2: %d\n", ret);
ret = snd_soc_dai_set_pll(codec_dai, PACIFIC_FLL2_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2 REF: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, PACIFIC_FLL2,
ARIZONA_CLK_SRC_MCLK1,
PACIFIC_DEFAULT_MCLK1,
priv->asyncclk_rate);
if (ret != 0) {
dev_err(card->dev,
"Failed to start FLL2: %d\n", ret);
return ret;
}
}
return 0;
}
static struct snd_soc_ops pacific_aif2_ops = {
.shutdown = pacific_aif_shutdown,
.hw_params = pacific_aif2_hw_params,
};
static int pacific_aif3_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_card *card = rtd->card;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct arizona_machine_priv *priv = rtd->card->drvdata;
int ret;
dev_info(card->dev, "%s-%d %dch, %dHz\n",
rtd->dai_link->name, substream->stream,
params_channels(params), params_rate(params));
/* Set Codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, priv->aif_format[2]);
if (ret < 0) {
dev_err(card->dev, "Failed to set aif3 codec fmt: %d\n", ret);
return ret;
}
if (priv->aif_format_tdm[2]) {
ret = snd_soc_dai_set_tdm_slot(codec_dai,
priv->aif_format_tdm[2] & 0x000F,
priv->aif_format_tdm[2] & 0x000F,
(priv->aif_format_tdm[2] & 0x00F0) >> 4,
(priv->aif_format_tdm[2] & 0xFF00) >> 8);
if (ret < 0) {
dev_err(card->dev,
"Failed to set aif3 tdm slot: %d\n", ret);
return ret;
}
}
return 0;
}
static struct snd_soc_ops pacific_aif3_ops = {
.shutdown = pacific_aif_shutdown,
.hw_params = pacific_aif3_hw_params,
};
static struct snd_soc_dai_driver pacific_ext_dai[] = {
{
.name = "pacific-ext voice call",
.playback = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
{
.name = "pacific-ext bluetooth sco",
.playback = {
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 16000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
#if defined(CONFIG_SND_SOC_MAX98505)
{
.name = "pacific-ext dsm",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
},
#endif
};
static struct snd_soc_dai_link pacific_wm5110_dai[] = {
{ /* playback & recording */
.name = "playback-pri",
.stream_name = "i2s0-pri",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
{ /* voice call */
.name = "baseband",
.stream_name = "pacific-ext voice call",
.cpu_dai_name = "pacific-ext voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "florida-aif2",
.ops = &pacific_aif2_ops,
.ignore_suspend = 1,
},
{ /* bluetooth sco */
.name = "bluetooth sco",
.stream_name = "pacific-ext bluetooth sco",
.cpu_dai_name = "pacific-ext bluetooth sco",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "florida-aif3",
.ops = &pacific_aif3_ops,
.ignore_suspend = 1,
},
{ /* deep buffer playback */
.name = "playback-sec",
.stream_name = "i2s0-sec",
.cpu_dai_name = "samsung-i2s-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
{
.name = "CPU-DSP Voice Control",
.stream_name = "CPU-DSP Voice Control",
.cpu_dai_name = "florida-cpu-voicectrl",
.platform_name = "florida-codec",
.codec_dai_name = "florida-dsp-voicectrl",
.codec_name = "florida-codec",
},
{ /* pcm dump interface */
.name = "CPU-DSP trace",
.stream_name = "CPU-DSP trace",
.cpu_dai_name = "florida-cpu-trace",
.platform_name = "florida-codec",
.codec_dai_name = "florida-dsp-trace",
.codec_name = "florida-codec",
},
{ /* eax0 playback */
.name = "playback-eax0",
.stream_name = "eax0",
.cpu_dai_name = "samsung-eax.0",
.platform_name = "samsung-eax.0",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax1 playback */
.name = "playback-eax1",
.stream_name = "eax1",
.cpu_dai_name = "samsung-eax.1",
.platform_name = "samsung-eax.1",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax2 playback */
.name = "playback-eax2",
.stream_name = "eax2",
.cpu_dai_name = "samsung-eax.2",
.platform_name = "samsung-eax.2",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax3 playback */
.name = "playback-eax3",
.stream_name = "eax3",
.cpu_dai_name = "samsung-eax.3",
.platform_name = "samsung-eax.3",
.codec_dai_name = "florida-aif1",
.ops = &pacific_aif1_ops,
},
};
#if defined(CONFIG_SND_SOC_MAX98505)
static const struct snd_soc_pcm_stream dsm_params = {
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
#endif
static struct snd_soc_dai_link pacific_wm1840_dai[] = {
{ /* playback & recording */
.name = "playback-pri",
.stream_name = "i2s0-pri",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
#if 0
{ /* bluetooth sco */
.name = "bluetooth sco",
.stream_name = "pacific-ext bluetooth sco",
.cpu_dai_name = "pacific-ext bluetooth sco",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "clearwater-aif3",
.ops = &pacific_aif3_ops,
.ignore_suspend = 1,
},
#endif
{ /* deep buffer playback */
.name = "playback-sec",
.stream_name = "i2s0-sec",
.cpu_dai_name = "samsung-i2s-sec",
.platform_name = "samsung-i2s-sec",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
{ /* voice call */
.name = "baseband",
.stream_name = "pacific-ext voice call",
.cpu_dai_name = "pacific-ext voice call",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "clearwater-aif2",
.ops = &pacific_aif2_ops,
.ignore_suspend = 1,
},
#if 0
{
.name = "CPU-DSP Voice Control",
.stream_name = "CPU-DSP Voice Control",
.cpu_dai_name = "clearwater-cpu-voicectrl",
.platform_name = "clearwater-codec",
.codec_dai_name = "clearwater-dsp-voicectrl",
.codec_name = "clearwater-codec",
},
{ /* pcm dump interface */
.name = "CPU-DSP trace",
.stream_name = "CPU-DSP trace",
.cpu_dai_name = "clearwater-cpu-trace",
.platform_name = "clearwater-codec",
.codec_dai_name = "clearwater-dsp-trace",
.codec_name = "clearwater-codec",
},
#endif
{ /* eax0 playback */
.name = "playback-eax0",
.stream_name = "eax0",
.cpu_dai_name = "samsung-eax.0",
.platform_name = "samsung-eax.0",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax1 playback */
.name = "playback-eax1",
.stream_name = "eax1",
.cpu_dai_name = "samsung-eax.1",
.platform_name = "samsung-eax.1",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax2 playback */
.name = "playback-eax2",
.stream_name = "eax2",
.cpu_dai_name = "samsung-eax.2",
.platform_name = "samsung-eax.2",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
{ /* eax3 playback */
.name = "playback-eax3",
.stream_name = "eax3",
.cpu_dai_name = "samsung-eax.3",
.platform_name = "samsung-eax.3",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
#if defined(CONFIG_SND_SOC_MAX98505)
{ /* dsm */
.name = "codec-dsm",
.stream_name = "pacific-ext dsm",
.cpu_dai_name = "clearwater-aif4",
.codec_dai_name = "max98505-aif1",
.codec_name = "max98505.0-0031",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.params = &dsm_params,
.ignore_suspend = 1,
},
#endif
#if defined(CONFIG_SND_SAMSUNG_COMPR)
{ /* compress playback */
.name = "playback offload",
.stream_name = "i2s0-compr",
.cpu_dai_name = "samsung-i2s-compr",
.platform_name = "samsung-i2s-compr",
.codec_dai_name = "clearwater-aif1",
.ops = &pacific_aif1_ops,
},
#endif
};
static int pacific_of_get_pdata(struct snd_soc_card *card)
{
struct device_node *pdata_np;
struct arizona_machine_priv *priv = card->drvdata;
int ret;
pdata_np = of_find_node_by_path("/audio_pdata");
if (!pdata_np) {
dev_err(card->dev,
"Property 'samsung,audio-pdata' missing or invalid\n");
return -EINVAL;
}
priv->mic_bias_gpio = of_get_named_gpio(pdata_np, "mic_bias_gpio", 0);
if (gpio_is_valid(priv->mic_bias_gpio)) {
ret = gpio_request(priv->mic_bias_gpio, "MICBIAS_EN_AP");
if (ret)
dev_err(card->dev, "Failed to request gpio: %d\n", ret);
gpio_direction_output(priv->mic_bias_gpio, 0);
}
pacific_update_impedance_table(pdata_np);
priv->seamless_voicewakeup =
of_property_read_bool(pdata_np, "seamless_voicewakeup");
ret = of_property_read_u32_array(pdata_np, "aif_format",
priv->aif_format, ARRAY_SIZE(priv->aif_format));
if (ret == -EINVAL) {
priv->aif_format[0] = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM;
priv->aif_format[1] = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS;
priv->aif_format[2] = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM;
}
of_property_read_u32_array(pdata_np, "aif_format_tdm",
priv->aif_format_tdm, ARRAY_SIZE(priv->aif_format_tdm));
return 0;
}
static void get_voicetrigger_dump(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
unsigned int sv_score, final_score, noise_floor;
regmap_read(priv->regmap_dsp, priv->sv_score_addr, &sv_score);
regmap_read(priv->regmap_dsp, priv->final_score_addr, &final_score);
regmap_read(priv->regmap_dsp, priv->noise_floor_addr, &noise_floor);
dev_info(card->dev,
"sv_score=0x%x, final_score=0x%x, noise-floor=0x%x\n",
sv_score, final_score, noise_floor);
}
static void ez2ctrl_voicewakeup_cb(void)
{
struct arizona_machine_priv *priv;
char keyword_buf[100];
char *envp[2];
memset(keyword_buf, 0, sizeof(keyword_buf));
dev_info(the_codec->dev, "Voice Control Callback invoked!\n");
WARN_ON(!the_codec);
if (!the_codec)
return;
priv = the_codec->component.card->drvdata;
if (!priv)
return;
if (priv->voice_uevent == 0) {
regmap_read(priv->regmap_dsp,
priv->word_id_addr, &keyword_type);
snprintf(keyword_buf, sizeof(keyword_buf),
"VOICE_WAKEUP_WORD_ID=%x", keyword_type);
} else if (priv->voice_uevent == 1) {
snprintf(keyword_buf, sizeof(keyword_buf),
"VOICE_WAKEUP_WORD_ID=LPSD");
} else {
dev_err(the_codec->dev, "Invalid voice control mode =%d",
priv->voice_uevent);
return;
}
get_voicetrigger_dump(the_codec->component.card);
envp[0] = keyword_buf;
envp[1] = NULL;
dev_info(the_codec->dev, "%s : raise the uevent, string = %s\n",
__func__, keyword_buf);
wake_lock_timeout(&priv->wake_lock, 5000);
kobject_uevent_env(&(the_codec->dev->kobj), KOBJ_CHANGE, envp);
return;
}
static ssize_t svoice_keyword_type_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
ret = snprintf(buf, PAGE_SIZE, "%x\n", keyword_type);
return ret;
}
static ssize_t svoice_keyword_type_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct snd_soc_codec *codec = dev_get_drvdata(dev);
int ret;
ret = kstrtoint(buf, 0, &keyword_type);
if (ret)
dev_err(codec->dev,
"Failed to convert a string to an int: %d\n", ret);
dev_info(codec->dev, "%s(): keyword_type = %x\n",
__func__, keyword_type);
return count;
}
static DEVICE_ATTR(keyword_type, S_IRUGO | S_IWUSR | S_IWGRP,
svoice_keyword_type_show, svoice_keyword_type_set);
static void ez2ctrl_debug_cb(void)
{
struct arizona_machine_priv *priv;
pr_info("Voice Control Callback invoked!\n");
WARN_ON(!the_codec);
if (!the_codec)
return;
priv = the_codec->component.card->drvdata;
if (!priv)
return;
input_report_key(priv->input, priv->voice_key_event, 1);
input_sync(priv->input);
usleep_range(10000, 20000);
input_report_key(priv->input, priv->voice_key_event, 0);
input_sync(priv->input);
return;
}
static int pacific_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
struct arizona_machine_priv *priv = card->drvdata;
int ret, index;
priv->codec = codec;
the_codec = codec;
pacific_of_get_pdata(card);
/* build audio interface & dai link mapping */
ret = of_property_read_u32(card->dev->of_node, "aif1-setup-index", &index);
if (ret == 0)
priv->aif[0] = card->rtd[index].codec_dai;
ret = of_property_read_u32(card->dev->of_node, "aif2-setup-index", &index);
if (ret == 0)
priv->aif[1] = card->rtd[index].codec_dai;
ret = of_property_read_u32(card->dev->of_node, "aif3-setup-index", &index);
if (ret == 0)
priv->aif[2] = card->rtd[index].codec_dai;
codec_dai->driver->playback.channels_max =
cpu_dai->driver->playback.channels_max;
/* close codec device immediately when pcm is closed */
codec->component.ignore_pmdown_time = true;
ret = snd_soc_add_codec_controls(codec, pacific_codec_controls,
ARRAY_SIZE(pacific_codec_controls));
if (ret < 0) {
dev_err(codec->dev,
"Failed to add controls to codec: %d\n", ret);
return ret;
}
snd_soc_dapm_ignore_suspend(&card->dapm, "Main Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Sub Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Third Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "FM In");
snd_soc_dapm_ignore_suspend(&card->dapm, "RCV");
snd_soc_dapm_ignore_suspend(&card->dapm, "SPK");
snd_soc_dapm_ignore_suspend(&card->dapm, "HP");
snd_soc_dapm_sync(&card->dapm);
snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2 Playback");
snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2 Capture");
snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3 Playback");
snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3 Capture");
snd_soc_dapm_ignore_suspend(&codec->dapm, "DSP Virtual Output");
snd_soc_dapm_sync(&codec->dapm);
pacific_set_media_clocking(priv);
wake_lock_init(&priv->wake_lock, WAKE_LOCK_SUSPEND,
"pacific-voicewakeup");
if (priv->seamless_voicewakeup) {
arizona_set_ez2ctrl_cb(codec, ez2ctrl_voicewakeup_cb);
svoice_class = class_create(THIS_MODULE, "svoice");
ret = IS_ERR(svoice_class);
if (ret) {
pr_err("Failed to create class(svoice)!\n");
goto err_class_create;
}
keyword_dev = device_create(svoice_class,
NULL, 0, NULL, "keyword");
ret = IS_ERR(keyword_dev);
if (ret) {
pr_err("Failed to create device(keyword)!\n");
goto err_device_create;
}
ret = device_create_file(keyword_dev, &dev_attr_keyword_type);
if (ret < 0) {
pr_err("Failed to create device file in sysfs entries(%s)!\n",
dev_attr_keyword_type.attr.name);
goto err_device_create_file;
}
} else {
priv->input = devm_input_allocate_device(card->dev);
if (!priv->input) {
dev_err(card->dev, "Can't allocate input dev\n");
ret = -ENOMEM;
goto err_register;
}
priv->input->name = "voicecontrol";
priv->input->dev.parent = card->dev;
input_set_capability(priv->input, EV_KEY,
KEY_VOICE_WAKEUP);
input_set_capability(priv->input, EV_KEY,
KEY_LPSD_WAKEUP);
ret = input_register_device(priv->input);
if (ret) {
dev_err(card->dev,
"Can't register input device: %d\n", ret);
goto err_register;
}
arizona_set_ez2ctrl_cb(codec, ez2ctrl_debug_cb);
priv->voice_key_event = KEY_VOICE_WAKEUP;
}
priv->regmap_dsp = arizona_get_regmap_dsp(codec);
arizona_set_hpdet_cb(codec, pacific_arizona_hpdet_cb);
arizona_set_micd_cb(codec, pacific_arizona_micd_cb);
#if defined(CONFIG_SND_SOC_MAXIM_DSM_CAL)
if (priv->regmap_dsp)
maxdsm_cal_set_regmap(priv->regmap_dsp);
#endif
#if defined(CONFIG_SND_ESA_SA_EFFECT)
ret = esa_effect_register(card);
if (ret < 0) {
dev_err(codec->dev,
"Failed to add esa controls to codec: %d\n", ret);
}
#endif
return 0;
err_device_create_file:
device_destroy(svoice_class, 0);
err_device_create:
class_destroy(svoice_class);
err_class_create:
err_register:
wake_lock_destroy(&priv->wake_lock);
return ret;
}
static int pacific_start_sysclk(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
struct snd_soc_codec *codec = priv->codec;
int ret;
if (priv->mclk) {
ret = clk_enable(priv->mclk);
if (ret < 0) {
dev_err(card->dev, "clk_enable failed = %d\n", ret);
return ret;
}
dev_info(card->dev, "mclk enabled\n");
}
ret = snd_soc_codec_set_pll(codec, PACIFIC_FLL1_REFCLK,
ARIZONA_FLL_SRC_NONE, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(codec, PACIFIC_FLL1, ARIZONA_CLK_SRC_MCLK1,
PACIFIC_DEFAULT_MCLK1,
priv->sysclk_rate);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
return ret;
}
static int pacific_change_sysclk(struct snd_soc_card *card, int source)
{
struct arizona_machine_priv *priv = card->drvdata;
struct snd_soc_codec *codec = priv->codec;
int ret;
dev_info(card->dev, "%s: source = %d\n", __func__, source);
if (source) {
/* uses MCLK1 when the source is 1 */
if (priv->mclk) {
ret = clk_enable(priv->mclk);
if (ret < 0) {
dev_err(card->dev,
"clk_enable failed = %d\n", ret);
return ret;
}
dev_info(card->dev, "mclk enabled\n");
}
ret = snd_soc_codec_set_pll(codec, PACIFIC_FLL1,
ARIZONA_FLL_SRC_MCLK1,
PACIFIC_DEFAULT_MCLK1,
priv->sysclk_rate);
if (ret != 0) {
dev_err(card->dev, "Failed to start FLL1: %d\n", ret);
return ret;
}
} else {
/* uses MCLK2 when the source is not 1 */
ret = snd_soc_codec_set_pll(codec, PACIFIC_FLL1,
ARIZONA_FLL_SRC_MCLK2,
PACIFIC_DEFAULT_MCLK2,
priv->sysclk_rate);
if (ret != 0) {
dev_err(card->dev, "Failed to change FLL1: %d\n", ret);
return ret;
}
if (priv->mclk) {
clk_disable(priv->mclk);
dev_info(card->dev, "mclk disbled\n");
}
}
return ret;
}
static int pacific_stop_sysclk(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
int ret;
ret = snd_soc_codec_set_pll(priv->codec, PACIFIC_FLL1, 0, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to stop FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_pll(priv->codec, PACIFIC_FLL2, 0, 0, 0);
if (ret != 0) {
dev_err(card->dev, "Failed to stop FLL1: %d\n", ret);
return ret;
}
if (priv->mclk) {
clk_disable(priv->mclk);
dev_info(card->dev, "mclk disbled\n");
}
return ret;
}
static int pacific_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct arizona_machine_priv *priv = card->drvdata;
if (!priv->codec || dapm != &priv->codec->dapm)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
if (card->dapm.bias_level == SND_SOC_BIAS_OFF)
pacific_start_sysclk(card);
break;
case SND_SOC_BIAS_OFF:
pacific_stop_sysclk(card);
break;
case SND_SOC_BIAS_PREPARE:
break;
default:
break;
}
card->dapm.bias_level = level;
dev_dbg(card->dev, "%s: %d\n", __func__, level);
return 0;
}
static int pacific_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
dev_dbg(card->dev, "%s: %d\n", __func__, level);
return 0;
}
static int pacific_check_clock_conditions(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct arizona_machine_priv *priv = card->drvdata;
int mainmic_state = 0;
#if defined(CONFIG_MFD_FLORIDA) || defined(CONFIG_MFD_CLEARWATER)
/* Check status of the Main Mic for ez2control
* Because when the phone goes to suspend mode,
* Enabling case of Main mic is only ez2control mode */
mainmic_state = snd_soc_dapm_get_pin_status(&card->dapm, "Main Mic");
#endif
if (!codec->component.active && mainmic_state) {
dev_info(card->dev, "MAIN_MIC is running without input stream\n");
return PACIFIC_RUN_MAINMIC;
}
if (!codec->component.active && priv->ear_mic && !mainmic_state) {
dev_info(card->dev, "EAR_MIC is running without input stream\n");
return PACIFIC_RUN_EARMIC;
}
return 0;
}
static int pacific_suspend_post(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
int ret;
/* When the card goes to suspend state, If codec is not active,
* the micbias of headset is enable and the ez2control is not running,
* The MCLK and the FLL1 should be disable to reduce the sleep current.
* In the other cases, these should keep previous status */
ret = pacific_check_clock_conditions(card);
if (ret == PACIFIC_RUN_EARMIC) {
arizona_enable_force_bypass(priv->codec);
pacific_stop_sysclk(card);
dev_info(card->dev, "%s: stop_sysclk\n", __func__);
} else if (ret == PACIFIC_RUN_MAINMIC) {
arizona_enable_force_bypass(priv->codec);
pacific_change_sysclk(card, 0);
dev_info(card->dev, "%s: change_sysclk\n", __func__);
}
if (!priv->codec->component.active) {
dev_info(card->dev, "%s : set AIF1 port slave\n", __func__);
snd_soc_update_bits(priv->codec, ARIZONA_AIF1_BCLK_CTRL,
ARIZONA_AIF1_BCLK_MSTR_MASK, 0);
snd_soc_update_bits(priv->codec, ARIZONA_AIF1_TX_PIN_CTRL,
ARIZONA_AIF1TX_LRCLK_MSTR_MASK, 0);
snd_soc_update_bits(priv->codec, ARIZONA_AIF1_RX_PIN_CTRL,
ARIZONA_AIF1RX_LRCLK_MSTR_MASK, 0);
}
return 0;
}
static int pacific_resume_pre(struct snd_soc_card *card)
{
struct arizona_machine_priv *priv = card->drvdata;
int ret;
/* When the card goes to resume state, If codec is not active,
* the micbias of headset is enable and the ez2control is not running,
* The MCLK and the FLL1 should be enable.
* In the other cases, these should keep previous status */
ret = pacific_check_clock_conditions(card);
if (ret == PACIFIC_RUN_EARMIC) {
pacific_start_sysclk(card);
arizona_disable_force_bypass(priv->codec);
dev_info(card->dev, "%s: start_sysclk\n", __func__);
} else if (ret == PACIFIC_RUN_MAINMIC) {
pacific_change_sysclk(card, 1);
arizona_disable_force_bypass(priv->codec);
dev_info(card->dev, "%s: change_sysclk\n", __func__);
}
return 0;
}
static struct snd_soc_card pacific_cards[] = {
{
.name = "Pacific WM5110 Sound",
.owner = THIS_MODULE,
.dai_link = pacific_wm5110_dai,
.num_links = ARRAY_SIZE(pacific_wm5110_dai),
.controls = pacific_controls,
.num_controls = ARRAY_SIZE(pacific_controls),
.dapm_widgets = pacific_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(pacific_dapm_widgets),
.late_probe = pacific_late_probe,
.suspend_post = pacific_suspend_post,
.resume_pre = pacific_resume_pre,
.set_bias_level = pacific_set_bias_level,
.set_bias_level_post = pacific_set_bias_level_post,
.drvdata = &pacific_wm5110_priv,
},
{
.name = "Pacific WM1840 Sound",
.owner = THIS_MODULE,
.dai_link = pacific_wm1840_dai,
.num_links = ARRAY_SIZE(pacific_wm1840_dai),
.controls = pacific_controls,
.num_controls = ARRAY_SIZE(pacific_controls),
.dapm_widgets = pacific_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(pacific_dapm_widgets),
.dapm_routes = pacific_wm1840_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(pacific_wm1840_dapm_routes),
.late_probe = pacific_late_probe,
.suspend_post = pacific_suspend_post,
.resume_pre = pacific_resume_pre,
.set_bias_level = pacific_set_bias_level,
.set_bias_level_post = pacific_set_bias_level_post,
.drvdata = &pacific_wm1840_priv,
},
};
static const char * const card_ids[] = {
"wm5110", "wm1840"
};
static int pacific_audio_probe(struct platform_device *pdev)
{
int n, ret;
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
struct snd_soc_card *card;
struct snd_soc_dai_link *dai_link;
struct arizona_machine_priv *priv;
int of_route, num_cards;
num_cards = (int) ARRAY_SIZE(card_ids);
for (n = 0; n < num_cards; n++) {
if (NULL != of_find_node_by_name(NULL, card_ids[n])) {
card = &pacific_cards[n];
dai_link = card->dai_link;
break;
}
}
if (n == num_cards) {
dev_err(&pdev->dev, "couldn't find card\n");
return -EINVAL;
}
card->dev = &pdev->dev;
priv = card->drvdata;
priv->mclk = devm_clk_get(card->dev, "mclk");
if (IS_ERR(priv->mclk)) {
dev_dbg(card->dev, "Device tree node not found for mclk");
priv->mclk = NULL;
} else
clk_prepare(priv->mclk);
ret = snd_soc_register_component(card->dev, &pacific_cmpnt,
pacific_ext_dai, ARRAY_SIZE(pacific_ext_dai));
if (ret != 0)
dev_err(&pdev->dev, "Failed to register component: %d\n", ret);
of_route = of_property_read_bool(card->dev->of_node,
"samsung,audio-routing");
if (!card->dapm_routes || !card->num_dapm_routes || of_route) {
ret = snd_soc_of_parse_audio_routing(card,
"samsung,audio-routing");
if (ret != 0) {
dev_err(&pdev->dev,
"Failed to parse audio routing: %d\n",
ret);
ret = -EINVAL;
goto out;
}
}
if (np) {
for (n = 0; n < card->num_links; n++) {
/* Skip parsing DT for fully formed dai links */
if (dai_link[n].platform_name &&
dai_link[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link %s\n",
dai_link[n].name);
continue;
}
if (dai_link[n].platform_name == NULL &&
dai_link[n].codec_name) {
dev_dbg(card->dev,
"Skipping dt for populated dai link(codec to codec) %s\n",
dai_link[n].name);
continue;
}
cpu_np = of_parse_phandle(np,
"samsung,audio-cpu", n);
if (!cpu_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-cpu'"
" missing or invalid\n");
ret = -EINVAL;
goto out;
}
codec_np = of_parse_phandle(np,
"samsung,audio-codec", n);
if (!codec_np) {
dev_err(&pdev->dev,
"Property 'samsung,audio-codec'"
" missing or invalid\n");
ret = -EINVAL;
goto out;
}
if (!dai_link[n].cpu_dai_name)
dai_link[n].cpu_of_node = cpu_np;
if (!dai_link[n].platform_name)
dai_link[n].platform_of_node = cpu_np;
dai_link[n].codec_of_node = codec_np;
}
} else
dev_err(&pdev->dev, "Failed to get device node\n");
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "Failed to register card:%d\n", ret);
goto out;
}
return ret;
out:
snd_soc_unregister_component(card->dev);
return ret;
}
static int pacific_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct arizona_machine_priv *priv = card->drvdata;
device_remove_file(keyword_dev, &dev_attr_keyword_type);
device_destroy(svoice_class, 0);
class_destroy(svoice_class);
snd_soc_unregister_component(card->dev);
snd_soc_unregister_card(card);
wake_lock_destroy(&priv->wake_lock);
if (priv->mclk) {
clk_unprepare(priv->mclk);
devm_clk_put(card->dev, priv->mclk);
}
return 0;
}
static const struct of_device_id pacific_arizona_of_match[] = {
{ .compatible = "samsung,pacific-arizona", },
{ },
};
MODULE_DEVICE_TABLE(of, pacific_arizona_of_match);
static struct platform_driver pacific_audio_driver = {
.driver = {
.name = "pacific-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(pacific_arizona_of_match),
},
.probe = pacific_audio_probe,
.remove = pacific_audio_remove,
};
module_platform_driver(pacific_audio_driver);
MODULE_DESCRIPTION("ALSA SoC PACIFIC ARIZONA");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pacific-audio");