diff options
Diffstat (limited to 'sound/soc/codecs/wm8960.c')
| -rw-r--r-- | sound/soc/codecs/wm8960.c | 583 |
1 files changed, 308 insertions, 275 deletions
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 7233cc68435..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 @@ -14,23 +16,17 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> -#include <linux/platform_device.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> -#include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> #include <sound/wm8960.h> #include "wm8960.h" -#define AUDIO_NAME "wm8960" - -struct snd_soc_codec_device soc_codec_dev_wm8960; - /* R25 - Power 1 */ #define WM8960_VMID_MASK 0x180 #define WM8960_VREF 0x40 @@ -56,35 +52,84 @@ struct snd_soc_codec_device soc_codec_dev_wm8960; * 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 { - u16 reg_cache[WM8960_CACHEREGNUM]; - struct snd_soc_codec codec; + struct regmap *regmap; + int (*set_bias_level)(struct snd_soc_codec *, + enum snd_soc_bias_level level); struct snd_soc_dapm_widget *lout1; struct snd_soc_dapm_widget *rout1; struct snd_soc_dapm_widget *out3; + bool deemph; + int playback_fs; }; #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) /* enumerated controls */ -static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", "Right Inverted", "Stereo Inversion"}; static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; @@ -93,7 +138,6 @@ static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; static const struct soc_enum wm8960_enum[] = { - SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), @@ -102,10 +146,65 @@ static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), }; +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->playback_fs) < + abs(deemph_settings[best] - wm8960->playback_fs)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + 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; + return 0; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + 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]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(codec); +} + 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, @@ -115,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), @@ -131,31 +239,32 @@ SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), -SOC_ENUM("ADC Polarity", wm8960_enum[1]), -SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), SOC_ENUM("DAC Polarity", wm8960_enum[2]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), -SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), -SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), -SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_ENUM("ALC Function", wm8960_enum[4]), SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), -SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_ENUM("ALC Mode", wm8960_enum[5]), SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), 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), @@ -212,7 +321,7 @@ SND_SOC_DAPM_INPUT("RINPUT2"), SND_SOC_DAPM_INPUT("LINPUT3"), SND_SOC_DAPM_INPUT("RINPUT3"), -SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), +SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), @@ -224,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), @@ -333,27 +442,28 @@ static int wm8960_add_widgets(struct snd_soc_codec *codec) { struct wm8960_data *pdata = codec->dev->platform_data; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; struct snd_soc_dapm_widget *w; - snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets)); - snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); /* In capless mode OUT3 is used to provide VMID for the * headphone outputs, otherwise it is used as a mono mixer. */ if (pdata && pdata->capless) { - snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless, + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, ARRAY_SIZE(wm8960_dapm_widgets_capless)); - snd_soc_dapm_add_routes(codec, audio_paths_capless, + snd_soc_dapm_add_routes(dapm, audio_paths_capless, ARRAY_SIZE(audio_paths_capless)); } else { - snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3, + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, ARRAY_SIZE(wm8960_dapm_widgets_out3)); - snd_soc_dapm_add_routes(codec, audio_paths_out3, + snd_soc_dapm_add_routes(dapm, audio_paths_out3, ARRAY_SIZE(audio_paths_out3)); } @@ -362,7 +472,9 @@ static int wm8960_add_widgets(struct snd_soc_codec *codec) * list each time to find the desired power state do so now * and save the result. */ - list_for_each_entry(w, &codec->dapm_widgets, list) { + list_for_each_entry(w, &codec->card->widgets, list) { + if (w->dapm != &codec->dapm) + continue; if (strcmp(w->name, "LOUT1 PGA") == 0) wm8960->lout1 = w; if (strcmp(w->name, "ROUT1 PGA") == 0) @@ -433,25 +545,59 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + 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_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->card->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 */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + wm8960->playback_fs = params_rate(params); + wm8960_set_deemph(codec); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_update_bits(codec, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); } /* set iface */ @@ -462,19 +608,18 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, static int wm8960_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; if (mute) - snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0x8); else - snd_soc_write(codec, WM8960_DACCTL1, mute_reg); + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0); return 0; } static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 reg; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); switch (level) { case SND_SOC_BIAS_ON: @@ -482,37 +627,32 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* Set VMID to 2x50k */ - reg = snd_soc_read(codec, WM8960_POWER1); - reg &= ~0x180; - reg |= 0x80; - snd_soc_write(codec, WM8960_POWER1, reg); + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80); break; case SND_SOC_BIAS_STANDBY: - if (codec->bias_level == SND_SOC_BIAS_OFF) { + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + regcache_sync(wm8960->regmap); + /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Enable & ramp VMID at 2x50k */ - reg = snd_soc_read(codec, WM8960_POWER1); - reg |= 0x80; - snd_soc_write(codec, WM8960_POWER1, reg); + snd_soc_update_bits(codec, WM8960_POWER1, 0x80, 0x80); msleep(100); /* Enable VREF */ - snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); + snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF, + WM8960_VREF); /* Disable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); } /* Set VMID to 2x250k */ - reg = snd_soc_read(codec, WM8960_POWER1); - reg &= ~0x180; - reg |= 0x100; - snd_soc_write(codec, WM8960_POWER1, reg); + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x100); break; case SND_SOC_BIAS_OFF: @@ -527,7 +667,7 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, break; } - codec->bias_level = level; + codec->dapm.bias_level = level; return 0; } @@ -543,7 +683,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_PREPARE: - switch (codec->bias_level) { + switch (codec->dapm.bias_level) { case SND_SOC_BIAS_STANDBY: /* Enable anti pop mode */ snd_soc_update_bits(codec, WM8960_APOP1, @@ -592,13 +732,16 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, WM8960_VREF | WM8960_VMID_MASK, 0); break; + case SND_SOC_BIAS_OFF: + regcache_sync(wm8960->regmap); + break; default: break; } break; case SND_SOC_BIAS_STANDBY: - switch (codec->bias_level) { + switch (codec->dapm.bias_level) { case SND_SOC_BIAS_PREPARE: /* Disable HP discharge */ snd_soc_update_bits(codec, WM8960_APOP2, @@ -622,7 +765,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, break; } - codec->bias_level = level; + codec->dapm.bias_level = level; return 0; } @@ -701,10 +844,8 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, /* Disable the PLL: even if we are changing the frequency the * PLL needs to be disabled while we do so. */ - snd_soc_write(codec, WM8960_CLOCK1, - snd_soc_read(codec, WM8960_CLOCK1) & ~1); - snd_soc_write(codec, WM8960_POWER2, - snd_soc_read(codec, WM8960_POWER2) & ~1); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0); + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0); if (!freq_in || !freq_out) return 0; @@ -716,18 +857,16 @@ 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); /* Turn it on */ - snd_soc_write(codec, WM8960_POWER2, - snd_soc_read(codec, WM8960_POWER2) | 1); + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0x1); msleep(250); - snd_soc_write(codec, WM8960_CLOCK1, - snd_soc_read(codec, WM8960_CLOCK1) | 1); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0x1); return 0; } @@ -766,13 +905,21 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, return 0; } +static int wm8960_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + return wm8960->set_bias_level(codec, level); +} + #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 #define WM8960_FORMATS \ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) -static struct snd_soc_dai_ops wm8960_dai_ops = { +static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, .digital_mute = wm8960_mute, .set_fmt = wm8960_set_dai_fmt, @@ -780,8 +927,8 @@ static struct snd_soc_dai_ops wm8960_dai_ops = { .set_pll = wm8960_set_dai_pll, }; -struct snd_soc_dai wm8960_dai = { - .name = "WM8960", +static struct snd_soc_dai_driver wm8960_dai = { + .name = "wm8960-hifi", .playback = { .stream_name = "Playback", .channels_min = 1, @@ -797,226 +944,131 @@ struct snd_soc_dai wm8960_dai = { .ops = &wm8960_dai_ops, .symmetric_rates = 1, }; -EXPORT_SYMBOL_GPL(wm8960_dai); - -static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - - codec->set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} -static int wm8960_resume(struct platform_device *pdev) +static int wm8960_suspend(struct snd_soc_codec *codec) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - int i; - u8 data[2]; - u16 *cache = codec->reg_cache; - - /* Sync reg_cache with the hardware */ - for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { - data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); - data[1] = cache[i] & 0x00ff; - codec->hw_write(codec->control_data, data, 2); - } - - codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } -static struct snd_soc_codec *wm8960_codec; - -static int wm8960_probe(struct platform_device *pdev) +static int wm8960_resume(struct snd_soc_codec *codec) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec; - int ret = 0; - - if (wm8960_codec == NULL) { - dev_err(&pdev->dev, "Codec device not registered\n"); - return -ENODEV; - } - - socdev->card->codec = wm8960_codec; - codec = wm8960_codec; - - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - dev_err(codec->dev, "failed to create pcms: %d\n", ret); - goto pcm_err; - } - - snd_soc_add_controls(codec, wm8960_snd_controls, - ARRAY_SIZE(wm8960_snd_controls)); - wm8960_add_widgets(codec); - - return ret; - -pcm_err: - return ret; -} - -/* power down chip */ -static int wm8960_remove(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } -struct snd_soc_codec_device soc_codec_dev_wm8960 = { - .probe = wm8960_probe, - .remove = wm8960_remove, - .suspend = wm8960_suspend, - .resume = wm8960_resume, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); - -static int wm8960_register(struct wm8960_priv *wm8960, - enum snd_soc_control_type control) +static int wm8960_probe(struct snd_soc_codec *codec) { - struct wm8960_data *pdata = wm8960->codec.dev->platform_data; - struct snd_soc_codec *codec = &wm8960->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = dev_get_platdata(codec->dev); int ret; - u16 reg; - if (wm8960_codec) { - dev_err(codec->dev, "Another WM8960 is registered\n"); - ret = -EINVAL; - goto err; - } - - codec->set_bias_level = wm8960_set_bias_level_out3; + wm8960->set_bias_level = wm8960_set_bias_level_out3; 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) - codec->set_bias_level = wm8960_set_bias_level_capless; - } - - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - - snd_soc_codec_set_drvdata(codec, wm8960); - codec->name = "WM8960"; - codec->owner = THIS_MODULE; - codec->bias_level = SND_SOC_BIAS_OFF; - codec->dai = &wm8960_dai; - codec->num_dai = 1; - codec->reg_cache_size = WM8960_CACHEREGNUM; - codec->reg_cache = &wm8960->reg_cache; - - memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); - - ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); - if (ret < 0) { - dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); - goto err; + wm8960->set_bias_level = wm8960_set_bias_level_capless; } ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); - goto err; + return ret; } - wm8960_dai.dev = codec->dev; - - codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the update bits */ - reg = snd_soc_read(codec, WM8960_LINVOL); - snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); - reg = snd_soc_read(codec, WM8960_RINVOL); - snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); - reg = snd_soc_read(codec, WM8960_LADC); - snd_soc_write(codec, WM8960_LADC, reg | 0x100); - reg = snd_soc_read(codec, WM8960_RADC); - snd_soc_write(codec, WM8960_RADC, reg | 0x100); - reg = snd_soc_read(codec, WM8960_LDAC); - snd_soc_write(codec, WM8960_LDAC, reg | 0x100); - reg = snd_soc_read(codec, WM8960_RDAC); - snd_soc_write(codec, WM8960_RDAC, reg | 0x100); - reg = snd_soc_read(codec, WM8960_LOUT1); - snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); - reg = snd_soc_read(codec, WM8960_ROUT1); - snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); - reg = snd_soc_read(codec, WM8960_LOUT2); - snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); - reg = snd_soc_read(codec, WM8960_ROUT2); - snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); - - wm8960_codec = codec; - - ret = snd_soc_register_codec(codec); - if (ret != 0) { - dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; - } - - ret = snd_soc_register_dai(&wm8960_dai); - if (ret != 0) { - dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - goto err_codec; - } + snd_soc_update_bits(codec, WM8960_LINVOL, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_RINVOL, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_LADC, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_RADC, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_LDAC, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_RDAC, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_LOUT1, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_ROUT1, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100); + snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100); + + snd_soc_add_codec_controls(codec, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(codec); return 0; - -err_codec: - snd_soc_unregister_codec(codec); -err: - kfree(wm8960); - return ret; } -static void wm8960_unregister(struct wm8960_priv *wm8960) +/* power down chip */ +static int wm8960_remove(struct snd_soc_codec *codec) { - wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); - snd_soc_unregister_dai(&wm8960_dai); - snd_soc_unregister_codec(&wm8960->codec); - kfree(wm8960); - wm8960_codec = NULL; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; } -static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { + .probe = wm8960_probe, + .remove = wm8960_remove, + .suspend = wm8960_suspend, + .resume = wm8960_resume, + .set_bias_level = wm8960_set_bias_level, +}; + +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; - struct snd_soc_codec *codec; + int ret; - wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); + wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), + GFP_KERNEL); if (wm8960 == NULL) return -ENOMEM; - codec = &wm8960->codec; + 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); - codec->control_data = i2c; - codec->dev = &i2c->dev; + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_wm8960, &wm8960_dai, 1); - return wm8960_register(wm8960, SND_SOC_I2C); + return ret; } -static __devexit int wm8960_i2c_remove(struct i2c_client *client) +static int wm8960_i2c_remove(struct i2c_client *client) { - struct wm8960_priv *wm8960 = i2c_get_clientdata(client); - wm8960_unregister(wm8960); + snd_soc_unregister_codec(&client->dev); return 0; } @@ -1032,30 +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; - - 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"); |
