| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * wm8510.c  --  WM8510 ALSA Soc Audio driver | 
|  | 3 | * | 
|  | 4 | * Copyright 2006 Wolfson Microelectronics PLC. | 
|  | 5 | * | 
| Liam Girdwood | d331124 | 2008-10-12 13:17:36 +0100 | [diff] [blame] | 6 | * Author: Liam Girdwood <lrg@slimlogic.co.uk> | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 7 | * | 
|  | 8 | * This program is free software; you can redistribute it and/or modify | 
|  | 9 | * it under the terms of the GNU General Public License version 2 as | 
|  | 10 | * published by the Free Software Foundation. | 
|  | 11 | */ | 
|  | 12 |  | 
|  | 13 | #include <linux/module.h> | 
|  | 14 | #include <linux/moduleparam.h> | 
|  | 15 | #include <linux/kernel.h> | 
|  | 16 | #include <linux/init.h> | 
|  | 17 | #include <linux/delay.h> | 
|  | 18 | #include <linux/pm.h> | 
|  | 19 | #include <linux/i2c.h> | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 20 | #include <linux/spi/spi.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 21 | #include <linux/slab.h> | 
| Mark Brown | 0a422e1 | 2011-08-02 13:03:04 +0900 | [diff] [blame] | 22 | #include <linux/of_device.h> | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 23 | #include <sound/core.h> | 
|  | 24 | #include <sound/pcm.h> | 
|  | 25 | #include <sound/pcm_params.h> | 
|  | 26 | #include <sound/soc.h> | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 27 | #include <sound/initval.h> | 
|  | 28 |  | 
|  | 29 | #include "wm8510.h" | 
|  | 30 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 31 | /* | 
|  | 32 | * wm8510 register cache | 
|  | 33 | * We can't read the WM8510 register space when we are | 
|  | 34 | * using 2 wire for device control, so we cache them instead. | 
|  | 35 | */ | 
|  | 36 | static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { | 
|  | 37 | 0x0000, 0x0000, 0x0000, 0x0000, | 
|  | 38 | 0x0050, 0x0000, 0x0140, 0x0000, | 
|  | 39 | 0x0000, 0x0000, 0x0000, 0x00ff, | 
|  | 40 | 0x0000, 0x0000, 0x0100, 0x00ff, | 
|  | 41 | 0x0000, 0x0000, 0x012c, 0x002c, | 
|  | 42 | 0x002c, 0x002c, 0x002c, 0x0000, | 
|  | 43 | 0x0032, 0x0000, 0x0000, 0x0000, | 
|  | 44 | 0x0000, 0x0000, 0x0000, 0x0000, | 
|  | 45 | 0x0038, 0x000b, 0x0032, 0x0000, | 
|  | 46 | 0x0008, 0x000c, 0x0093, 0x00e9, | 
|  | 47 | 0x0000, 0x0000, 0x0000, 0x0000, | 
|  | 48 | 0x0003, 0x0010, 0x0000, 0x0000, | 
|  | 49 | 0x0000, 0x0002, 0x0001, 0x0000, | 
|  | 50 | 0x0000, 0x0000, 0x0039, 0x0000, | 
| Mark Brown | 05a076d | 2008-06-06 17:13:53 +0100 | [diff] [blame] | 51 | 0x0001, | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 52 | }; | 
|  | 53 |  | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 54 | #define WM8510_POWER1_BIASEN  0x08 | 
|  | 55 | #define WM8510_POWER1_BUFIOEN 0x10 | 
|  | 56 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 57 | #define wm8510_reset(c)	snd_soc_write(c, WM8510_RESET, 0) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 58 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 59 | /* codec private data */ | 
|  | 60 | struct wm8510_priv { | 
|  | 61 | enum snd_soc_control_type control_type; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 62 | }; | 
|  | 63 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 64 | static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; | 
|  | 65 | static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; | 
|  | 66 | static const char *wm8510_alc[] = { "ALC", "Limiter" }; | 
|  | 67 |  | 
|  | 68 | static const struct soc_enum wm8510_enum[] = { | 
|  | 69 | SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ | 
|  | 70 | SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ | 
|  | 71 | SOC_ENUM_SINGLE(WM8510_DAC,  4, 4, wm8510_deemp), | 
|  | 72 | SOC_ENUM_SINGLE(WM8510_ALC3,  8, 2, wm8510_alc), | 
|  | 73 | }; | 
|  | 74 |  | 
|  | 75 | static const struct snd_kcontrol_new wm8510_snd_controls[] = { | 
|  | 76 |  | 
|  | 77 | SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), | 
|  | 78 |  | 
|  | 79 | SOC_ENUM("DAC Companding", wm8510_enum[1]), | 
|  | 80 | SOC_ENUM("ADC Companding", wm8510_enum[0]), | 
|  | 81 |  | 
|  | 82 | SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), | 
|  | 83 | SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), | 
|  | 84 |  | 
|  | 85 | SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), | 
|  | 86 |  | 
|  | 87 | SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), | 
|  | 88 | SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), | 
|  | 89 | SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), | 
|  | 90 |  | 
|  | 91 | SOC_SINGLE("Capture Volume", WM8510_ADCVOL,  0, 127, 0), | 
|  | 92 |  | 
|  | 93 | SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1,  8, 1, 0), | 
|  | 94 | SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1,  4, 15, 0), | 
|  | 95 | SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1,  0, 15, 0), | 
|  | 96 |  | 
|  | 97 | SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2,  4, 7, 0), | 
|  | 98 | SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2,  0, 15, 0), | 
|  | 99 |  | 
|  | 100 | SOC_SINGLE("ALC Enable Switch", WM8510_ALC1,  8, 1, 0), | 
|  | 101 | SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1,  3, 7, 0), | 
|  | 102 | SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1,  0, 7, 0), | 
|  | 103 |  | 
|  | 104 | SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2,  8, 1, 0), | 
|  | 105 | SOC_SINGLE("ALC Capture Hold", WM8510_ALC2,  4, 7, 0), | 
|  | 106 | SOC_SINGLE("ALC Capture Target", WM8510_ALC2,  0, 15, 0), | 
|  | 107 |  | 
|  | 108 | SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), | 
|  | 109 | SOC_SINGLE("ALC Capture Decay", WM8510_ALC3,  4, 15, 0), | 
|  | 110 | SOC_SINGLE("ALC Capture Attack", WM8510_ALC3,  0, 15, 0), | 
|  | 111 |  | 
|  | 112 | SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE,  3, 1, 0), | 
|  | 113 | SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE,  0, 7, 0), | 
|  | 114 |  | 
|  | 115 | SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA,  7, 1, 0), | 
|  | 116 | SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA,  0, 63, 0), | 
|  | 117 |  | 
|  | 118 | SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL,  7, 1, 0), | 
|  | 119 | SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL,  6, 1, 1), | 
|  | 120 | SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL,  0, 63, 0), | 
|  | 121 | SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), | 
|  | 122 |  | 
|  | 123 | SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST,  8, 1, 0), | 
|  | 124 | SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), | 
|  | 125 | }; | 
|  | 126 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 127 | /* Speaker Output Mixer */ | 
|  | 128 | static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { | 
|  | 129 | SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), | 
|  | 130 | SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), | 
|  | 131 | SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0), | 
|  | 132 | }; | 
|  | 133 |  | 
|  | 134 | /* Mono Output Mixer */ | 
|  | 135 | static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { | 
|  | 136 | SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), | 
|  | 137 | SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), | 
|  | 138 | SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), | 
|  | 139 | }; | 
|  | 140 |  | 
|  | 141 | static const struct snd_kcontrol_new wm8510_boost_controls[] = { | 
| Mark Brown | 8ae23ec | 2008-10-06 11:33:21 +0100 | [diff] [blame] | 142 | SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA,  6, 1, 1), | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 143 | SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), | 
|  | 144 | SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), | 
|  | 145 | }; | 
|  | 146 |  | 
|  | 147 | static const struct snd_kcontrol_new wm8510_micpga_controls[] = { | 
|  | 148 | SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0), | 
|  | 149 | SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0), | 
|  | 150 | SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0), | 
|  | 151 | }; | 
|  | 152 |  | 
|  | 153 | static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { | 
|  | 154 | SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, | 
|  | 155 | &wm8510_speaker_mixer_controls[0], | 
|  | 156 | ARRAY_SIZE(wm8510_speaker_mixer_controls)), | 
|  | 157 | SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, | 
|  | 158 | &wm8510_mono_mixer_controls[0], | 
|  | 159 | ARRAY_SIZE(wm8510_mono_mixer_controls)), | 
|  | 160 | SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), | 
|  | 161 | SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0), | 
|  | 162 | SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), | 
|  | 163 | SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), | 
|  | 164 | SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), | 
|  | 165 | SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), | 
|  | 166 |  | 
| Mark Brown | 2b5f34c | 2008-10-07 16:13:50 +0100 | [diff] [blame] | 167 | SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0, | 
|  | 168 | &wm8510_micpga_controls[0], | 
|  | 169 | ARRAY_SIZE(wm8510_micpga_controls)), | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 170 | SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, | 
|  | 171 | &wm8510_boost_controls[0], | 
|  | 172 | ARRAY_SIZE(wm8510_boost_controls)), | 
|  | 173 |  | 
|  | 174 | SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), | 
|  | 175 |  | 
|  | 176 | SND_SOC_DAPM_INPUT("MICN"), | 
|  | 177 | SND_SOC_DAPM_INPUT("MICP"), | 
|  | 178 | SND_SOC_DAPM_INPUT("AUX"), | 
|  | 179 | SND_SOC_DAPM_OUTPUT("MONOOUT"), | 
|  | 180 | SND_SOC_DAPM_OUTPUT("SPKOUTP"), | 
|  | 181 | SND_SOC_DAPM_OUTPUT("SPKOUTN"), | 
|  | 182 | }; | 
|  | 183 |  | 
| Mark Brown | b6709f3 | 2011-12-03 11:41:45 +0000 | [diff] [blame] | 184 | static const struct snd_soc_dapm_route wm8510_dapm_routes[] = { | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 185 | /* Mono output mixer */ | 
|  | 186 | {"Mono Mixer", "PCM Playback Switch", "DAC"}, | 
|  | 187 | {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, | 
|  | 188 | {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, | 
|  | 189 |  | 
|  | 190 | /* Speaker output mixer */ | 
|  | 191 | {"Speaker Mixer", "PCM Playback Switch", "DAC"}, | 
|  | 192 | {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, | 
|  | 193 | {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, | 
|  | 194 |  | 
|  | 195 | /* Outputs */ | 
|  | 196 | {"Mono Out", NULL, "Mono Mixer"}, | 
|  | 197 | {"MONOOUT", NULL, "Mono Out"}, | 
|  | 198 | {"SpkN Out", NULL, "Speaker Mixer"}, | 
|  | 199 | {"SpkP Out", NULL, "Speaker Mixer"}, | 
|  | 200 | {"SPKOUTN", NULL, "SpkN Out"}, | 
|  | 201 | {"SPKOUTP", NULL, "SpkP Out"}, | 
|  | 202 |  | 
|  | 203 | /* Microphone PGA */ | 
|  | 204 | {"Mic PGA", "MICN Switch", "MICN"}, | 
|  | 205 | {"Mic PGA", "MICP Switch", "MICP"}, | 
|  | 206 | { "Mic PGA", "AUX Switch", "Aux Input" }, | 
|  | 207 |  | 
|  | 208 | /* Boost Mixer */ | 
|  | 209 | {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, | 
|  | 210 | {"Boost Mixer", "Mic Volume", "MICP"}, | 
|  | 211 | {"Boost Mixer", "Aux Volume", "Aux Input"}, | 
|  | 212 |  | 
|  | 213 | {"ADC", NULL, "Boost Mixer"}, | 
|  | 214 | }; | 
|  | 215 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 216 | struct pll_ { | 
|  | 217 | unsigned int pre_div:4; /* prescale - 1 */ | 
|  | 218 | unsigned int n:4; | 
|  | 219 | unsigned int k; | 
|  | 220 | }; | 
|  | 221 |  | 
|  | 222 | static struct pll_ pll_div; | 
|  | 223 |  | 
|  | 224 | /* The size in bits of the pll divide multiplied by 10 | 
|  | 225 | * to allow rounding later */ | 
|  | 226 | #define FIXED_PLL_SIZE ((1 << 24) * 10) | 
|  | 227 |  | 
|  | 228 | static void pll_factors(unsigned int target, unsigned int source) | 
|  | 229 | { | 
|  | 230 | unsigned long long Kpart; | 
|  | 231 | unsigned int K, Ndiv, Nmod; | 
|  | 232 |  | 
|  | 233 | Ndiv = target / source; | 
|  | 234 | if (Ndiv < 6) { | 
|  | 235 | source >>= 1; | 
|  | 236 | pll_div.pre_div = 1; | 
|  | 237 | Ndiv = target / source; | 
|  | 238 | } else | 
|  | 239 | pll_div.pre_div = 0; | 
|  | 240 |  | 
|  | 241 | if ((Ndiv < 6) || (Ndiv > 12)) | 
|  | 242 | printk(KERN_WARNING | 
| Roel Kluin | 449bd54 | 2009-05-27 17:08:39 -0700 | [diff] [blame] | 243 | "WM8510 N value %u outwith recommended range!d\n", | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 244 | Ndiv); | 
|  | 245 |  | 
|  | 246 | pll_div.n = Ndiv; | 
|  | 247 | Nmod = target % source; | 
|  | 248 | Kpart = FIXED_PLL_SIZE * (long long)Nmod; | 
|  | 249 |  | 
|  | 250 | do_div(Kpart, source); | 
|  | 251 |  | 
|  | 252 | K = Kpart & 0xFFFFFFFF; | 
|  | 253 |  | 
|  | 254 | /* Check if we need to round */ | 
|  | 255 | if ((K % 10) >= 5) | 
|  | 256 | K += 5; | 
|  | 257 |  | 
|  | 258 | /* Move down to proper range now rounding is done */ | 
|  | 259 | K /= 10; | 
|  | 260 |  | 
|  | 261 | pll_div.k = K; | 
|  | 262 | } | 
|  | 263 |  | 
| Mark Brown | 8548803 | 2009-09-05 18:52:16 +0100 | [diff] [blame] | 264 | static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, | 
|  | 265 | int source, unsigned int freq_in, unsigned int freq_out) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 266 | { | 
|  | 267 | struct snd_soc_codec *codec = codec_dai->codec; | 
|  | 268 | u16 reg; | 
|  | 269 |  | 
|  | 270 | if (freq_in == 0 || freq_out == 0) { | 
|  | 271 | /* Clock CODEC directly from MCLK */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 272 | reg = snd_soc_read(codec, WM8510_CLOCK); | 
|  | 273 | snd_soc_write(codec, WM8510_CLOCK, reg & 0x0ff); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 274 |  | 
|  | 275 | /* Turn off PLL */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 276 | reg = snd_soc_read(codec, WM8510_POWER1); | 
|  | 277 | snd_soc_write(codec, WM8510_POWER1, reg & 0x1df); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 278 | return 0; | 
|  | 279 | } | 
|  | 280 |  | 
| Jonas Andersson | 86027ae | 2009-03-04 08:24:26 +0100 | [diff] [blame] | 281 | pll_factors(freq_out*4, freq_in); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 282 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 283 | snd_soc_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); | 
|  | 284 | snd_soc_write(codec, WM8510_PLLK1, pll_div.k >> 18); | 
|  | 285 | snd_soc_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); | 
|  | 286 | snd_soc_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); | 
|  | 287 | reg = snd_soc_read(codec, WM8510_POWER1); | 
|  | 288 | snd_soc_write(codec, WM8510_POWER1, reg | 0x020); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 289 |  | 
|  | 290 | /* Run CODEC from PLL instead of MCLK */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 291 | reg = snd_soc_read(codec, WM8510_CLOCK); | 
|  | 292 | snd_soc_write(codec, WM8510_CLOCK, reg | 0x100); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 293 |  | 
|  | 294 | return 0; | 
|  | 295 | } | 
|  | 296 |  | 
|  | 297 | /* | 
|  | 298 | * Configure WM8510 clock dividers. | 
|  | 299 | */ | 
| Liam Girdwood | e550e17 | 2008-07-07 16:07:52 +0100 | [diff] [blame] | 300 | static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 301 | int div_id, int div) | 
|  | 302 | { | 
|  | 303 | struct snd_soc_codec *codec = codec_dai->codec; | 
|  | 304 | u16 reg; | 
|  | 305 |  | 
|  | 306 | switch (div_id) { | 
|  | 307 | case WM8510_OPCLKDIV: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 308 | reg = snd_soc_read(codec, WM8510_GPIO) & 0x1cf; | 
|  | 309 | snd_soc_write(codec, WM8510_GPIO, reg | div); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 310 | break; | 
|  | 311 | case WM8510_MCLKDIV: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 312 | reg = snd_soc_read(codec, WM8510_CLOCK) & 0x11f; | 
|  | 313 | snd_soc_write(codec, WM8510_CLOCK, reg | div); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 314 | break; | 
|  | 315 | case WM8510_ADCCLK: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 316 | reg = snd_soc_read(codec, WM8510_ADC) & 0x1f7; | 
|  | 317 | snd_soc_write(codec, WM8510_ADC, reg | div); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 318 | break; | 
|  | 319 | case WM8510_DACCLK: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 320 | reg = snd_soc_read(codec, WM8510_DAC) & 0x1f7; | 
|  | 321 | snd_soc_write(codec, WM8510_DAC, reg | div); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 322 | break; | 
|  | 323 | case WM8510_BCLKDIV: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 324 | reg = snd_soc_read(codec, WM8510_CLOCK) & 0x1e3; | 
|  | 325 | snd_soc_write(codec, WM8510_CLOCK, reg | div); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 326 | break; | 
|  | 327 | default: | 
|  | 328 | return -EINVAL; | 
|  | 329 | } | 
|  | 330 |  | 
|  | 331 | return 0; | 
|  | 332 | } | 
|  | 333 |  | 
| Liam Girdwood | e550e17 | 2008-07-07 16:07:52 +0100 | [diff] [blame] | 334 | static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 335 | unsigned int fmt) | 
|  | 336 | { | 
|  | 337 | struct snd_soc_codec *codec = codec_dai->codec; | 
|  | 338 | u16 iface = 0; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 339 | u16 clk = snd_soc_read(codec, WM8510_CLOCK) & 0x1fe; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 340 |  | 
|  | 341 | /* set master/slave audio interface */ | 
|  | 342 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | 
|  | 343 | case SND_SOC_DAIFMT_CBM_CFM: | 
|  | 344 | clk |= 0x0001; | 
|  | 345 | break; | 
|  | 346 | case SND_SOC_DAIFMT_CBS_CFS: | 
|  | 347 | break; | 
|  | 348 | default: | 
|  | 349 | return -EINVAL; | 
|  | 350 | } | 
|  | 351 |  | 
|  | 352 | /* interface format */ | 
|  | 353 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | 
|  | 354 | case SND_SOC_DAIFMT_I2S: | 
|  | 355 | iface |= 0x0010; | 
|  | 356 | break; | 
|  | 357 | case SND_SOC_DAIFMT_RIGHT_J: | 
|  | 358 | break; | 
|  | 359 | case SND_SOC_DAIFMT_LEFT_J: | 
|  | 360 | iface |= 0x0008; | 
|  | 361 | break; | 
|  | 362 | case SND_SOC_DAIFMT_DSP_A: | 
|  | 363 | iface |= 0x00018; | 
|  | 364 | break; | 
|  | 365 | default: | 
|  | 366 | return -EINVAL; | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | /* clock inversion */ | 
|  | 370 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | 
|  | 371 | case SND_SOC_DAIFMT_NB_NF: | 
|  | 372 | break; | 
|  | 373 | case SND_SOC_DAIFMT_IB_IF: | 
|  | 374 | iface |= 0x0180; | 
|  | 375 | break; | 
|  | 376 | case SND_SOC_DAIFMT_IB_NF: | 
|  | 377 | iface |= 0x0100; | 
|  | 378 | break; | 
|  | 379 | case SND_SOC_DAIFMT_NB_IF: | 
|  | 380 | iface |= 0x0080; | 
|  | 381 | break; | 
|  | 382 | default: | 
|  | 383 | return -EINVAL; | 
|  | 384 | } | 
|  | 385 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 386 | snd_soc_write(codec, WM8510_IFACE, iface); | 
|  | 387 | snd_soc_write(codec, WM8510_CLOCK, clk); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 388 | return 0; | 
|  | 389 | } | 
|  | 390 |  | 
|  | 391 | static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, | 
| Mark Brown | dee89c4 | 2008-11-18 22:11:38 +0000 | [diff] [blame] | 392 | struct snd_pcm_hw_params *params, | 
|  | 393 | struct snd_soc_dai *dai) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 394 | { | 
|  | 395 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 396 | struct snd_soc_codec *codec = rtd->codec; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 397 | u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f; | 
|  | 398 | u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 399 |  | 
|  | 400 | /* bit size */ | 
|  | 401 | switch (params_format(params)) { | 
|  | 402 | case SNDRV_PCM_FORMAT_S16_LE: | 
|  | 403 | break; | 
|  | 404 | case SNDRV_PCM_FORMAT_S20_3LE: | 
|  | 405 | iface |= 0x0020; | 
|  | 406 | break; | 
|  | 407 | case SNDRV_PCM_FORMAT_S24_LE: | 
|  | 408 | iface |= 0x0040; | 
|  | 409 | break; | 
|  | 410 | case SNDRV_PCM_FORMAT_S32_LE: | 
|  | 411 | iface |= 0x0060; | 
|  | 412 | break; | 
|  | 413 | } | 
|  | 414 |  | 
|  | 415 | /* filter coefficient */ | 
|  | 416 | switch (params_rate(params)) { | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 417 | case 8000: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 418 | adn |= 0x5 << 1; | 
|  | 419 | break; | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 420 | case 11025: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 421 | adn |= 0x4 << 1; | 
|  | 422 | break; | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 423 | case 16000: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 424 | adn |= 0x3 << 1; | 
|  | 425 | break; | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 426 | case 22050: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 427 | adn |= 0x2 << 1; | 
|  | 428 | break; | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 429 | case 32000: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 430 | adn |= 0x1 << 1; | 
|  | 431 | break; | 
| Guennadi Liakhovetski | b3172f2 | 2009-12-24 01:13:51 +0100 | [diff] [blame] | 432 | case 44100: | 
|  | 433 | case 48000: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 434 | break; | 
|  | 435 | } | 
|  | 436 |  | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 437 | snd_soc_write(codec, WM8510_IFACE, iface); | 
|  | 438 | snd_soc_write(codec, WM8510_ADD, adn); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 439 | return 0; | 
|  | 440 | } | 
|  | 441 |  | 
| Liam Girdwood | e550e17 | 2008-07-07 16:07:52 +0100 | [diff] [blame] | 442 | static int wm8510_mute(struct snd_soc_dai *dai, int mute) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 443 | { | 
|  | 444 | struct snd_soc_codec *codec = dai->codec; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 445 | u16 mute_reg = snd_soc_read(codec, WM8510_DAC) & 0xffbf; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 446 |  | 
|  | 447 | if (mute) | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 448 | snd_soc_write(codec, WM8510_DAC, mute_reg | 0x40); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 449 | else | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 450 | snd_soc_write(codec, WM8510_DAC, mute_reg); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 451 | return 0; | 
|  | 452 | } | 
|  | 453 |  | 
|  | 454 | /* liam need to make this lower power with dapm */ | 
|  | 455 | static int wm8510_set_bias_level(struct snd_soc_codec *codec, | 
|  | 456 | enum snd_soc_bias_level level) | 
|  | 457 | { | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 458 | u16 power1 = snd_soc_read(codec, WM8510_POWER1) & ~0x3; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 459 |  | 
|  | 460 | switch (level) { | 
|  | 461 | case SND_SOC_BIAS_ON: | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 462 | case SND_SOC_BIAS_PREPARE: | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 463 | power1 |= 0x1;  /* VMID 50k */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 464 | snd_soc_write(codec, WM8510_POWER1, power1); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 465 | break; | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 466 |  | 
|  | 467 | case SND_SOC_BIAS_STANDBY: | 
|  | 468 | power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN; | 
|  | 469 |  | 
| Liam Girdwood | ce6120c | 2010-11-05 15:53:46 +0200 | [diff] [blame] | 470 | if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { | 
| Axel Lin | 94f17e9 | 2011-10-07 21:36:27 +0800 | [diff] [blame] | 471 | snd_soc_cache_sync(codec); | 
|  | 472 |  | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 473 | /* Initial cap charge at VMID 5k */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 474 | snd_soc_write(codec, WM8510_POWER1, power1 | 0x3); | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 475 | mdelay(100); | 
|  | 476 | } | 
|  | 477 |  | 
|  | 478 | power1 |= 0x2;  /* VMID 500k */ | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 479 | snd_soc_write(codec, WM8510_POWER1, power1); | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 480 | break; | 
|  | 481 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 482 | case SND_SOC_BIAS_OFF: | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 483 | snd_soc_write(codec, WM8510_POWER1, 0); | 
|  | 484 | snd_soc_write(codec, WM8510_POWER2, 0); | 
|  | 485 | snd_soc_write(codec, WM8510_POWER3, 0); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 486 | break; | 
|  | 487 | } | 
| Mark Brown | 09af98b | 2008-10-07 13:04:58 +0100 | [diff] [blame] | 488 |  | 
| Liam Girdwood | ce6120c | 2010-11-05 15:53:46 +0200 | [diff] [blame] | 489 | codec->dapm.bias_level = level; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 490 | return 0; | 
|  | 491 | } | 
|  | 492 |  | 
|  | 493 | #define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ | 
|  | 494 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ | 
|  | 495 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) | 
|  | 496 |  | 
|  | 497 | #define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ | 
|  | 498 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) | 
|  | 499 |  | 
| Lars-Peter Clausen | 85e7652 | 2011-11-23 11:40:40 +0100 | [diff] [blame] | 500 | static const struct snd_soc_dai_ops wm8510_dai_ops = { | 
| Eric Miao | 6335d05 | 2009-03-03 09:41:00 +0800 | [diff] [blame] | 501 | .hw_params	= wm8510_pcm_hw_params, | 
|  | 502 | .digital_mute	= wm8510_mute, | 
|  | 503 | .set_fmt	= wm8510_set_dai_fmt, | 
|  | 504 | .set_clkdiv	= wm8510_set_dai_clkdiv, | 
|  | 505 | .set_pll	= wm8510_set_dai_pll, | 
|  | 506 | }; | 
|  | 507 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 508 | static struct snd_soc_dai_driver wm8510_dai = { | 
|  | 509 | .name = "wm8510-hifi", | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 510 | .playback = { | 
|  | 511 | .stream_name = "Playback", | 
|  | 512 | .channels_min = 2, | 
|  | 513 | .channels_max = 2, | 
|  | 514 | .rates = WM8510_RATES, | 
|  | 515 | .formats = WM8510_FORMATS,}, | 
|  | 516 | .capture = { | 
|  | 517 | .stream_name = "Capture", | 
|  | 518 | .channels_min = 2, | 
|  | 519 | .channels_max = 2, | 
|  | 520 | .rates = WM8510_RATES, | 
|  | 521 | .formats = WM8510_FORMATS,}, | 
| Eric Miao | 6335d05 | 2009-03-03 09:41:00 +0800 | [diff] [blame] | 522 | .ops = &wm8510_dai_ops, | 
| Mark Brown | cc369cf | 2009-07-09 11:28:07 +0100 | [diff] [blame] | 523 | .symmetric_rates = 1, | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 524 | }; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 525 |  | 
| Lars-Peter Clausen | 84b315e | 2011-12-02 10:18:28 +0100 | [diff] [blame] | 526 | static int wm8510_suspend(struct snd_soc_codec *codec) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 527 | { | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 528 | wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); | 
|  | 529 | return 0; | 
|  | 530 | } | 
|  | 531 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 532 | static int wm8510_resume(struct snd_soc_codec *codec) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 533 | { | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 534 | wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 535 | return 0; | 
|  | 536 | } | 
|  | 537 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 538 | static int wm8510_probe(struct snd_soc_codec *codec) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 539 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 540 | struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec); | 
|  | 541 | int ret; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 542 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 543 | ret = snd_soc_codec_set_cache_io(codec, 7, 9,  wm8510->control_type); | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 544 | if (ret < 0) { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 545 | printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n", ret); | 
|  | 546 | return ret; | 
| Mark Brown | 17a52fd | 2009-07-05 17:24:50 +0100 | [diff] [blame] | 547 | } | 
|  | 548 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 549 | wm8510_reset(codec); | 
|  | 550 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 551 | /* power on device */ | 
|  | 552 | wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | 
| Mark Brown | fe3e78e | 2009-11-03 22:13:13 +0000 | [diff] [blame] | 553 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 554 | return ret; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 555 | } | 
|  | 556 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 557 | /* power down chip */ | 
|  | 558 | static int wm8510_remove(struct snd_soc_codec *codec) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 559 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 560 | struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 561 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 562 | wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); | 
|  | 563 | kfree(wm8510); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 564 | return 0; | 
|  | 565 | } | 
|  | 566 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 567 | static struct snd_soc_codec_driver soc_codec_dev_wm8510 = { | 
|  | 568 | .probe =	wm8510_probe, | 
|  | 569 | .remove =	wm8510_remove, | 
|  | 570 | .suspend =	wm8510_suspend, | 
|  | 571 | .resume =	wm8510_resume, | 
|  | 572 | .set_bias_level = wm8510_set_bias_level, | 
|  | 573 | .reg_cache_size = ARRAY_SIZE(wm8510_reg), | 
|  | 574 | .reg_word_size = sizeof(u16), | 
|  | 575 | .reg_cache_default =wm8510_reg, | 
| Mark Brown | b6709f3 | 2011-12-03 11:41:45 +0000 | [diff] [blame] | 576 |  | 
|  | 577 | .controls = wm8510_snd_controls, | 
|  | 578 | .num_controls = ARRAY_SIZE(wm8510_snd_controls), | 
|  | 579 | .dapm_widgets = wm8510_dapm_widgets, | 
|  | 580 | .num_dapm_widgets = ARRAY_SIZE(wm8510_dapm_widgets), | 
|  | 581 | .dapm_routes = wm8510_dapm_routes, | 
|  | 582 | .num_dapm_routes = ARRAY_SIZE(wm8510_dapm_routes), | 
| Jean Delvare | 41759c2 | 2008-09-02 17:07:30 +0200 | [diff] [blame] | 583 | }; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 584 |  | 
| Mark Brown | 0a422e1 | 2011-08-02 13:03:04 +0900 | [diff] [blame] | 585 | static const struct of_device_id wm8510_of_match[] = { | 
|  | 586 | { .compatible = "wlf,wm8510" }, | 
|  | 587 | { }, | 
|  | 588 | }; | 
|  | 589 |  | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 590 | #if defined(CONFIG_SPI_MASTER) | 
|  | 591 | static int __devinit wm8510_spi_probe(struct spi_device *spi) | 
|  | 592 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 593 | struct wm8510_priv *wm8510; | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 594 | int ret; | 
|  | 595 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 596 | wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL); | 
|  | 597 | if (wm8510 == NULL) | 
|  | 598 | return -ENOMEM; | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 599 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 600 | wm8510->control_type = SND_SOC_SPI; | 
|  | 601 | spi_set_drvdata(spi, wm8510); | 
|  | 602 |  | 
|  | 603 | ret = snd_soc_register_codec(&spi->dev, | 
|  | 604 | &soc_codec_dev_wm8510, &wm8510_dai, 1); | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 605 | if (ret < 0) | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 606 | kfree(wm8510); | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 607 | return ret; | 
|  | 608 | } | 
|  | 609 |  | 
|  | 610 | static int __devexit wm8510_spi_remove(struct spi_device *spi) | 
|  | 611 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 612 | snd_soc_unregister_codec(&spi->dev); | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 613 | return 0; | 
|  | 614 | } | 
|  | 615 |  | 
|  | 616 | static struct spi_driver wm8510_spi_driver = { | 
|  | 617 | .driver = { | 
|  | 618 | .name	= "wm8510", | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 619 | .owner	= THIS_MODULE, | 
| Mark Brown | 0a422e1 | 2011-08-02 13:03:04 +0900 | [diff] [blame] | 620 | .of_match_table = wm8510_of_match, | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 621 | }, | 
|  | 622 | .probe		= wm8510_spi_probe, | 
|  | 623 | .remove		= __devexit_p(wm8510_spi_remove), | 
|  | 624 | }; | 
| Mark Brown | 5e35795 | 2008-10-07 11:56:20 +0100 | [diff] [blame] | 625 | #endif /* CONFIG_SPI_MASTER */ | 
|  | 626 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 627 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | 
|  | 628 | static __devinit int wm8510_i2c_probe(struct i2c_client *i2c, | 
|  | 629 | const struct i2c_device_id *id) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 630 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 631 | struct wm8510_priv *wm8510; | 
|  | 632 | int ret; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 633 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 634 | wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL); | 
|  | 635 | if (wm8510 == NULL) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 636 | return -ENOMEM; | 
|  | 637 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 638 | i2c_set_clientdata(i2c, wm8510); | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 639 | wm8510->control_type = SND_SOC_I2C; | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 640 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 641 | ret =  snd_soc_register_codec(&i2c->dev, | 
|  | 642 | &soc_codec_dev_wm8510, &wm8510_dai, 1); | 
|  | 643 | if (ret < 0) | 
|  | 644 | kfree(wm8510); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 645 | return ret; | 
|  | 646 | } | 
|  | 647 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 648 | static __devexit int wm8510_i2c_remove(struct i2c_client *client) | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 649 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 650 | snd_soc_unregister_codec(&client->dev); | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 651 | return 0; | 
|  | 652 | } | 
|  | 653 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 654 | static const struct i2c_device_id wm8510_i2c_id[] = { | 
|  | 655 | { "wm8510", 0 }, | 
|  | 656 | { } | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 657 | }; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 658 | MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id); | 
|  | 659 |  | 
|  | 660 | static struct i2c_driver wm8510_i2c_driver = { | 
|  | 661 | .driver = { | 
| Mark Brown | 091edcc | 2011-12-02 22:08:49 +0000 | [diff] [blame] | 662 | .name = "wm8510", | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 663 | .owner = THIS_MODULE, | 
| Mark Brown | 0a422e1 | 2011-08-02 13:03:04 +0900 | [diff] [blame] | 664 | .of_match_table = wm8510_of_match, | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 665 | }, | 
|  | 666 | .probe =    wm8510_i2c_probe, | 
|  | 667 | .remove =   __devexit_p(wm8510_i2c_remove), | 
|  | 668 | .id_table = wm8510_i2c_id, | 
|  | 669 | }; | 
|  | 670 | #endif | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 671 |  | 
| Takashi Iwai | c9b3a40 | 2008-12-10 07:47:22 +0100 | [diff] [blame] | 672 | static int __init wm8510_modinit(void) | 
| Mark Brown | 64089b8 | 2008-12-08 19:17:58 +0000 | [diff] [blame] | 673 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 674 | int ret = 0; | 
|  | 675 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | 
|  | 676 | ret = i2c_add_driver(&wm8510_i2c_driver); | 
|  | 677 | if (ret != 0) { | 
|  | 678 | printk(KERN_ERR "Failed to register WM8510 I2C driver: %d\n", | 
|  | 679 | ret); | 
|  | 680 | } | 
|  | 681 | #endif | 
|  | 682 | #if defined(CONFIG_SPI_MASTER) | 
|  | 683 | ret = spi_register_driver(&wm8510_spi_driver); | 
|  | 684 | if (ret != 0) { | 
|  | 685 | printk(KERN_ERR "Failed to register WM8510 SPI driver: %d\n", | 
|  | 686 | ret); | 
|  | 687 | } | 
|  | 688 | #endif | 
|  | 689 | return ret; | 
| Mark Brown | 64089b8 | 2008-12-08 19:17:58 +0000 | [diff] [blame] | 690 | } | 
|  | 691 | module_init(wm8510_modinit); | 
|  | 692 |  | 
|  | 693 | static void __exit wm8510_exit(void) | 
|  | 694 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 695 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | 
|  | 696 | i2c_del_driver(&wm8510_i2c_driver); | 
|  | 697 | #endif | 
|  | 698 | #if defined(CONFIG_SPI_MASTER) | 
|  | 699 | spi_unregister_driver(&wm8510_spi_driver); | 
|  | 700 | #endif | 
| Mark Brown | 64089b8 | 2008-12-08 19:17:58 +0000 | [diff] [blame] | 701 | } | 
|  | 702 | module_exit(wm8510_exit); | 
|  | 703 |  | 
| Mark Brown | 5d42151 | 2008-06-05 13:49:32 +0100 | [diff] [blame] | 704 | MODULE_DESCRIPTION("ASoC WM8510 driver"); | 
|  | 705 | MODULE_AUTHOR("Liam Girdwood"); | 
|  | 706 | MODULE_LICENSE("GPL"); |