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