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