Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: WM8770: Initial driver

The WM8770 is a high performance, multi-channel audio
codec. The WM8770 is ideal for surround sound processing
applications for home hi-fi, automotive and other audio
visual equipment.

Signed-off-by: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>

authored by

Dimitris Papastamos and committed by
Mark Brown
c046fd4d 3a45b867

+807
+4
sound/soc/codecs/Kconfig
··· 58 58 select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI 59 59 select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI 60 60 select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI 61 + select SND_SOC_WM8770 if SPI_MASTER 61 62 select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI 62 63 select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI 63 64 select SND_SOC_WM8900 if I2C ··· 243 242 tristate 244 243 245 244 config SND_SOC_WM8753 245 + tristate 246 + 247 + config SND_SOC_WM8770 246 248 tristate 247 249 248 250 config SND_SOC_WM8776
+2
sound/soc/codecs/Makefile
··· 42 42 snd-soc-wm8741-objs := wm8741.o 43 43 snd-soc-wm8750-objs := wm8750.o 44 44 snd-soc-wm8753-objs := wm8753.o 45 + snd-soc-wm8770-objs := wm8770.o 45 46 snd-soc-wm8776-objs := wm8776.o 46 47 snd-soc-wm8804-objs := wm8804.o 47 48 snd-soc-wm8900-objs := wm8900.o ··· 119 118 obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o 120 119 obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o 121 120 obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o 121 + obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o 122 122 obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o 123 123 obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o 124 124 obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
+750
sound/soc/codecs/wm8770.c
··· 1 + /* 2 + * wm8770.c -- WM8770 ALSA SoC Audio driver 3 + * 4 + * Copyright 2010 Wolfson Microelectronics plc 5 + * 6 + * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> 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/init.h> 16 + #include <linux/delay.h> 17 + #include <linux/pm.h> 18 + #include <linux/platform_device.h> 19 + #include <linux/spi/spi.h> 20 + #include <linux/regulator/consumer.h> 21 + #include <linux/slab.h> 22 + #include <sound/core.h> 23 + #include <sound/pcm.h> 24 + #include <sound/pcm_params.h> 25 + #include <sound/soc.h> 26 + #include <sound/soc-dapm.h> 27 + #include <sound/initval.h> 28 + #include <sound/tlv.h> 29 + 30 + #include "wm8770.h" 31 + 32 + #define WM8770_NUM_SUPPLIES 3 33 + static const char *wm8770_supply_names[WM8770_NUM_SUPPLIES] = { 34 + "AVDD1", 35 + "AVDD2", 36 + "DVDD" 37 + }; 38 + 39 + static const u16 wm8770_reg_defs[WM8770_CACHEREGNUM] = { 40 + 0x7f, 0x7f, 0x7f, 0x7f, 41 + 0x7f, 0x7f, 0x7f, 0x7f, 42 + 0x7f, 0xff, 0xff, 0xff, 43 + 0xff, 0xff, 0xff, 0xff, 44 + 0xff, 0xff, 0, 0x90, 0, 45 + 0, 0x22, 0x22, 0x3e, 46 + 0xc, 0xc, 0x100, 0x189, 47 + 0x189, 0x8770 48 + }; 49 + 50 + struct wm8770_priv { 51 + enum snd_soc_control_type control_type; 52 + struct regulator_bulk_data supplies[WM8770_NUM_SUPPLIES]; 53 + struct notifier_block disable_nb[WM8770_NUM_SUPPLIES]; 54 + struct snd_soc_codec *codec; 55 + int sysclk; 56 + }; 57 + 58 + static int vout12supply_event(struct snd_soc_dapm_widget *w, 59 + struct snd_kcontrol *kcontrol, int event); 60 + static int vout34supply_event(struct snd_soc_dapm_widget *w, 61 + struct snd_kcontrol *kcontrol, int event); 62 + 63 + /* 64 + * We can't use the same notifier block for more than one supply and 65 + * there's no way I can see to get from a callback to the caller 66 + * except container_of(). 67 + */ 68 + #define WM8770_REGULATOR_EVENT(n) \ 69 + static int wm8770_regulator_event_##n(struct notifier_block *nb, \ 70 + unsigned long event, void *data) \ 71 + { \ 72 + struct wm8770_priv *wm8770 = container_of(nb, struct wm8770_priv, \ 73 + disable_nb[n]); \ 74 + if (event & REGULATOR_EVENT_DISABLE) { \ 75 + wm8770->codec->cache_sync = 1; \ 76 + } \ 77 + return 0; \ 78 + } 79 + 80 + WM8770_REGULATOR_EVENT(0) 81 + WM8770_REGULATOR_EVENT(1) 82 + WM8770_REGULATOR_EVENT(2) 83 + 84 + static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); 85 + static const DECLARE_TLV_DB_SCALE(dac_dig_tlv, -12750, 50, 1); 86 + static const DECLARE_TLV_DB_SCALE(dac_alg_tlv, -12700, 100, 1); 87 + 88 + static const char *dac_phase_text[][2] = { 89 + { "DAC1 Normal", "DAC1 Inverted" }, 90 + { "DAC2 Normal", "DAC2 Inverted" }, 91 + { "DAC3 Normal", "DAC3 Inverted" }, 92 + { "DAC4 Normal", "DAC4 Inverted" }, 93 + }; 94 + 95 + static const struct soc_enum dac_phase[] = { 96 + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 0, 1, 2, dac_phase_text[0]), 97 + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 2, 3, 2, dac_phase_text[1]), 98 + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 4, 5, 2, dac_phase_text[2]), 99 + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 6, 7, 2, dac_phase_text[3]), 100 + }; 101 + 102 + static const struct snd_kcontrol_new wm8770_snd_controls[] = { 103 + /* global DAC playback controls */ 104 + SOC_SINGLE_TLV("DAC Playback Volume", WM8770_MSDIGVOL, 0, 255, 0, 105 + dac_dig_tlv), 106 + SOC_SINGLE("DAC Playback Switch", WM8770_DACMUTE, 4, 1, 1), 107 + SOC_SINGLE("DAC Playback ZC Switch", WM8770_DACCTRL1, 0, 1, 0), 108 + 109 + /* global VOUT playback controls */ 110 + SOC_SINGLE_TLV("VOUT Playback Volume", WM8770_MSALGVOL, 0, 127, 0, 111 + dac_alg_tlv), 112 + SOC_SINGLE("VOUT Playback ZC Switch", WM8770_MSALGVOL, 7, 1, 0), 113 + 114 + /* VOUT1/2/3/4 specific controls */ 115 + SOC_DOUBLE_R_TLV("VOUT1 Playback Volume", WM8770_VOUT1LVOL, 116 + WM8770_VOUT1RVOL, 0, 127, 0, dac_alg_tlv), 117 + SOC_DOUBLE_R("VOUT1 Playback ZC Switch", WM8770_VOUT1LVOL, 118 + WM8770_VOUT1RVOL, 7, 1, 0), 119 + SOC_DOUBLE_R_TLV("VOUT2 Playback Volume", WM8770_VOUT2LVOL, 120 + WM8770_VOUT2RVOL, 0, 127, 0, dac_alg_tlv), 121 + SOC_DOUBLE_R("VOUT2 Playback ZC Switch", WM8770_VOUT2LVOL, 122 + WM8770_VOUT2RVOL, 7, 1, 0), 123 + SOC_DOUBLE_R_TLV("VOUT3 Playback Volume", WM8770_VOUT3LVOL, 124 + WM8770_VOUT3RVOL, 0, 127, 0, dac_alg_tlv), 125 + SOC_DOUBLE_R("VOUT3 Playback ZC Switch", WM8770_VOUT3LVOL, 126 + WM8770_VOUT3RVOL, 7, 1, 0), 127 + SOC_DOUBLE_R_TLV("VOUT4 Playback Volume", WM8770_VOUT4LVOL, 128 + WM8770_VOUT4RVOL, 0, 127, 0, dac_alg_tlv), 129 + SOC_DOUBLE_R("VOUT4 Playback ZC Switch", WM8770_VOUT4LVOL, 130 + WM8770_VOUT4RVOL, 7, 1, 0), 131 + 132 + /* DAC1/2/3/4 specific controls */ 133 + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", WM8770_DAC1LVOL, 134 + WM8770_DAC1RVOL, 0, 255, 0, dac_dig_tlv), 135 + SOC_SINGLE("DAC1 Deemphasis Switch", WM8770_DACCTRL2, 0, 1, 0), 136 + SOC_ENUM("DAC1 Phase", dac_phase[0]), 137 + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", WM8770_DAC2LVOL, 138 + WM8770_DAC2RVOL, 0, 255, 0, dac_dig_tlv), 139 + SOC_SINGLE("DAC2 Deemphasis Switch", WM8770_DACCTRL2, 1, 1, 0), 140 + SOC_ENUM("DAC2 Phase", dac_phase[1]), 141 + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", WM8770_DAC3LVOL, 142 + WM8770_DAC3RVOL, 0, 255, 0, dac_dig_tlv), 143 + SOC_SINGLE("DAC3 Deemphasis Switch", WM8770_DACCTRL2, 2, 1, 0), 144 + SOC_ENUM("DAC3 Phase", dac_phase[2]), 145 + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", WM8770_DAC4LVOL, 146 + WM8770_DAC4RVOL, 0, 255, 0, dac_dig_tlv), 147 + SOC_SINGLE("DAC4 Deemphasis Switch", WM8770_DACCTRL2, 3, 1, 0), 148 + SOC_ENUM("DAC4 Phase", dac_phase[3]), 149 + 150 + /* ADC specific controls */ 151 + SOC_DOUBLE_R_TLV("Capture Volume", WM8770_ADCLCTRL, WM8770_ADCRCTRL, 152 + 0, 31, 0, adc_tlv), 153 + SOC_DOUBLE_R("Capture Switch", WM8770_ADCLCTRL, WM8770_ADCRCTRL, 154 + 5, 1, 1), 155 + 156 + /* other controls */ 157 + SOC_SINGLE("ADC 128x Oversampling Switch", WM8770_MSTRCTRL, 3, 1, 0), 158 + SOC_SINGLE("ADC Highpass Filter Switch", WM8770_IFACECTRL, 8, 1, 1) 159 + }; 160 + 161 + static const char *ain_text[] = { 162 + "AIN1", "AIN2", "AIN3", "AIN4", 163 + "AIN5", "AIN6", "AIN7", "AIN8" 164 + }; 165 + 166 + static const struct soc_enum ain_enum = 167 + SOC_ENUM_DOUBLE(WM8770_ADCMUX, 0, 4, 8, ain_text); 168 + 169 + static const struct snd_kcontrol_new ain_mux = 170 + SOC_DAPM_ENUM("Capture Mux", ain_enum); 171 + 172 + static const struct snd_kcontrol_new vout1_mix_controls[] = { 173 + SOC_DAPM_SINGLE("DAC1 Switch", WM8770_OUTMUX1, 0, 1, 0), 174 + SOC_DAPM_SINGLE("AUX1 Switch", WM8770_OUTMUX1, 1, 1, 0), 175 + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 2, 1, 0) 176 + }; 177 + 178 + static const struct snd_kcontrol_new vout2_mix_controls[] = { 179 + SOC_DAPM_SINGLE("DAC2 Switch", WM8770_OUTMUX1, 3, 1, 0), 180 + SOC_DAPM_SINGLE("AUX2 Switch", WM8770_OUTMUX1, 4, 1, 0), 181 + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 5, 1, 0) 182 + }; 183 + 184 + static const struct snd_kcontrol_new vout3_mix_controls[] = { 185 + SOC_DAPM_SINGLE("DAC3 Switch", WM8770_OUTMUX2, 0, 1, 0), 186 + SOC_DAPM_SINGLE("AUX3 Switch", WM8770_OUTMUX2, 1, 1, 0), 187 + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 2, 1, 0) 188 + }; 189 + 190 + static const struct snd_kcontrol_new vout4_mix_controls[] = { 191 + SOC_DAPM_SINGLE("DAC4 Switch", WM8770_OUTMUX2, 3, 1, 0), 192 + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 4, 1, 0) 193 + }; 194 + 195 + static const struct snd_soc_dapm_widget wm8770_dapm_widgets[] = { 196 + SND_SOC_DAPM_INPUT("AUX1"), 197 + SND_SOC_DAPM_INPUT("AUX2"), 198 + SND_SOC_DAPM_INPUT("AUX3"), 199 + 200 + SND_SOC_DAPM_INPUT("AIN1"), 201 + SND_SOC_DAPM_INPUT("AIN2"), 202 + SND_SOC_DAPM_INPUT("AIN3"), 203 + SND_SOC_DAPM_INPUT("AIN4"), 204 + SND_SOC_DAPM_INPUT("AIN5"), 205 + SND_SOC_DAPM_INPUT("AIN6"), 206 + SND_SOC_DAPM_INPUT("AIN7"), 207 + SND_SOC_DAPM_INPUT("AIN8"), 208 + 209 + SND_SOC_DAPM_MUX("Capture Mux", WM8770_ADCMUX, 8, 1, &ain_mux), 210 + 211 + SND_SOC_DAPM_ADC("ADC", "Capture", WM8770_PWDNCTRL, 1, 1), 212 + 213 + SND_SOC_DAPM_DAC("DAC1", "Playback", WM8770_PWDNCTRL, 2, 1), 214 + SND_SOC_DAPM_DAC("DAC2", "Playback", WM8770_PWDNCTRL, 3, 1), 215 + SND_SOC_DAPM_DAC("DAC3", "Playback", WM8770_PWDNCTRL, 4, 1), 216 + SND_SOC_DAPM_DAC("DAC4", "Playback", WM8770_PWDNCTRL, 5, 1), 217 + 218 + SND_SOC_DAPM_SUPPLY("VOUT12 Supply", SND_SOC_NOPM, 0, 0, 219 + vout12supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 220 + SND_SOC_DAPM_SUPPLY("VOUT34 Supply", SND_SOC_NOPM, 0, 0, 221 + vout34supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 222 + 223 + SND_SOC_DAPM_MIXER("VOUT1 Mixer", SND_SOC_NOPM, 0, 0, 224 + vout1_mix_controls, ARRAY_SIZE(vout1_mix_controls)), 225 + SND_SOC_DAPM_MIXER("VOUT2 Mixer", SND_SOC_NOPM, 0, 0, 226 + vout2_mix_controls, ARRAY_SIZE(vout2_mix_controls)), 227 + SND_SOC_DAPM_MIXER("VOUT3 Mixer", SND_SOC_NOPM, 0, 0, 228 + vout3_mix_controls, ARRAY_SIZE(vout3_mix_controls)), 229 + SND_SOC_DAPM_MIXER("VOUT4 Mixer", SND_SOC_NOPM, 0, 0, 230 + vout4_mix_controls, ARRAY_SIZE(vout4_mix_controls)), 231 + 232 + SND_SOC_DAPM_OUTPUT("VOUT1"), 233 + SND_SOC_DAPM_OUTPUT("VOUT2"), 234 + SND_SOC_DAPM_OUTPUT("VOUT3"), 235 + SND_SOC_DAPM_OUTPUT("VOUT4") 236 + }; 237 + 238 + static const struct snd_soc_dapm_route wm8770_intercon[] = { 239 + { "Capture Mux", "AIN1", "AIN1" }, 240 + { "Capture Mux", "AIN2", "AIN2" }, 241 + { "Capture Mux", "AIN3", "AIN3" }, 242 + { "Capture Mux", "AIN4", "AIN4" }, 243 + { "Capture Mux", "AIN5", "AIN5" }, 244 + { "Capture Mux", "AIN6", "AIN6" }, 245 + { "Capture Mux", "AIN7", "AIN7" }, 246 + { "Capture Mux", "AIN8", "AIN8" }, 247 + 248 + { "ADC", NULL, "Capture Mux" }, 249 + 250 + { "VOUT1 Mixer", NULL, "VOUT12 Supply" }, 251 + { "VOUT1 Mixer", "DAC1 Switch", "DAC1" }, 252 + { "VOUT1 Mixer", "AUX1 Switch", "AUX1" }, 253 + { "VOUT1 Mixer", "Bypass Switch", "Capture Mux" }, 254 + 255 + { "VOUT2 Mixer", NULL, "VOUT12 Supply" }, 256 + { "VOUT2 Mixer", "DAC2 Switch", "DAC2" }, 257 + { "VOUT2 Mixer", "AUX2 Switch", "AUX2" }, 258 + { "VOUT2 Mixer", "Bypass Switch", "Capture Mux" }, 259 + 260 + { "VOUT3 Mixer", NULL, "VOUT34 Supply" }, 261 + { "VOUT3 Mixer", "DAC3 Switch", "DAC3" }, 262 + { "VOUT3 Mixer", "AUX3 Switch", "AUX3" }, 263 + { "VOUT3 Mixer", "Bypass Switch", "Capture Mux" }, 264 + 265 + { "VOUT4 Mixer", NULL, "VOUT34 Supply" }, 266 + { "VOUT4 Mixer", "DAC4 Switch", "DAC4" }, 267 + { "VOUT4 Mixer", "Bypass Switch", "Capture Mux" }, 268 + 269 + { "VOUT1", NULL, "VOUT1 Mixer" }, 270 + { "VOUT2", NULL, "VOUT2 Mixer" }, 271 + { "VOUT3", NULL, "VOUT3 Mixer" }, 272 + { "VOUT4", NULL, "VOUT4 Mixer" } 273 + }; 274 + 275 + static int vout12supply_event(struct snd_soc_dapm_widget *w, 276 + struct snd_kcontrol *kcontrol, int event) 277 + { 278 + struct snd_soc_codec *codec; 279 + 280 + codec = w->codec; 281 + 282 + switch (event) { 283 + case SND_SOC_DAPM_PRE_PMU: 284 + snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0); 285 + break; 286 + case SND_SOC_DAPM_POST_PMD: 287 + snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0x180); 288 + break; 289 + } 290 + 291 + return 0; 292 + } 293 + 294 + static int vout34supply_event(struct snd_soc_dapm_widget *w, 295 + struct snd_kcontrol *kcontrol, int event) 296 + { 297 + struct snd_soc_codec *codec; 298 + 299 + codec = w->codec; 300 + 301 + switch (event) { 302 + case SND_SOC_DAPM_PRE_PMU: 303 + snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0); 304 + break; 305 + case SND_SOC_DAPM_POST_PMD: 306 + snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0x180); 307 + break; 308 + } 309 + 310 + return 0; 311 + } 312 + 313 + static int wm8770_reset(struct snd_soc_codec *codec) 314 + { 315 + return snd_soc_write(codec, WM8770_RESET, 0); 316 + } 317 + 318 + static int wm8770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 319 + { 320 + struct snd_soc_codec *codec; 321 + int iface, master; 322 + 323 + codec = dai->codec; 324 + 325 + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 326 + case SND_SOC_DAIFMT_CBM_CFM: 327 + master = 0x100; 328 + break; 329 + case SND_SOC_DAIFMT_CBS_CFS: 330 + master = 0; 331 + break; 332 + default: 333 + return -EINVAL; 334 + } 335 + 336 + iface = 0; 337 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 338 + case SND_SOC_DAIFMT_I2S: 339 + iface |= 0x2; 340 + break; 341 + case SND_SOC_DAIFMT_RIGHT_J: 342 + break; 343 + case SND_SOC_DAIFMT_LEFT_J: 344 + iface |= 0x1; 345 + break; 346 + default: 347 + return -EINVAL; 348 + } 349 + 350 + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 351 + case SND_SOC_DAIFMT_NB_NF: 352 + break; 353 + case SND_SOC_DAIFMT_IB_IF: 354 + iface |= 0xc; 355 + break; 356 + case SND_SOC_DAIFMT_IB_NF: 357 + iface |= 0x8; 358 + break; 359 + case SND_SOC_DAIFMT_NB_IF: 360 + iface |= 0x4; 361 + break; 362 + default: 363 + return -EINVAL; 364 + } 365 + 366 + snd_soc_update_bits(codec, WM8770_IFACECTRL, 0xf, iface); 367 + snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x100, master); 368 + 369 + return 0; 370 + } 371 + 372 + static const int mclk_ratios[] = { 373 + 128, 374 + 192, 375 + 256, 376 + 384, 377 + 512, 378 + 768 379 + }; 380 + 381 + static int wm8770_hw_params(struct snd_pcm_substream *substream, 382 + struct snd_pcm_hw_params *params, 383 + struct snd_soc_dai *dai) 384 + { 385 + struct snd_soc_codec *codec; 386 + struct wm8770_priv *wm8770; 387 + int i; 388 + int iface; 389 + int shift; 390 + int ratio; 391 + 392 + codec = dai->codec; 393 + wm8770 = snd_soc_codec_get_drvdata(codec); 394 + 395 + iface = 0; 396 + switch (params_format(params)) { 397 + case SNDRV_PCM_FORMAT_S16_LE: 398 + break; 399 + case SNDRV_PCM_FORMAT_S20_3LE: 400 + iface |= 0x10; 401 + break; 402 + case SNDRV_PCM_FORMAT_S24_LE: 403 + iface |= 0x20; 404 + break; 405 + case SNDRV_PCM_FORMAT_S32_LE: 406 + iface |= 0x30; 407 + break; 408 + } 409 + 410 + switch (substream->stream) { 411 + case SNDRV_PCM_STREAM_PLAYBACK: 412 + i = 0; 413 + shift = 4; 414 + break; 415 + case SNDRV_PCM_STREAM_CAPTURE: 416 + i = 2; 417 + shift = 0; 418 + break; 419 + default: 420 + return -EINVAL; 421 + } 422 + 423 + /* Only need to set MCLK/LRCLK ratio if we're master */ 424 + if (snd_soc_read(codec, WM8770_MSTRCTRL) & 0x100) { 425 + for (; i < ARRAY_SIZE(mclk_ratios); ++i) { 426 + ratio = wm8770->sysclk / params_rate(params); 427 + if (ratio == mclk_ratios[i]) 428 + break; 429 + } 430 + 431 + if (i == ARRAY_SIZE(mclk_ratios)) { 432 + dev_err(codec->dev, 433 + "Unable to configure MCLK ratio %d/%d\n", 434 + wm8770->sysclk, params_rate(params)); 435 + return -EINVAL; 436 + } 437 + 438 + dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]); 439 + 440 + snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x7 << shift, 441 + i << shift); 442 + } 443 + 444 + snd_soc_update_bits(codec, WM8770_IFACECTRL, 0x30, iface); 445 + 446 + return 0; 447 + } 448 + 449 + static int wm8770_mute(struct snd_soc_dai *dai, int mute) 450 + { 451 + struct snd_soc_codec *codec; 452 + 453 + codec = dai->codec; 454 + return snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10, 455 + !!mute << 4); 456 + } 457 + 458 + static int wm8770_set_sysclk(struct snd_soc_dai *dai, 459 + int clk_id, unsigned int freq, int dir) 460 + { 461 + struct snd_soc_codec *codec; 462 + struct wm8770_priv *wm8770; 463 + 464 + codec = dai->codec; 465 + wm8770 = snd_soc_codec_get_drvdata(codec); 466 + wm8770->sysclk = freq; 467 + return 0; 468 + } 469 + 470 + static void wm8770_sync_cache(struct snd_soc_codec *codec) 471 + { 472 + int i; 473 + u16 *cache; 474 + 475 + if (!codec->cache_sync) 476 + return; 477 + 478 + codec->cache_only = 0; 479 + cache = codec->reg_cache; 480 + for (i = 0; i < codec->driver->reg_cache_size; i++) { 481 + if (i == WM8770_RESET || cache[i] == wm8770_reg_defs[i]) 482 + continue; 483 + snd_soc_write(codec, i, cache[i]); 484 + } 485 + codec->cache_sync = 0; 486 + } 487 + 488 + static int wm8770_set_bias_level(struct snd_soc_codec *codec, 489 + enum snd_soc_bias_level level) 490 + { 491 + int ret; 492 + struct wm8770_priv *wm8770; 493 + 494 + wm8770 = snd_soc_codec_get_drvdata(codec); 495 + 496 + switch (level) { 497 + case SND_SOC_BIAS_ON: 498 + break; 499 + case SND_SOC_BIAS_PREPARE: 500 + break; 501 + case SND_SOC_BIAS_STANDBY: 502 + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { 503 + ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), 504 + wm8770->supplies); 505 + if (ret) { 506 + dev_err(codec->dev, 507 + "Failed to enable supplies: %d\n", 508 + ret); 509 + return ret; 510 + } 511 + wm8770_sync_cache(codec); 512 + /* global powerup */ 513 + snd_soc_write(codec, WM8770_PWDNCTRL, 0); 514 + } 515 + break; 516 + case SND_SOC_BIAS_OFF: 517 + /* global powerdown */ 518 + snd_soc_write(codec, WM8770_PWDNCTRL, 1); 519 + regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), 520 + wm8770->supplies); 521 + break; 522 + } 523 + 524 + codec->dapm.bias_level = level; 525 + return 0; 526 + } 527 + 528 + #define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 529 + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) 530 + 531 + static struct snd_soc_dai_ops wm8770_dai_ops = { 532 + .digital_mute = wm8770_mute, 533 + .hw_params = wm8770_hw_params, 534 + .set_fmt = wm8770_set_fmt, 535 + .set_sysclk = wm8770_set_sysclk, 536 + }; 537 + 538 + static struct snd_soc_dai_driver wm8770_dai = { 539 + .name = "wm8770-hifi", 540 + .playback = { 541 + .stream_name = "Playback", 542 + .channels_min = 2, 543 + .channels_max = 2, 544 + .rates = SNDRV_PCM_RATE_8000_192000, 545 + .formats = WM8770_FORMATS 546 + }, 547 + .capture = { 548 + .stream_name = "Capture", 549 + .channels_min = 2, 550 + .channels_max = 2, 551 + .rates = SNDRV_PCM_RATE_8000_96000, 552 + .formats = WM8770_FORMATS 553 + }, 554 + .ops = &wm8770_dai_ops, 555 + .symmetric_rates = 1 556 + }; 557 + 558 + #ifdef CONFIG_PM 559 + static int wm8770_suspend(struct snd_soc_codec *codec, pm_message_t state) 560 + { 561 + wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF); 562 + return 0; 563 + } 564 + 565 + static int wm8770_resume(struct snd_soc_codec *codec) 566 + { 567 + wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 568 + return 0; 569 + } 570 + #else 571 + #define wm8770_suspend NULL 572 + #define wm8770_resume NULL 573 + #endif 574 + 575 + static int wm8770_probe(struct snd_soc_codec *codec) 576 + { 577 + struct wm8770_priv *wm8770; 578 + int ret; 579 + int i; 580 + 581 + wm8770 = snd_soc_codec_get_drvdata(codec); 582 + wm8770->codec = codec; 583 + 584 + codec->dapm.idle_bias_off = 1; 585 + 586 + ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8770->control_type); 587 + if (ret < 0) { 588 + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 589 + return ret; 590 + } 591 + 592 + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) 593 + wm8770->supplies[i].supply = wm8770_supply_names[i]; 594 + 595 + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8770->supplies), 596 + wm8770->supplies); 597 + if (ret) { 598 + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); 599 + return ret; 600 + } 601 + 602 + wm8770->disable_nb[0].notifier_call = wm8770_regulator_event_0; 603 + wm8770->disable_nb[1].notifier_call = wm8770_regulator_event_1; 604 + wm8770->disable_nb[2].notifier_call = wm8770_regulator_event_2; 605 + 606 + /* This should really be moved into the regulator core */ 607 + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) { 608 + ret = regulator_register_notifier(wm8770->supplies[i].consumer, 609 + &wm8770->disable_nb[i]); 610 + if (ret) { 611 + dev_err(codec->dev, 612 + "Failed to register regulator notifier: %d\n", 613 + ret); 614 + } 615 + } 616 + 617 + ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), 618 + wm8770->supplies); 619 + if (ret) { 620 + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); 621 + goto err_reg_get; 622 + } 623 + 624 + ret = wm8770_reset(codec); 625 + if (ret < 0) { 626 + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); 627 + goto err_reg_enable; 628 + } 629 + 630 + wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 631 + 632 + /* latch the volume update bits */ 633 + snd_soc_update_bits(codec, WM8770_MSDIGVOL, 0x100, 0x100); 634 + snd_soc_update_bits(codec, WM8770_MSALGVOL, 0x100, 0x100); 635 + snd_soc_update_bits(codec, WM8770_VOUT1RVOL, 0x100, 0x100); 636 + snd_soc_update_bits(codec, WM8770_VOUT2RVOL, 0x100, 0x100); 637 + snd_soc_update_bits(codec, WM8770_VOUT3RVOL, 0x100, 0x100); 638 + snd_soc_update_bits(codec, WM8770_VOUT4RVOL, 0x100, 0x100); 639 + snd_soc_update_bits(codec, WM8770_DAC1RVOL, 0x100, 0x100); 640 + snd_soc_update_bits(codec, WM8770_DAC2RVOL, 0x100, 0x100); 641 + snd_soc_update_bits(codec, WM8770_DAC3RVOL, 0x100, 0x100); 642 + snd_soc_update_bits(codec, WM8770_DAC4RVOL, 0x100, 0x100); 643 + 644 + /* mute all DACs */ 645 + snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10, 0x10); 646 + 647 + snd_soc_add_controls(codec, wm8770_snd_controls, 648 + ARRAY_SIZE(wm8770_snd_controls)); 649 + snd_soc_dapm_new_controls(&codec->dapm, wm8770_dapm_widgets, 650 + ARRAY_SIZE(wm8770_dapm_widgets)); 651 + snd_soc_dapm_add_routes(&codec->dapm, wm8770_intercon, 652 + ARRAY_SIZE(wm8770_intercon)); 653 + return 0; 654 + 655 + err_reg_enable: 656 + regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); 657 + err_reg_get: 658 + regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); 659 + return ret; 660 + } 661 + 662 + static int wm8770_remove(struct snd_soc_codec *codec) 663 + { 664 + struct wm8770_priv *wm8770; 665 + int i; 666 + 667 + wm8770 = snd_soc_codec_get_drvdata(codec); 668 + wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF); 669 + 670 + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); ++i) 671 + regulator_unregister_notifier(wm8770->supplies[i].consumer, 672 + &wm8770->disable_nb[i]); 673 + regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); 674 + return 0; 675 + } 676 + 677 + static struct snd_soc_codec_driver soc_codec_dev_wm8770 = { 678 + .probe = wm8770_probe, 679 + .remove = wm8770_remove, 680 + .suspend = wm8770_suspend, 681 + .resume = wm8770_resume, 682 + .set_bias_level = wm8770_set_bias_level, 683 + .reg_cache_size = ARRAY_SIZE(wm8770_reg_defs), 684 + .reg_word_size = sizeof (u16), 685 + .reg_cache_default = wm8770_reg_defs 686 + }; 687 + 688 + #if defined(CONFIG_SPI_MASTER) 689 + static int __devinit wm8770_spi_probe(struct spi_device *spi) 690 + { 691 + struct wm8770_priv *wm8770; 692 + int ret; 693 + 694 + wm8770 = kzalloc(sizeof(struct wm8770_priv), GFP_KERNEL); 695 + if (!wm8770) 696 + return -ENOMEM; 697 + 698 + wm8770->control_type = SND_SOC_SPI; 699 + spi_set_drvdata(spi, wm8770); 700 + 701 + ret = snd_soc_register_codec(&spi->dev, 702 + &soc_codec_dev_wm8770, &wm8770_dai, 1); 703 + if (ret < 0) 704 + kfree(wm8770); 705 + return ret; 706 + } 707 + 708 + static int __devexit wm8770_spi_remove(struct spi_device *spi) 709 + { 710 + snd_soc_unregister_codec(&spi->dev); 711 + kfree(spi_get_drvdata(spi)); 712 + return 0; 713 + } 714 + 715 + static struct spi_driver wm8770_spi_driver = { 716 + .driver = { 717 + .name = "wm8770", 718 + .owner = THIS_MODULE, 719 + }, 720 + .probe = wm8770_spi_probe, 721 + .remove = __devexit_p(wm8770_spi_remove) 722 + }; 723 + #endif 724 + 725 + static int __init wm8770_modinit(void) 726 + { 727 + int ret = 0; 728 + 729 + #if defined(CONFIG_SPI_MASTER) 730 + ret = spi_register_driver(&wm8770_spi_driver); 731 + if (ret) { 732 + printk(KERN_ERR "Failed to register wm8770 SPI driver: %d\n", 733 + ret); 734 + } 735 + #endif 736 + return ret; 737 + } 738 + module_init(wm8770_modinit); 739 + 740 + static void __exit wm8770_exit(void) 741 + { 742 + #if defined(CONFIG_SPI_MASTER) 743 + spi_unregister_driver(&wm8770_spi_driver); 744 + #endif 745 + } 746 + module_exit(wm8770_exit); 747 + 748 + MODULE_DESCRIPTION("ASoC WM8770 driver"); 749 + MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>"); 750 + MODULE_LICENSE("GPL");
+51
sound/soc/codecs/wm8770.h
··· 1 + /* 2 + * wm8770.h -- WM8770 ASoC driver 3 + * 4 + * Copyright 2010 Wolfson Microelectronics plc 5 + * 6 + * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> 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 + #ifndef _WM8770_H 14 + #define _WM8770_H 15 + 16 + /* Registers */ 17 + #define WM8770_VOUT1LVOL 0 18 + #define WM8770_VOUT1RVOL 0x1 19 + #define WM8770_VOUT2LVOL 0x2 20 + #define WM8770_VOUT2RVOL 0x3 21 + #define WM8770_VOUT3LVOL 0x4 22 + #define WM8770_VOUT3RVOL 0x5 23 + #define WM8770_VOUT4LVOL 0x6 24 + #define WM8770_VOUT4RVOL 0x7 25 + #define WM8770_MSALGVOL 0x8 26 + #define WM8770_DAC1LVOL 0x9 27 + #define WM8770_DAC1RVOL 0xa 28 + #define WM8770_DAC2LVOL 0xb 29 + #define WM8770_DAC2RVOL 0xc 30 + #define WM8770_DAC3LVOL 0xd 31 + #define WM8770_DAC3RVOL 0xe 32 + #define WM8770_DAC4LVOL 0xf 33 + #define WM8770_DAC4RVOL 0x10 34 + #define WM8770_MSDIGVOL 0x11 35 + #define WM8770_DACPHASE 0x12 36 + #define WM8770_DACCTRL1 0x13 37 + #define WM8770_DACMUTE 0x14 38 + #define WM8770_DACCTRL2 0x15 39 + #define WM8770_IFACECTRL 0x16 40 + #define WM8770_MSTRCTRL 0x17 41 + #define WM8770_PWDNCTRL 0x18 42 + #define WM8770_ADCLCTRL 0x19 43 + #define WM8770_ADCRCTRL 0x1a 44 + #define WM8770_ADCMUX 0x1b 45 + #define WM8770_OUTMUX1 0x1c 46 + #define WM8770_OUTMUX2 0x1d 47 + #define WM8770_RESET 0x31 48 + 49 + #define WM8770_CACHEREGNUM 0x20 50 + 51 + #endif