1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * wm8804.c -- WM8804 S/PDIF transceiver driver |
4 | * |
5 | * Copyright 2010-11 Wolfson Microelectronics plc |
6 | * |
7 | * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/moduleparam.h> |
12 | #include <linux/init.h> |
13 | #include <linux/gpio/consumer.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/pm.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/regulator/consumer.h> |
18 | #include <linux/slab.h> |
19 | #include <sound/core.h> |
20 | #include <sound/pcm.h> |
21 | #include <sound/pcm_params.h> |
22 | #include <sound/soc.h> |
23 | #include <sound/initval.h> |
24 | #include <sound/tlv.h> |
25 | #include <sound/soc-dapm.h> |
26 | |
27 | #include "wm8804.h" |
28 | |
29 | #define WM8804_NUM_SUPPLIES 2 |
30 | static const char *wm8804_supply_names[WM8804_NUM_SUPPLIES] = { |
31 | "PVDD" , |
32 | "DVDD" |
33 | }; |
34 | |
35 | static const struct reg_default wm8804_reg_defaults[] = { |
36 | { 3, 0x21 }, /* R3 - PLL1 */ |
37 | { 4, 0xFD }, /* R4 - PLL2 */ |
38 | { 5, 0x36 }, /* R5 - PLL3 */ |
39 | { 6, 0x07 }, /* R6 - PLL4 */ |
40 | { 7, 0x16 }, /* R7 - PLL5 */ |
41 | { 8, 0x18 }, /* R8 - PLL6 */ |
42 | { 9, 0xFF }, /* R9 - SPDMODE */ |
43 | { 10, 0x00 }, /* R10 - INTMASK */ |
44 | { 18, 0x00 }, /* R18 - SPDTX1 */ |
45 | { 19, 0x00 }, /* R19 - SPDTX2 */ |
46 | { 20, 0x00 }, /* R20 - SPDTX3 */ |
47 | { 21, 0x71 }, /* R21 - SPDTX4 */ |
48 | { 22, 0x0B }, /* R22 - SPDTX5 */ |
49 | { 23, 0x70 }, /* R23 - GPO0 */ |
50 | { 24, 0x57 }, /* R24 - GPO1 */ |
51 | { 26, 0x42 }, /* R26 - GPO2 */ |
52 | { 27, 0x06 }, /* R27 - AIFTX */ |
53 | { 28, 0x06 }, /* R28 - AIFRX */ |
54 | { 29, 0x80 }, /* R29 - SPDRX1 */ |
55 | { 30, 0x07 }, /* R30 - PWRDN */ |
56 | }; |
57 | |
58 | struct wm8804_priv { |
59 | struct device *dev; |
60 | struct regmap *regmap; |
61 | struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES]; |
62 | struct notifier_block disable_nb[WM8804_NUM_SUPPLIES]; |
63 | int mclk_div; |
64 | |
65 | struct gpio_desc *reset; |
66 | |
67 | int aif_pwr; |
68 | }; |
69 | |
70 | static int txsrc_put(struct snd_kcontrol *kcontrol, |
71 | struct snd_ctl_elem_value *ucontrol); |
72 | |
73 | static int wm8804_aif_event(struct snd_soc_dapm_widget *w, |
74 | struct snd_kcontrol *kcontrol, int event); |
75 | |
76 | /* |
77 | * We can't use the same notifier block for more than one supply and |
78 | * there's no way I can see to get from a callback to the caller |
79 | * except container_of(). |
80 | */ |
81 | #define WM8804_REGULATOR_EVENT(n) \ |
82 | static int wm8804_regulator_event_##n(struct notifier_block *nb, \ |
83 | unsigned long event, void *data) \ |
84 | { \ |
85 | struct wm8804_priv *wm8804 = container_of(nb, struct wm8804_priv, \ |
86 | disable_nb[n]); \ |
87 | if (event & REGULATOR_EVENT_DISABLE) { \ |
88 | regcache_mark_dirty(wm8804->regmap); \ |
89 | } \ |
90 | return 0; \ |
91 | } |
92 | |
93 | WM8804_REGULATOR_EVENT(0) |
94 | WM8804_REGULATOR_EVENT(1) |
95 | |
96 | static const char *txsrc_text[] = { "S/PDIF RX" , "AIF" }; |
97 | static SOC_ENUM_SINGLE_DECL(txsrc, WM8804_SPDTX4, 6, txsrc_text); |
98 | |
99 | static const struct snd_kcontrol_new wm8804_tx_source_mux[] = { |
100 | SOC_DAPM_ENUM_EXT("Input Source" , txsrc, |
101 | snd_soc_dapm_get_enum_double, txsrc_put), |
102 | }; |
103 | |
104 | static const struct snd_soc_dapm_widget wm8804_dapm_widgets[] = { |
105 | SND_SOC_DAPM_OUTPUT("SPDIF Out" ), |
106 | SND_SOC_DAPM_INPUT("SPDIF In" ), |
107 | |
108 | SND_SOC_DAPM_PGA("SPDIFTX" , WM8804_PWRDN, 2, 1, NULL, 0), |
109 | SND_SOC_DAPM_PGA("SPDIFRX" , WM8804_PWRDN, 1, 1, NULL, 0), |
110 | |
111 | SND_SOC_DAPM_MUX("Tx Source" , SND_SOC_NOPM, 6, 0, wm8804_tx_source_mux), |
112 | |
113 | SND_SOC_DAPM_AIF_OUT_E("AIFTX" , NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, |
114 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), |
115 | SND_SOC_DAPM_AIF_IN_E("AIFRX" , NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, |
116 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), |
117 | }; |
118 | |
119 | static const struct snd_soc_dapm_route wm8804_dapm_routes[] = { |
120 | { "AIFRX" , NULL, "Playback" }, |
121 | { "Tx Source" , "AIF" , "AIFRX" }, |
122 | |
123 | { "SPDIFRX" , NULL, "SPDIF In" }, |
124 | { "Tx Source" , "S/PDIF RX" , "SPDIFRX" }, |
125 | |
126 | { "SPDIFTX" , NULL, "Tx Source" }, |
127 | { "SPDIF Out" , NULL, "SPDIFTX" }, |
128 | |
129 | { "AIFTX" , NULL, "SPDIFRX" }, |
130 | { "Capture" , NULL, "AIFTX" }, |
131 | }; |
132 | |
133 | static int wm8804_aif_event(struct snd_soc_dapm_widget *w, |
134 | struct snd_kcontrol *kcontrol, int event) |
135 | { |
136 | struct snd_soc_component *component = snd_soc_dapm_to_component(dapm: w->dapm); |
137 | struct wm8804_priv *wm8804 = snd_soc_component_get_drvdata(c: component); |
138 | |
139 | switch (event) { |
140 | case SND_SOC_DAPM_POST_PMU: |
141 | /* power up the aif */ |
142 | if (!wm8804->aif_pwr) |
143 | snd_soc_component_update_bits(component, WM8804_PWRDN, mask: 0x10, val: 0x0); |
144 | wm8804->aif_pwr++; |
145 | break; |
146 | case SND_SOC_DAPM_POST_PMD: |
147 | /* power down only both paths are disabled */ |
148 | wm8804->aif_pwr--; |
149 | if (!wm8804->aif_pwr) |
150 | snd_soc_component_update_bits(component, WM8804_PWRDN, mask: 0x10, val: 0x10); |
151 | break; |
152 | } |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | static int txsrc_put(struct snd_kcontrol *kcontrol, |
158 | struct snd_ctl_elem_value *ucontrol) |
159 | { |
160 | struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); |
161 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); |
162 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
163 | unsigned int val = ucontrol->value.enumerated.item[0] << e->shift_l; |
164 | unsigned int mask = 1 << e->shift_l; |
165 | unsigned int txpwr; |
166 | |
167 | if (val != 0 && val != mask) |
168 | return -EINVAL; |
169 | |
170 | snd_soc_dapm_mutex_lock(dapm); |
171 | |
172 | if (snd_soc_component_test_bits(component, reg: e->reg, mask, value: val)) { |
173 | /* save the current power state of the transmitter */ |
174 | txpwr = snd_soc_component_read(component, WM8804_PWRDN) & 0x4; |
175 | |
176 | /* power down the transmitter */ |
177 | snd_soc_component_update_bits(component, WM8804_PWRDN, mask: 0x4, val: 0x4); |
178 | |
179 | /* set the tx source */ |
180 | snd_soc_component_update_bits(component, reg: e->reg, mask, val); |
181 | |
182 | /* restore the transmitter's configuration */ |
183 | snd_soc_component_update_bits(component, WM8804_PWRDN, mask: 0x4, val: txpwr); |
184 | } |
185 | |
186 | snd_soc_dapm_mutex_unlock(dapm); |
187 | |
188 | return 0; |
189 | } |
190 | |
191 | static bool wm8804_volatile(struct device *dev, unsigned int reg) |
192 | { |
193 | switch (reg) { |
194 | case WM8804_RST_DEVID1: |
195 | case WM8804_DEVID2: |
196 | case WM8804_DEVREV: |
197 | case WM8804_INTSTAT: |
198 | case WM8804_SPDSTAT: |
199 | case WM8804_RXCHAN1: |
200 | case WM8804_RXCHAN2: |
201 | case WM8804_RXCHAN3: |
202 | case WM8804_RXCHAN4: |
203 | case WM8804_RXCHAN5: |
204 | return true; |
205 | default: |
206 | return false; |
207 | } |
208 | } |
209 | |
210 | static int wm8804_soft_reset(struct wm8804_priv *wm8804) |
211 | { |
212 | return regmap_write(map: wm8804->regmap, WM8804_RST_DEVID1, val: 0x0); |
213 | } |
214 | |
215 | static int wm8804_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
216 | { |
217 | struct snd_soc_component *component; |
218 | u16 format, master, bcp, lrp; |
219 | |
220 | component = dai->component; |
221 | |
222 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
223 | case SND_SOC_DAIFMT_I2S: |
224 | format = 0x2; |
225 | break; |
226 | case SND_SOC_DAIFMT_RIGHT_J: |
227 | format = 0x0; |
228 | break; |
229 | case SND_SOC_DAIFMT_LEFT_J: |
230 | format = 0x1; |
231 | break; |
232 | case SND_SOC_DAIFMT_DSP_A: |
233 | case SND_SOC_DAIFMT_DSP_B: |
234 | format = 0x3; |
235 | break; |
236 | default: |
237 | dev_err(dai->dev, "Unknown dai format\n" ); |
238 | return -EINVAL; |
239 | } |
240 | |
241 | /* set data format */ |
242 | snd_soc_component_update_bits(component, WM8804_AIFTX, mask: 0x3, val: format); |
243 | snd_soc_component_update_bits(component, WM8804_AIFRX, mask: 0x3, val: format); |
244 | |
245 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
246 | case SND_SOC_DAIFMT_CBM_CFM: |
247 | master = 1; |
248 | break; |
249 | case SND_SOC_DAIFMT_CBS_CFS: |
250 | master = 0; |
251 | break; |
252 | default: |
253 | dev_err(dai->dev, "Unknown master/slave configuration\n" ); |
254 | return -EINVAL; |
255 | } |
256 | |
257 | /* set master/slave mode */ |
258 | snd_soc_component_update_bits(component, WM8804_AIFRX, mask: 0x40, val: master << 6); |
259 | |
260 | bcp = lrp = 0; |
261 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
262 | case SND_SOC_DAIFMT_NB_NF: |
263 | break; |
264 | case SND_SOC_DAIFMT_IB_IF: |
265 | bcp = lrp = 1; |
266 | break; |
267 | case SND_SOC_DAIFMT_IB_NF: |
268 | bcp = 1; |
269 | break; |
270 | case SND_SOC_DAIFMT_NB_IF: |
271 | lrp = 1; |
272 | break; |
273 | default: |
274 | dev_err(dai->dev, "Unknown polarity configuration\n" ); |
275 | return -EINVAL; |
276 | } |
277 | |
278 | /* set frame inversion */ |
279 | snd_soc_component_update_bits(component, WM8804_AIFTX, mask: 0x10 | 0x20, |
280 | val: (bcp << 4) | (lrp << 5)); |
281 | snd_soc_component_update_bits(component, WM8804_AIFRX, mask: 0x10 | 0x20, |
282 | val: (bcp << 4) | (lrp << 5)); |
283 | return 0; |
284 | } |
285 | |
286 | static int wm8804_hw_params(struct snd_pcm_substream *substream, |
287 | struct snd_pcm_hw_params *params, |
288 | struct snd_soc_dai *dai) |
289 | { |
290 | struct snd_soc_component *component; |
291 | u16 blen; |
292 | |
293 | component = dai->component; |
294 | |
295 | switch (params_width(p: params)) { |
296 | case 16: |
297 | blen = 0x0; |
298 | break; |
299 | case 20: |
300 | blen = 0x1; |
301 | break; |
302 | case 24: |
303 | blen = 0x2; |
304 | break; |
305 | default: |
306 | dev_err(dai->dev, "Unsupported word length: %u\n" , |
307 | params_width(params)); |
308 | return -EINVAL; |
309 | } |
310 | |
311 | /* set word length */ |
312 | snd_soc_component_update_bits(component, WM8804_AIFTX, mask: 0xc, val: blen << 2); |
313 | snd_soc_component_update_bits(component, WM8804_AIFRX, mask: 0xc, val: blen << 2); |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | struct pll_div { |
319 | u32 prescale:1; |
320 | u32 mclkdiv:1; |
321 | u32 freqmode:2; |
322 | u32 n:4; |
323 | u32 k:22; |
324 | }; |
325 | |
326 | /* PLL rate to output rate divisions */ |
327 | static struct { |
328 | unsigned int div; |
329 | unsigned int freqmode; |
330 | unsigned int mclkdiv; |
331 | } post_table[] = { |
332 | { 2, 0, 0 }, |
333 | { 4, 0, 1 }, |
334 | { 4, 1, 0 }, |
335 | { 8, 1, 1 }, |
336 | { 8, 2, 0 }, |
337 | { 16, 2, 1 }, |
338 | { 12, 3, 0 }, |
339 | { 24, 3, 1 } |
340 | }; |
341 | |
342 | #define FIXED_PLL_SIZE ((1ULL << 22) * 10) |
343 | static int pll_factors(struct pll_div *pll_div, unsigned int target, |
344 | unsigned int source, unsigned int mclk_div) |
345 | { |
346 | u64 Kpart; |
347 | unsigned long int K, Ndiv, Nmod, tmp; |
348 | int i; |
349 | |
350 | /* |
351 | * Scale the output frequency up; the PLL should run in the |
352 | * region of 90-100MHz. |
353 | */ |
354 | for (i = 0; i < ARRAY_SIZE(post_table); i++) { |
355 | tmp = target * post_table[i].div; |
356 | if ((tmp >= 90000000 && tmp <= 100000000) && |
357 | (mclk_div == post_table[i].mclkdiv)) { |
358 | pll_div->freqmode = post_table[i].freqmode; |
359 | pll_div->mclkdiv = post_table[i].mclkdiv; |
360 | target *= post_table[i].div; |
361 | break; |
362 | } |
363 | } |
364 | |
365 | if (i == ARRAY_SIZE(post_table)) { |
366 | pr_err("%s: Unable to scale output frequency: %uHz\n" , |
367 | __func__, target); |
368 | return -EINVAL; |
369 | } |
370 | |
371 | pll_div->prescale = 0; |
372 | Ndiv = target / source; |
373 | if (Ndiv < 5) { |
374 | source >>= 1; |
375 | pll_div->prescale = 1; |
376 | Ndiv = target / source; |
377 | } |
378 | |
379 | if (Ndiv < 5 || Ndiv > 13) { |
380 | pr_err("%s: WM8804 N value is not within the recommended range: %lu\n" , |
381 | __func__, Ndiv); |
382 | return -EINVAL; |
383 | } |
384 | pll_div->n = Ndiv; |
385 | |
386 | Nmod = target % source; |
387 | Kpart = FIXED_PLL_SIZE * (u64)Nmod; |
388 | |
389 | do_div(Kpart, source); |
390 | |
391 | K = Kpart & 0xffffffff; |
392 | if ((K % 10) >= 5) |
393 | K += 5; |
394 | K /= 10; |
395 | pll_div->k = K; |
396 | |
397 | return 0; |
398 | } |
399 | |
400 | static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id, |
401 | int source, unsigned int freq_in, |
402 | unsigned int freq_out) |
403 | { |
404 | struct snd_soc_component *component = dai->component; |
405 | struct wm8804_priv *wm8804 = snd_soc_component_get_drvdata(c: component); |
406 | bool change; |
407 | |
408 | if (!freq_in || !freq_out) { |
409 | /* disable the PLL */ |
410 | regmap_update_bits_check(map: wm8804->regmap, WM8804_PWRDN, |
411 | mask: 0x1, val: 0x1, change: &change); |
412 | if (change) |
413 | pm_runtime_put(dev: wm8804->dev); |
414 | } else { |
415 | int ret; |
416 | struct pll_div pll_div; |
417 | |
418 | ret = pll_factors(pll_div: &pll_div, target: freq_out, source: freq_in, |
419 | mclk_div: wm8804->mclk_div); |
420 | if (ret) |
421 | return ret; |
422 | |
423 | /* power down the PLL before reprogramming it */ |
424 | regmap_update_bits_check(map: wm8804->regmap, WM8804_PWRDN, |
425 | mask: 0x1, val: 0x1, change: &change); |
426 | if (!change) |
427 | pm_runtime_get_sync(dev: wm8804->dev); |
428 | |
429 | /* set PLLN and PRESCALE */ |
430 | snd_soc_component_update_bits(component, WM8804_PLL4, mask: 0xf | 0x10, |
431 | val: pll_div.n | (pll_div.prescale << 4)); |
432 | /* set mclkdiv and freqmode */ |
433 | snd_soc_component_update_bits(component, WM8804_PLL5, mask: 0x3 | 0x8, |
434 | val: pll_div.freqmode | (pll_div.mclkdiv << 3)); |
435 | /* set PLLK */ |
436 | snd_soc_component_write(component, WM8804_PLL1, val: pll_div.k & 0xff); |
437 | snd_soc_component_write(component, WM8804_PLL2, val: (pll_div.k >> 8) & 0xff); |
438 | snd_soc_component_write(component, WM8804_PLL3, val: pll_div.k >> 16); |
439 | |
440 | /* power up the PLL */ |
441 | snd_soc_component_update_bits(component, WM8804_PWRDN, mask: 0x1, val: 0); |
442 | } |
443 | |
444 | return 0; |
445 | } |
446 | |
447 | static int wm8804_set_sysclk(struct snd_soc_dai *dai, |
448 | int clk_id, unsigned int freq, int dir) |
449 | { |
450 | struct snd_soc_component *component; |
451 | |
452 | component = dai->component; |
453 | |
454 | switch (clk_id) { |
455 | case WM8804_TX_CLKSRC_MCLK: |
456 | if ((freq >= 10000000 && freq <= 14400000) |
457 | || (freq >= 16280000 && freq <= 27000000)) |
458 | snd_soc_component_update_bits(component, WM8804_PLL6, mask: 0x80, val: 0x80); |
459 | else { |
460 | dev_err(dai->dev, "OSCCLOCK is not within the " |
461 | "recommended range: %uHz\n" , freq); |
462 | return -EINVAL; |
463 | } |
464 | break; |
465 | case WM8804_TX_CLKSRC_PLL: |
466 | snd_soc_component_update_bits(component, WM8804_PLL6, mask: 0x80, val: 0); |
467 | break; |
468 | case WM8804_CLKOUT_SRC_CLK1: |
469 | snd_soc_component_update_bits(component, WM8804_PLL6, mask: 0x8, val: 0); |
470 | break; |
471 | case WM8804_CLKOUT_SRC_OSCCLK: |
472 | snd_soc_component_update_bits(component, WM8804_PLL6, mask: 0x8, val: 0x8); |
473 | break; |
474 | default: |
475 | dev_err(dai->dev, "Unknown clock source: %d\n" , clk_id); |
476 | return -EINVAL; |
477 | } |
478 | |
479 | return 0; |
480 | } |
481 | |
482 | static int wm8804_set_clkdiv(struct snd_soc_dai *dai, |
483 | int div_id, int div) |
484 | { |
485 | struct snd_soc_component *component; |
486 | struct wm8804_priv *wm8804; |
487 | |
488 | component = dai->component; |
489 | switch (div_id) { |
490 | case WM8804_CLKOUT_DIV: |
491 | snd_soc_component_update_bits(component, WM8804_PLL5, mask: 0x30, |
492 | val: (div & 0x3) << 4); |
493 | break; |
494 | case WM8804_MCLK_DIV: |
495 | wm8804 = snd_soc_component_get_drvdata(c: component); |
496 | wm8804->mclk_div = div; |
497 | break; |
498 | default: |
499 | dev_err(dai->dev, "Unknown clock divider: %d\n" , div_id); |
500 | return -EINVAL; |
501 | } |
502 | return 0; |
503 | } |
504 | |
505 | static const struct snd_soc_dai_ops wm8804_dai_ops = { |
506 | .hw_params = wm8804_hw_params, |
507 | .set_fmt = wm8804_set_fmt, |
508 | .set_sysclk = wm8804_set_sysclk, |
509 | .set_clkdiv = wm8804_set_clkdiv, |
510 | .set_pll = wm8804_set_pll |
511 | }; |
512 | |
513 | #define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ |
514 | SNDRV_PCM_FMTBIT_S24_LE) |
515 | |
516 | #define WM8804_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ |
517 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ |
518 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ |
519 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) |
520 | |
521 | static struct snd_soc_dai_driver wm8804_dai = { |
522 | .name = "wm8804-spdif" , |
523 | .playback = { |
524 | .stream_name = "Playback" , |
525 | .channels_min = 2, |
526 | .channels_max = 2, |
527 | .rates = WM8804_RATES, |
528 | .formats = WM8804_FORMATS, |
529 | }, |
530 | .capture = { |
531 | .stream_name = "Capture" , |
532 | .channels_min = 2, |
533 | .channels_max = 2, |
534 | .rates = WM8804_RATES, |
535 | .formats = WM8804_FORMATS, |
536 | }, |
537 | .ops = &wm8804_dai_ops, |
538 | .symmetric_rate = 1 |
539 | }; |
540 | |
541 | static const struct snd_soc_component_driver soc_component_dev_wm8804 = { |
542 | .dapm_widgets = wm8804_dapm_widgets, |
543 | .num_dapm_widgets = ARRAY_SIZE(wm8804_dapm_widgets), |
544 | .dapm_routes = wm8804_dapm_routes, |
545 | .num_dapm_routes = ARRAY_SIZE(wm8804_dapm_routes), |
546 | .use_pmdown_time = 1, |
547 | .endianness = 1, |
548 | }; |
549 | |
550 | const struct regmap_config wm8804_regmap_config = { |
551 | .reg_bits = 8, |
552 | .val_bits = 8, |
553 | |
554 | .max_register = WM8804_MAX_REGISTER, |
555 | .volatile_reg = wm8804_volatile, |
556 | |
557 | .cache_type = REGCACHE_MAPLE, |
558 | .reg_defaults = wm8804_reg_defaults, |
559 | .num_reg_defaults = ARRAY_SIZE(wm8804_reg_defaults), |
560 | }; |
561 | EXPORT_SYMBOL_GPL(wm8804_regmap_config); |
562 | |
563 | int wm8804_probe(struct device *dev, struct regmap *regmap) |
564 | { |
565 | struct wm8804_priv *wm8804; |
566 | unsigned int id1, id2; |
567 | int i, ret; |
568 | |
569 | wm8804 = devm_kzalloc(dev, size: sizeof(*wm8804), GFP_KERNEL); |
570 | if (!wm8804) |
571 | return -ENOMEM; |
572 | |
573 | dev_set_drvdata(dev, data: wm8804); |
574 | |
575 | wm8804->dev = dev; |
576 | wm8804->regmap = regmap; |
577 | |
578 | wm8804->reset = devm_gpiod_get_optional(dev, con_id: "wlf,reset" , |
579 | flags: GPIOD_OUT_LOW); |
580 | if (IS_ERR(ptr: wm8804->reset)) { |
581 | ret = PTR_ERR(ptr: wm8804->reset); |
582 | dev_err(dev, "Failed to get reset line: %d\n" , ret); |
583 | return ret; |
584 | } |
585 | |
586 | for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) |
587 | wm8804->supplies[i].supply = wm8804_supply_names[i]; |
588 | |
589 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8804->supplies), |
590 | consumers: wm8804->supplies); |
591 | if (ret) { |
592 | dev_err(dev, "Failed to request supplies: %d\n" , ret); |
593 | return ret; |
594 | } |
595 | |
596 | wm8804->disable_nb[0].notifier_call = wm8804_regulator_event_0; |
597 | wm8804->disable_nb[1].notifier_call = wm8804_regulator_event_1; |
598 | |
599 | /* This should really be moved into the regulator core */ |
600 | for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) { |
601 | struct regulator *regulator = wm8804->supplies[i].consumer; |
602 | |
603 | ret = devm_regulator_register_notifier(regulator, |
604 | nb: &wm8804->disable_nb[i]); |
605 | if (ret != 0) { |
606 | dev_err(dev, |
607 | "Failed to register regulator notifier: %d\n" , |
608 | ret); |
609 | return ret; |
610 | } |
611 | } |
612 | |
613 | ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), |
614 | consumers: wm8804->supplies); |
615 | if (ret) { |
616 | dev_err(dev, "Failed to enable supplies: %d\n" , ret); |
617 | return ret; |
618 | } |
619 | |
620 | gpiod_set_value_cansleep(desc: wm8804->reset, value: 1); |
621 | |
622 | ret = regmap_read(map: regmap, WM8804_RST_DEVID1, val: &id1); |
623 | if (ret < 0) { |
624 | dev_err(dev, "Failed to read device ID: %d\n" , ret); |
625 | goto err_reg_enable; |
626 | } |
627 | |
628 | ret = regmap_read(map: regmap, WM8804_DEVID2, val: &id2); |
629 | if (ret < 0) { |
630 | dev_err(dev, "Failed to read device ID: %d\n" , ret); |
631 | goto err_reg_enable; |
632 | } |
633 | |
634 | id2 = (id2 << 8) | id1; |
635 | |
636 | if (id2 != 0x8805) { |
637 | dev_err(dev, "Invalid device ID: %#x\n" , id2); |
638 | ret = -EINVAL; |
639 | goto err_reg_enable; |
640 | } |
641 | |
642 | ret = regmap_read(map: regmap, WM8804_DEVREV, val: &id1); |
643 | if (ret < 0) { |
644 | dev_err(dev, "Failed to read device revision: %d\n" , |
645 | ret); |
646 | goto err_reg_enable; |
647 | } |
648 | dev_info(dev, "revision %c\n" , id1 + 'A'); |
649 | |
650 | if (!wm8804->reset) { |
651 | ret = wm8804_soft_reset(wm8804); |
652 | if (ret < 0) { |
653 | dev_err(dev, "Failed to issue reset: %d\n" , ret); |
654 | goto err_reg_enable; |
655 | } |
656 | } |
657 | |
658 | ret = devm_snd_soc_register_component(dev, component_driver: &soc_component_dev_wm8804, |
659 | dai_drv: &wm8804_dai, num_dai: 1); |
660 | if (ret < 0) { |
661 | dev_err(dev, "Failed to register CODEC: %d\n" , ret); |
662 | goto err_reg_enable; |
663 | } |
664 | |
665 | pm_runtime_set_active(dev); |
666 | pm_runtime_enable(dev); |
667 | pm_runtime_idle(dev); |
668 | |
669 | return 0; |
670 | |
671 | err_reg_enable: |
672 | regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), consumers: wm8804->supplies); |
673 | return ret; |
674 | } |
675 | EXPORT_SYMBOL_GPL(wm8804_probe); |
676 | |
677 | void wm8804_remove(struct device *dev) |
678 | { |
679 | pm_runtime_disable(dev); |
680 | } |
681 | EXPORT_SYMBOL_GPL(wm8804_remove); |
682 | |
683 | #if IS_ENABLED(CONFIG_PM) |
684 | static int wm8804_runtime_resume(struct device *dev) |
685 | { |
686 | struct wm8804_priv *wm8804 = dev_get_drvdata(dev); |
687 | int ret; |
688 | |
689 | ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), |
690 | consumers: wm8804->supplies); |
691 | if (ret) { |
692 | dev_err(wm8804->dev, "Failed to enable supplies: %d\n" , ret); |
693 | return ret; |
694 | } |
695 | |
696 | regcache_sync(map: wm8804->regmap); |
697 | |
698 | /* Power up OSCCLK */ |
699 | regmap_update_bits(map: wm8804->regmap, WM8804_PWRDN, mask: 0x8, val: 0x0); |
700 | |
701 | return 0; |
702 | } |
703 | |
704 | static int wm8804_runtime_suspend(struct device *dev) |
705 | { |
706 | struct wm8804_priv *wm8804 = dev_get_drvdata(dev); |
707 | |
708 | /* Power down OSCCLK */ |
709 | regmap_update_bits(map: wm8804->regmap, WM8804_PWRDN, mask: 0x8, val: 0x8); |
710 | |
711 | regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), |
712 | consumers: wm8804->supplies); |
713 | |
714 | return 0; |
715 | } |
716 | #endif |
717 | |
718 | const struct dev_pm_ops wm8804_pm = { |
719 | SET_RUNTIME_PM_OPS(wm8804_runtime_suspend, wm8804_runtime_resume, NULL) |
720 | }; |
721 | EXPORT_SYMBOL_GPL(wm8804_pm); |
722 | |
723 | MODULE_DESCRIPTION("ASoC WM8804 driver" ); |
724 | MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>" ); |
725 | MODULE_LICENSE("GPL" ); |
726 | |