diff options
Diffstat (limited to 'sound/soc/codecs/wm8960.c')
| -rw-r--r-- | sound/soc/codecs/wm8960.c | 197 |
1 files changed, 130 insertions, 67 deletions
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index e5caae32e54..a145d0431b6 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -1,6 +1,8 @@ /* * wm8960.c -- WM8960 ALSA SoC Audio driver * + * Copyright 2007-11 Wolfson Microelectronics, plc + * * Author: Liam Girdwood * * This program is free software; you can redistribute it and/or modify @@ -50,25 +52,72 @@ * We can't read the WM8960 register space when we are * using 2 wire for device control, so we cache them instead. */ -static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { - 0x0097, 0x0097, 0x0000, 0x0000, - 0x0000, 0x0008, 0x0000, 0x000a, - 0x01c0, 0x0000, 0x00ff, 0x00ff, - 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x007b, 0x0100, 0x0032, - 0x0000, 0x00c3, 0x00c3, 0x01c0, - 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, - 0x0100, 0x0100, 0x0050, 0x0050, - 0x0050, 0x0050, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0040, 0x0000, - 0x0000, 0x0050, 0x0050, 0x0000, - 0x0002, 0x0037, 0x004d, 0x0080, - 0x0008, 0x0031, 0x0026, 0x00e9, +static const struct reg_default wm8960_reg_defaults[] = { + { 0x0, 0x00a7 }, + { 0x1, 0x00a7 }, + { 0x2, 0x0000 }, + { 0x3, 0x0000 }, + { 0x4, 0x0000 }, + { 0x5, 0x0008 }, + { 0x6, 0x0000 }, + { 0x7, 0x000a }, + { 0x8, 0x01c0 }, + { 0x9, 0x0000 }, + { 0xa, 0x00ff }, + { 0xb, 0x00ff }, + + { 0x10, 0x0000 }, + { 0x11, 0x007b }, + { 0x12, 0x0100 }, + { 0x13, 0x0032 }, + { 0x14, 0x0000 }, + { 0x15, 0x00c3 }, + { 0x16, 0x00c3 }, + { 0x17, 0x01c0 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + + { 0x20, 0x0100 }, + { 0x21, 0x0100 }, + { 0x22, 0x0050 }, + + { 0x25, 0x0050 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x0000 }, + { 0x2a, 0x0040 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0000 }, + { 0x2d, 0x0050 }, + { 0x2e, 0x0050 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0002 }, + { 0x31, 0x0037 }, + + { 0x33, 0x0080 }, + { 0x34, 0x0008 }, + { 0x35, 0x0031 }, + { 0x36, 0x0026 }, + { 0x37, 0x00e9 }, }; +static bool wm8960_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8960_RESET: + return true; + default: + return false; + } +} + struct wm8960_priv { - enum snd_soc_control_type control_type; + struct regmap *regmap; int (*set_bias_level)(struct snd_soc_codec *, enum snd_soc_bias_level level); struct snd_soc_dapm_widget *lout1; @@ -129,7 +178,7 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec) static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8960->deemph; @@ -139,7 +188,7 @@ static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; @@ -155,6 +204,7 @@ static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1); static const struct snd_kcontrol_new wm8960_snd_controls[] = { SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, @@ -164,6 +214,15 @@ SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 7, 1, 0), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX1, 4, 7, 0, boost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX1, 1, 7, 0, boost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, boost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, boost_tlv), + SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 0, 255, 0, dac_tlv), @@ -204,8 +263,8 @@ SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), -SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, - 0, 127, 0), +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", WM8960_BYPASS1, 4, 7, 1, bypass_tlv), @@ -274,8 +333,8 @@ SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, wm8960_rin, ARRAY_SIZE(wm8960_rin)), -SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), -SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0), SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), @@ -505,22 +564,28 @@ static int wm8960_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; + struct snd_soc_codec *codec = dai->codec; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + snd_pcm_format_t format = params_format(params); int i; /* bit size */ - switch (params_format(params)) { + switch (format) { case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: break; case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: iface |= 0x0004; break; case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: iface |= 0x0008; break; + default: + dev_err(codec->dev, "unsupported format %i\n", format); + return -EINVAL; } /* Update filters for the new rate */ @@ -554,6 +619,8 @@ static int wm8960_mute(struct snd_soc_dai *dai, int mute) static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + switch (level) { case SND_SOC_BIAS_ON: break; @@ -565,7 +632,7 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { - snd_soc_cache_sync(codec); + regcache_sync(wm8960->regmap); /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, @@ -666,7 +733,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_OFF: - snd_soc_cache_sync(codec); + regcache_sync(wm8960->regmap); break; default: break; @@ -790,9 +857,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, if (pll_div.k) { reg |= 0x20; - snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); - snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); - snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); + snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0xff); } snd_soc_write(codec, WM8960_PLL1, reg); @@ -905,21 +972,10 @@ static int wm8960_probe(struct snd_soc_codec *codec) if (!pdata) { dev_warn(codec->dev, "No platform data supplied\n"); } else { - if (pdata->dres > WM8960_DRES_MAX) { - dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); - pdata->dres = 0; - } - if (pdata->capless) wm8960->set_bias_level = wm8960_set_bias_level_capless; } - ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type); - if (ret < 0) { - dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); - return ret; - } - ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); @@ -940,7 +996,7 @@ static int wm8960_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100); snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100); - snd_soc_add_controls(codec, wm8960_snd_controls, + snd_soc_add_codec_controls(codec, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls)); wm8960_add_widgets(codec); @@ -962,14 +1018,24 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { .suspend = wm8960_suspend, .resume = wm8960_resume, .set_bias_level = wm8960_set_bias_level, - .reg_cache_size = ARRAY_SIZE(wm8960_reg), - .reg_word_size = sizeof(u16), - .reg_cache_default = wm8960_reg, }; -static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static const struct regmap_config wm8960_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8960_PLL4, + + .reg_defaults = wm8960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8960_volatile, +}; + +static int wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { + struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; int ret; @@ -978,8 +1044,21 @@ static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, if (wm8960 == NULL) return -ENOMEM; + wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata && pdata->shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + i2c_set_clientdata(i2c, wm8960); - wm8960->control_type = SND_SOC_I2C; ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8960, &wm8960_dai, 1); @@ -987,7 +1066,7 @@ static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, return ret; } -static __devexit int wm8960_i2c_remove(struct i2c_client *client) +static int wm8960_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); return 0; @@ -1005,27 +1084,11 @@ static struct i2c_driver wm8960_i2c_driver = { .owner = THIS_MODULE, }, .probe = wm8960_i2c_probe, - .remove = __devexit_p(wm8960_i2c_remove), + .remove = wm8960_i2c_remove, .id_table = wm8960_i2c_id, }; -static int __init wm8960_modinit(void) -{ - int ret = 0; - ret = i2c_add_driver(&wm8960_i2c_driver); - if (ret != 0) { - printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", - ret); - } - return ret; -} -module_init(wm8960_modinit); - -static void __exit wm8960_exit(void) -{ - i2c_del_driver(&wm8960_i2c_driver); -} -module_exit(wm8960_exit); +module_i2c_driver(wm8960_i2c_driver); MODULE_DESCRIPTION("ASoC WM8960 driver"); MODULE_AUTHOR("Liam Girdwood"); |
