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