blob: ac9ffd3965143a02db8332a886da21f9a9d01d3e [file] [log] [blame]
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "wau8822.h"
#define CODEC_NAME "wau8822"
#define wau8822_reset(c) wau8822_write(c, WAU8822_RESET, 0)
struct wau8822_priv {
enum snd_soc_control_type control_type;
void *control_data;
u16 suspended_cache[WAU8822_CACHEREG_NUM];
};
/* wau8822 default cfg values */
static const u16 wau8822_reg[WAU8822_CACHEREG_NUM] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0050, 0x0000, 0x0140, 0x0000,
0x0000, 0x0080, 0x0000, 0x00ff,
0x00ff, 0x0000, 0x0100, 0x00ff,
0x00ff, 0x0000, 0x012c, 0x002c,
0x002c, 0x002c, 0x002c, 0x0000,
0x0032, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0038, 0x000b, 0x0032, 0x0010,
0x0008, 0x000c, 0x0093, 0x00e9,
0x0000, 0x0000, 0x0000, 0x0000,
0x0033, 0x0010, 0x0010, 0x0100,
0x0100, 0x0002, 0x0001, 0x0001,
0x0039, 0x0039, 0x0000, 0x0000,
0x0001, 0x0001, 0x0000, 0x0000,
0x0020, 0x000 , 0x0000, 0x001A,
};
/* wau8822 custom defaults values */
static const struct reg_default wau8822_reg_defaults[] = {
/* starting values other than default ones */
{WAU8822_POWER1, 0x001f}, /*
* VREF 3kohm nominal impedance
* tie-off buffer power on
* bias buffer power on
* micbias enabled
*/
{WAU8822_POWER2, 0x01bf}, /*
* left ADC on
* right ADC on
* left PGA on
* right PGA on
* left ADC mix / boost on
* right ADC mix / boost on
* left headphone on
* right headphone on
*/
{WAU8822_POWER3, 0x006f}, /*
* left DAC on
* right DAC on
* left main mixer on
* right main mixer on
* left spk on
* right spk on
*/
{WAU8822_IFACE, 0x0070}, /* standard i2s */
{WAU8822_CLOCK1, 0x0000}, /* divider for fs set to 1 */
{WAU8822_GPIO, 0x0004}, /* output divided PLL clock */
{WAU8822_DAC, 0x0008}, /* 128x oversampling */
{WAU8822_ADCVOLL, 0x01ff}, /* left adc set to 0dB */
{WAU8822_ADCVOLR, 0x01ff}, /* right adc set to 0dB */
{WAU8822_INPUT, 0x0000}, /* disconnect all input */
{WAU8822_INPGAL, 0x0110}, /* left PGA 0dB */
{WAU8822_INPGAR, 0x0110}, /* right PGA 0dB */
{WAU8822_ADCBOOSTL, 0x0055}, /*
* left line-in 0dB
* left aux-in 0dB
*/
{WAU8822_ADCBOOSTR, 0x0055}, /*
* right line-in 0dB
* right aux-in 0dB
*/
};
static inline unsigned int wau8822_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
u16 *cache = codec->reg_cache;
if (reg == WAU8822_RESET)
return 0;
if (reg >= WAU8822_CACHEREG_NUM)
return -1;
return cache[reg];
}
static inline void wau8822_write_reg_cache(struct snd_soc_codec *codec,
u16 reg, unsigned int value)
{
u16 *cache = codec->reg_cache;
if (reg >= WAU8822_CACHEREG_NUM)
return;
cache[reg] = value;
}
static int wau8822_write(struct snd_soc_codec *codec,
unsigned int reg,
unsigned int value)
{
int ret = 0;
wau8822_write_reg_cache(codec, reg, value);
ret = snd_soc_write(codec, reg, value);
if (!ret) {
dev_dbg(codec->dev, "%s: reg = 0x%x ; value = 0x%x success\n",
__func__, reg, value);
} else {
dev_err(codec->dev, "%s: reg = 0x%x ; value = 0x%x error\n",
__func__, reg, value);
ret = -EIO;
}
return ret;
}
static const char * const wau8822_pga_input_sel_text[] = {
"None", "Line", "Mic"
};
static const struct soc_enum wau8822_pga_input_sel_enum =
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wau8822_pga_input_sel_text),
wau8822_pga_input_sel_text);
int wau8822_vol_update(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int reg = mc->reg;
unsigned int reg2 = mc->rreg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
int err;
bool type_2r = 0;
unsigned int val2 = 0;
unsigned int val, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
if (invert)
val = max - val;
val_mask = mask << shift;
val = val << shift;
if (snd_soc_volsw_is_stereo(mc)) {
val2 = (ucontrol->value.integer.value[1] & mask);
if (invert)
val2 = max - val2;
if (reg == reg2) {
val_mask |= mask << rshift;
val |= val2 << rshift;
} else {
val2 = val2 << shift;
type_2r = 1;
}
}
err = snd_soc_update_bits_locked(codec,
reg,
val_mask | 0x100,
val | 0x100);
if (err < 0)
return err;
if (type_2r)
err = snd_soc_update_bits_locked(codec,
reg2,
val_mask | 0x100,
val2 | 0x100);
return err;
}
static int wau8822_pga_input_get_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret = 0;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
u16 input_left = wau8822_read_reg_cache(codec, WAU8822_INPUT) & 0x7;
u16 input_right = (wau8822_read_reg_cache(codec, WAU8822_INPUT) >> 4)
& 0x7;
if (!input_left && !input_right) {
/* PGA input none */
ucontrol->value.integer.value[0] = 0;
} else if (input_left == 0x3 && input_right == 0x3) {
/* PGA input is Mic */
ucontrol->value.integer.value[0] = 2;
} else if (input_left == 0x4 && input_right == 0x4) {
/* PGA input is Line-in */
ucontrol->value.integer.value[0] = 1;
} else {
ret = -EINVAL;
dev_err(codec->dev, "%s: unexpected control value\n", __func__);
}
return ret;
}
static int wau8822_pga_input_set_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret = 0;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
u16 input = wau8822_read_reg_cache(codec, WAU8822_INPUT) & ~0x77;
switch (ucontrol->value.integer.value[0]) {
case 0:
break;
case 1:
/* Line-in */
input |= 0x44;
break;
case 2:
/* Mic */
input |= 0x33;
break;
default:
ret = -EINVAL;
dev_err(codec->dev, "%s: unexpected control value\n", __func__);
goto error;
break;
}
wau8822_write(codec, WAU8822_INPUT, input);
error:
return ret;
}
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 0);
static const DECLARE_TLV_DB_SCALE(pga_tlv, -1200, 75, 0);
static const DECLARE_TLV_DB_SCALE(hp_tlv, -5700, 100, 0);
static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 0);
static const struct snd_kcontrol_new wau8822_snd_controls[] = {
SOC_SINGLE("Digital Loopback Switch", WAU8822_COMP, 0, 1, 0),
SOC_DOUBLE_R_EXT_TLV("DAC Playback Volume",
WAU8822_DACVOLL, WAU8822_DACVOLR, 0, 255, 0,
snd_soc_get_volsw, wau8822_vol_update, dac_tlv),
SOC_DOUBLE_R_EXT_TLV("ADC Capture Volume",
WAU8822_ADCVOLL, WAU8822_ADCVOLR, 0, 255, 0,
snd_soc_get_volsw, wau8822_vol_update, dac_tlv),
SOC_DOUBLE_R_EXT_TLV("PGA Capture Volume",
WAU8822_INPGAL, WAU8822_INPGAR, 0, 63, 0,
snd_soc_get_volsw, wau8822_vol_update, pga_tlv),
SOC_DOUBLE_R_EXT_TLV("Headphone Playback Volume",
WAU8822_HPVOLL, WAU8822_HPVOLR, 0, 63, 0,
snd_soc_get_volsw, wau8822_vol_update, hp_tlv),
SOC_DOUBLE_R("Headphone Playback Switch",
WAU8822_HPVOLL, WAU8822_HPVOLR, 6, 1, 1),
SOC_DOUBLE_R_EXT_TLV("Speaker Playback Volume",
WAU8822_SPKVOLL, WAU8822_SPKVOLR, 0, 63, 0,
snd_soc_get_volsw, wau8822_vol_update, hp_tlv),
SOC_DOUBLE_R("Speaker Playback Switch",
WAU8822_SPKVOLL, WAU8822_SPKVOLR, 6, 1, 1),
SOC_DOUBLE_R_TLV("Line In Capture Volume", WAU8822_ADCBOOSTL,
WAU8822_ADCBOOSTR, 4, 7, 0, boost_tlv),
SOC_DOUBLE_R_TLV("Aux In Capture Volume", WAU8822_ADCBOOSTL,
WAU8822_ADCBOOSTR, 0, 7, 0, boost_tlv),
SOC_DOUBLE_R("Capture Boost(+20dB)",
WAU8822_ADCBOOSTL, WAU8822_ADCBOOSTR, 8, 1, 0),
SOC_ENUM_EXT("PGA Input Selection", wau8822_pga_input_sel_enum,
wau8822_pga_input_get_switch,
wau8822_pga_input_set_switch),
};
static int wau8822_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
u16 iface = wau8822_read_reg_cache(codec, WAU8822_IFACE) & 0x19f;
u16 clk2 = wau8822_read_reg_cache(codec, WAU8822_CLOCK2) & 0x1f1;
/* set word length */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
case SNDRV_PCM_FORMAT_S20_3LE:
iface |= 0x0020;
break;
case SNDRV_PCM_FORMAT_S24_LE:
iface |= 0x0040;
break;
case SNDRV_PCM_FORMAT_S32_LE:
iface |= 0x0060;
break;
}
/* set sample rate */
switch (params_rate(params)) {
case SNDRV_PCM_RATE_8000:
clk2 |= 0x5 << 1;
break;
case SNDRV_PCM_RATE_11025:
clk2 |= 0x4 << 1;
break;
case SNDRV_PCM_RATE_16000:
clk2 |= 0x3 << 1;
break;
case SNDRV_PCM_RATE_22050:
clk2 |= 0x2 << 1;
break;
case SNDRV_PCM_RATE_32000:
clk2 |= 0x1 << 1;
break;
case SNDRV_PCM_RATE_44100:
case SNDRV_PCM_RATE_48000:
break;
}
wau8822_write(codec, WAU8822_IFACE, iface);
wau8822_write(codec, WAU8822_CLOCK2, clk2);
return 0;
}
static int wau8822_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 iface = wau8822_read_reg_cache(codec, WAU8822_IFACE) & 0x67;
u16 clk1 = wau8822_read_reg_cache(codec, WAU8822_CLOCK1) & 0x1fe;
/* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
clk1 |= 0x0001;
break;
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
return -EINVAL;
}
/* interface format */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
iface |= 0x0010;
break;
case SND_SOC_DAIFMT_RIGHT_J:
break;
case SND_SOC_DAIFMT_LEFT_J:
iface |= 0x0008;
break;
case SND_SOC_DAIFMT_DSP_A:
iface |= 0x00018;
break;
default:
return -EINVAL;
}
/* clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
iface |= 0x0180;
break;
case SND_SOC_DAIFMT_IB_NF:
iface |= 0x0100;
break;
case SND_SOC_DAIFMT_NB_IF:
iface |= 0x0080;
break;
default:
return -EINVAL;
}
wau8822_write(codec, WAU8822_IFACE, iface);
wau8822_write(codec, WAU8822_CLOCK1, clk1);
return 0;
}
static int wau8822_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
int div_id, int div)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg;
switch (div_id) {
case WAU8822_OPCLKDIV:
reg = wau8822_read_reg_cache(codec, WAU8822_GPIO) & 0x1cf;
wau8822_write(codec, WAU8822_GPIO, reg | div);
break;
case WAU8822_MCLKDIV:
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1) & 0x11f;
wau8822_write(codec, WAU8822_CLOCK1, reg | div);
break;
case WAU8822_ADCCLK:
reg = wau8822_read_reg_cache(codec, WAU8822_ADC) & 0x1f7;
wau8822_write(codec, WAU8822_ADC, reg | div);
break;
case WAU8822_DACCLK:
reg = wau8822_read_reg_cache(codec, WAU8822_DAC) & 0x1f7;
wau8822_write(codec, WAU8822_DAC, reg | div);
break;
case WAU8822_BCLKDIV:
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1) & 0x1e3;
wau8822_write(codec, WAU8822_CLOCK1, reg | div);
break;
default:
return -EINVAL;
}
return 0;
}
struct pll_ {
unsigned int pre_div:4; /* prescale - 1 */
unsigned int n:4;
unsigned int k;
};
static struct pll_ pll_div;
/*
* The size in bits of the pll divide multiplied by 10
* to allow rounding later
*/
#define FIXED_PLL_SIZE ((1 << 24) * 10)
static void pll_factors(unsigned int target, unsigned int source)
{
unsigned long long Kpart;
unsigned int K, Ndiv, Nmod;
Ndiv = target / source;
if (Ndiv < 6) {
source >>= 1;
pll_div.pre_div = 1;
Ndiv = target / source;
} else {
pll_div.pre_div = 0;
}
if ((Ndiv < 6) || (Ndiv > 12))
pr_warn("WAU8822 N value %d outwith recommended range!\n",
Ndiv);
pll_div.n = Ndiv;
Nmod = target % source;
Kpart = FIXED_PLL_SIZE * (long long)Nmod;
do_div(Kpart, source);
K = Kpart & 0xffffffff;
/* Check if we need to round */
if ((K % 10) >= 5)
K += 5;
/* Move down to proper range now rounding is done */
K /= 10;
pll_div.k = K;
pr_debug(CODEC_NAME "%s: pll_factor, freq_in %d, freq_out %d, "
"pre_div %d, Ndiv %d, K 0x%x\n", __func__,
source, target, pll_div.pre_div, pll_div.n, pll_div.k);
}
static int wau8822_set_dai_pll(struct snd_soc_dai *codec_dai,
int pll_id, int source,
unsigned int freq_in, unsigned int freq_out)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg;
dev_dbg(codec->dev, "%s: pll_id %d, freq_in %d, frq_out %d\n",
__func__, pll_id, freq_in, freq_out);
if (freq_in == 0 || freq_out == 0) {
/* Clock CODEC directly from MCLK, MCLK is master clock */
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1);
wau8822_write(codec, WAU8822_CLOCK1, reg & 0x0ff);
/* Turn off PLL */
reg = wau8822_read_reg_cache(codec, WAU8822_POWER1);
wau8822_write(codec, WAU8822_POWER1, reg & 0x1df);
return 0;
}
/* compute PLL factors */
pll_factors(freq_out * 8, freq_in);
wau8822_write(codec, WAU8822_PLLN, (pll_div.pre_div << 4) | pll_div.n);
wau8822_write(codec, WAU8822_PLLK1, pll_div.k >> 18);
wau8822_write(codec, WAU8822_PLLK2, (pll_div.k >> 9) & 0x1ff);
wau8822_write(codec, WAU8822_PLLK3, pll_div.k & 0x1ff);
/* Turn on PLL */
reg = wau8822_read_reg_cache(codec, WAU8822_POWER1);
wau8822_write(codec, WAU8822_POWER1, reg | 0x020);
/* Run CODEC from PLL instead of MCLK */
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1) & 0x11f;
wau8822_write(codec, WAU8822_CLOCK1, reg | 0x100);
/* adapt the MCLK */
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1);
wau8822_write(codec, WAU8822_CLOCK1, reg | (0x020 << pll_div.pre_div));
/* to try to put the out in aiz */
wau8822_write(codec, WAU8822_MISC, 0x0);
/*
* to adapt bclk - McBSP driver currently
* only support 16bits data
*/
reg = wau8822_read_reg_cache(codec, WAU8822_CLOCK1) & 0x1e3;
/*
* if divided by 8 -> 16BCLK per channel
* if divided by 4 -> 32BCLK per channel
* wau8822_write(codec, WAU8822_CLOCK1, reg | WAU8822_BCLKDIV_8);
*/
wau8822_write(codec, WAU8822_CLOCK1, reg | (2 << 2));
return 0;
}
static int wau8822_mute(struct snd_soc_dai *codec_dai, int mute)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 mute_reg = wau8822_read_reg_cache(codec, WAU8822_DAC) & 0xffbf;
if (mute)
wau8822_write(codec, WAU8822_DAC, mute_reg | 0x40);
else
wau8822_write(codec, WAU8822_DAC, mute_reg);
return 0;
}
static struct snd_soc_dai_ops wau8822_dai_ops_playback = {
.hw_params = wau8822_pcm_hw_params,
.set_fmt = wau8822_set_dai_fmt,
.set_clkdiv = wau8822_set_dai_clkdiv,
.set_pll = wau8822_set_dai_pll,
.digital_mute = wau8822_mute,
};
static int wau8822_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
u16 power1 = wau8822_read_reg_cache(codec, WAU8822_POWER1) & ~0x3;
switch (level) {
case SND_SOC_BIAS_ON:
case SND_SOC_BIAS_PREPARE:
power1 |= 0x1; /* VMID 50k */
wau8822_write(codec, WAU8822_POWER1, power1);
break;
case SND_SOC_BIAS_STANDBY:
power1 |= 0x08 | 0x04;
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
/* Initial cap charge at VMID 5k */
wau8822_write(codec, WAU8822_POWER1, power1 | 0x3);
mdelay(100);
}
power1 |= 0x2; /* VMID 500k */
wau8822_write(codec, WAU8822_POWER1, power1);
break;
case SND_SOC_BIAS_OFF:
wau8822_write(codec, WAU8822_POWER1, 0);
wau8822_write(codec, WAU8822_POWER2, 0);
wau8822_write(codec, WAU8822_POWER3, 0);
break;
}
codec->dapm.bias_level = level;
return 0;
}
static int wau8822_power_down(struct snd_soc_codec *codec)
{
/* power2 shutdown */
wau8822_write(codec, WAU8822_POWER2, 0x0);
/* power3 shutdown except main mixer */
wau8822_write(codec, WAU8822_POWER3, 0xc);
/* power3 complete shutdown */
wau8822_write(codec, WAU8822_POWER3, 0x0);
/* power2 shutdown */
wau8822_write(codec, WAU8822_POWER1, 0x0);
/* headphone output mute */
wau8822_write(codec, WAU8822_HPVOLL, 0x140);
wau8822_write(codec, WAU8822_HPVOLR, 0x140);
return wau8822_set_bias_level(codec, SND_SOC_BIAS_OFF);
}
static int wau8822_probe(struct snd_soc_codec *codec)
{
struct wau8822_priv *wau8822 = snd_soc_codec_get_drvdata(codec);
int ret = 0;
int i;
unsigned int value = 0;
dev_info(codec->dev, "WAU8822 Audio Codec");
codec->control_data = wau8822->control_data;
ret = snd_soc_codec_set_cache_io(codec, 7, 9, wau8822->control_type);
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
goto error;
}
/* Reset */
ret = wau8822_reset(codec);
if (ret != 0) {
dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
goto error;
}
/* power on device */
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
wau8822_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
/* initialize codec register */
for (i = 0; i < ARRAY_SIZE(wau8822_reg_defaults); i++) {
wau8822_write(codec, wau8822_reg_defaults[i].reg,
wau8822_reg_defaults[i].def);
}
/* update cache */
for (i = 0; i < WAU8822_CACHEREG_NUM; i++) {
value = snd_soc_read(codec, i);
wau8822_write_reg_cache(codec, i, value);
}
/* init controls */
dev_dbg(codec->dev, "init controls\n");
snd_soc_add_codec_controls(codec, wau8822_snd_controls,
ARRAY_SIZE(wau8822_snd_controls));
error:
return ret;
}
static int wau8822_remove(struct snd_soc_codec *codec)
{
return wau8822_power_down(codec);
}
static int wau8822_suspend(struct snd_soc_codec *codec)
{
struct wau8822_priv *wau8822 = snd_soc_codec_get_drvdata(codec);
u16 *cache = codec->reg_cache;
/* we only need to suspend if we are a valid card */
if (!codec->card)
return 0;
/* save cache for suspend */
memcpy(wau8822->suspended_cache, cache,
WAU8822_CACHEREG_NUM * sizeof(u16));
return wau8822_power_down(codec);
}
static int wau8822_resume(struct snd_soc_codec *codec)
{
int ret;
int i;
u16 *cache = codec->reg_cache;
struct wau8822_priv *wau8822 = snd_soc_codec_get_drvdata(codec);
/* we only need to resume if we are a valid card */
if (!codec->card)
return 0;
/* Reset */
ret = wau8822_reset(codec);
if (ret != 0) {
dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
goto error;
}
/* power on device */
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
wau8822_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
/* initialize codec register */
for (i = 0; i < ARRAY_SIZE(wau8822_reg_defaults); i++) {
snd_soc_write(codec, wau8822_reg_defaults[i].reg,
wau8822_reg_defaults[i].def);
}
/* restore suspended bias level */
wau8822_set_bias_level(codec, codec->dapm.suspend_bias_level);
/* restore cached regs */
memcpy(cache, wau8822->suspended_cache,
WAU8822_CACHEREG_NUM * sizeof(u16));
for (i = 0; i < ARRAY_SIZE(wau8822_reg); i++) {
if (i == WAU8822_RESET)
continue;
wau8822_write(codec, i, cache[i]);
}
error:
return ret;
}
static int wau8822_volatile_register(struct snd_soc_codec *codec,
unsigned int reg)
{
return reg == WAU8822_RESET;
}
struct snd_soc_codec_driver soc_codec_dev_wau8822 = {
.probe = wau8822_probe,
.remove = wau8822_remove,
.suspend = wau8822_suspend,
.resume = wau8822_resume,
.set_bias_level = wau8822_set_bias_level,
.volatile_register = wau8822_volatile_register,
.reg_cache_size = sizeof(wau8822_reg),
.reg_word_size = sizeof(u16),
.reg_cache_default = &wau8822_reg,
};
#define WAU8822_RATES (SNDRV_PCM_RATE_8000 | \
SNDRV_PCM_RATE_11025 | \
SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000)
#define WAU8822_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver wau8822_dai[] = {
{
.name = "wau8822-hifi",
.id = 0,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = WAU8822_RATES,
.formats = WAU8822_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = WAU8822_RATES,
.formats = WAU8822_FORMATS,
},
.ops = &wau8822_dai_ops_playback,
},
};
static int wau8822_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct wau8822_priv *wau8822;
int ret;
wau8822 = kzalloc(sizeof(struct wau8822_priv), GFP_KERNEL);
if (wau8822 == NULL)
return -ENOMEM;
i2c_set_clientdata(i2c, wau8822);
wau8822->control_data = i2c;
wau8822->control_type = SND_SOC_I2C;
ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wau8822,
wau8822_dai, ARRAY_SIZE(wau8822_dai));
if (ret < 0) {
kfree(wau8822);
pr_err(CODEC_NAME ": %s failed: ret = %d\n", __func__, ret);
}
return ret;
}
static int wau8822_i2c_remove(struct i2c_client *client)
{
struct snd_soc_codec *codec = i2c_get_clientdata(client);
kfree(codec);
snd_soc_unregister_codec(&client->dev);
return 0;
}
/*
* WAU8822 2 wire address is determined by A1 pin
* state during powerup.
* low = 0x1a
* high = 0x1b
*/
static const struct i2c_device_id wau8822_i2c_table[] = {
{"wau8822", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, wau8822_i2c_table);
/* i2c codec control layer */
static struct i2c_driver wau8822_i2c_driver = {
.driver = {
.name = "wau8822",
.owner = THIS_MODULE,
},
.probe = wau8822_i2c_probe,
.remove = wau8822_i2c_remove,
.id_table = wau8822_i2c_table,
};
static int __init wau8822_modinit(void)
{
int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
pr_info(CODEC_NAME ": module init\n");
ret = i2c_add_driver(&wau8822_i2c_driver);
if (ret != 0)
pr_err(CODEC_NAME
": Failed to register wau8822 I2C driver:%d\n",
ret);
#endif
return ret;
}
module_init(wau8822_modinit);
static void __exit wau8822_modexit(void)
{
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
pr_info(CODEC_NAME ": module exit\n");
i2c_del_driver(&wau8822_i2c_driver);
#endif
}
module_exit(wau8822_modexit);
MODULE_DESCRIPTION("ASoC wau8822 driver");
MODULE_LICENSE("GPL");