1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * cx20442.c -- CX20442 ALSA Soc Audio driver
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Initially based on sound/soc/codecs/wm8400.c
8*4882a593Smuzhiyun * Copyright 2008, 2009 Wolfson Microelectronics PLC.
9*4882a593Smuzhiyun * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
10*4882a593Smuzhiyun */
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun #include <linux/tty.h>
13*4882a593Smuzhiyun #include <linux/slab.h>
14*4882a593Smuzhiyun #include <linux/module.h>
15*4882a593Smuzhiyun #include <linux/regulator/consumer.h>
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun #include <sound/core.h>
18*4882a593Smuzhiyun #include <sound/initval.h>
19*4882a593Smuzhiyun #include <sound/soc.h>
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun #include "cx20442.h"
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun struct cx20442_priv {
25*4882a593Smuzhiyun struct tty_struct *tty;
26*4882a593Smuzhiyun struct regulator *por;
27*4882a593Smuzhiyun u8 reg_cache;
28*4882a593Smuzhiyun };
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun #define CX20442_PM 0x0
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun #define CX20442_TELIN 0
33*4882a593Smuzhiyun #define CX20442_TELOUT 1
34*4882a593Smuzhiyun #define CX20442_MIC 2
35*4882a593Smuzhiyun #define CX20442_SPKOUT 3
36*4882a593Smuzhiyun #define CX20442_AGC 4
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
39*4882a593Smuzhiyun SND_SOC_DAPM_OUTPUT("TELOUT"),
40*4882a593Smuzhiyun SND_SOC_DAPM_OUTPUT("SPKOUT"),
41*4882a593Smuzhiyun SND_SOC_DAPM_OUTPUT("AGCOUT"),
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
46*4882a593Smuzhiyun SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
47*4882a593Smuzhiyun SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
50*4882a593Smuzhiyun SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
55*4882a593Smuzhiyun SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
56*4882a593Smuzhiyun
57*4882a593Smuzhiyun SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun SND_SOC_DAPM_INPUT("TELIN"),
60*4882a593Smuzhiyun SND_SOC_DAPM_INPUT("MIC"),
61*4882a593Smuzhiyun SND_SOC_DAPM_INPUT("AGCIN"),
62*4882a593Smuzhiyun };
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun static const struct snd_soc_dapm_route cx20442_audio_map[] = {
65*4882a593Smuzhiyun {"TELOUT", NULL, "TELOUT Amp"},
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun {"SPKOUT", NULL, "SPKOUT Mixer"},
68*4882a593Smuzhiyun {"SPKOUT Mixer", NULL, "SPKOUT Amp"},
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun {"TELOUT Amp", NULL, "DAC"},
71*4882a593Smuzhiyun {"SPKOUT Amp", NULL, "DAC"},
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun {"SPKOUT Mixer", NULL, "SPKOUT AGC"},
74*4882a593Smuzhiyun {"SPKOUT AGC", NULL, "AGCIN"},
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun {"AGCOUT", NULL, "MIC AGC"},
77*4882a593Smuzhiyun {"MIC AGC", NULL, "MIC"},
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun {"MIC Bias", NULL, "MIC"},
80*4882a593Smuzhiyun {"Input Mixer", NULL, "MIC Bias"},
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun {"TELIN Bias", NULL, "TELIN"},
83*4882a593Smuzhiyun {"Input Mixer", NULL, "TELIN Bias"},
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun {"ADC", NULL, "Input Mixer"},
86*4882a593Smuzhiyun };
87*4882a593Smuzhiyun
cx20442_read_reg_cache(struct snd_soc_component * component,unsigned int reg)88*4882a593Smuzhiyun static unsigned int cx20442_read_reg_cache(struct snd_soc_component *component,
89*4882a593Smuzhiyun unsigned int reg)
90*4882a593Smuzhiyun {
91*4882a593Smuzhiyun struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun if (reg >= 1)
94*4882a593Smuzhiyun return -EINVAL;
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun return cx20442->reg_cache;
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun enum v253_vls {
100*4882a593Smuzhiyun V253_VLS_NONE = 0,
101*4882a593Smuzhiyun V253_VLS_T,
102*4882a593Smuzhiyun V253_VLS_L,
103*4882a593Smuzhiyun V253_VLS_LT,
104*4882a593Smuzhiyun V253_VLS_S,
105*4882a593Smuzhiyun V253_VLS_ST,
106*4882a593Smuzhiyun V253_VLS_M,
107*4882a593Smuzhiyun V253_VLS_MST,
108*4882a593Smuzhiyun V253_VLS_S1,
109*4882a593Smuzhiyun V253_VLS_S1T,
110*4882a593Smuzhiyun V253_VLS_MS1T,
111*4882a593Smuzhiyun V253_VLS_M1,
112*4882a593Smuzhiyun V253_VLS_M1ST,
113*4882a593Smuzhiyun V253_VLS_M1S1T,
114*4882a593Smuzhiyun V253_VLS_H,
115*4882a593Smuzhiyun V253_VLS_HT,
116*4882a593Smuzhiyun V253_VLS_MS,
117*4882a593Smuzhiyun V253_VLS_MS1,
118*4882a593Smuzhiyun V253_VLS_M1S,
119*4882a593Smuzhiyun V253_VLS_M1S1,
120*4882a593Smuzhiyun V253_VLS_TEST,
121*4882a593Smuzhiyun };
122*4882a593Smuzhiyun
cx20442_pm_to_v253_vls(u8 value)123*4882a593Smuzhiyun static int cx20442_pm_to_v253_vls(u8 value)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun switch (value & ~(1 << CX20442_AGC)) {
126*4882a593Smuzhiyun case 0:
127*4882a593Smuzhiyun return V253_VLS_T;
128*4882a593Smuzhiyun case (1 << CX20442_SPKOUT):
129*4882a593Smuzhiyun case (1 << CX20442_MIC):
130*4882a593Smuzhiyun case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
131*4882a593Smuzhiyun return V253_VLS_M1S1;
132*4882a593Smuzhiyun case (1 << CX20442_TELOUT):
133*4882a593Smuzhiyun case (1 << CX20442_TELIN):
134*4882a593Smuzhiyun case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
135*4882a593Smuzhiyun return V253_VLS_L;
136*4882a593Smuzhiyun case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
137*4882a593Smuzhiyun return V253_VLS_NONE;
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun return -EINVAL;
140*4882a593Smuzhiyun }
cx20442_pm_to_v253_vsp(u8 value)141*4882a593Smuzhiyun static int cx20442_pm_to_v253_vsp(u8 value)
142*4882a593Smuzhiyun {
143*4882a593Smuzhiyun switch (value & ~(1 << CX20442_AGC)) {
144*4882a593Smuzhiyun case (1 << CX20442_SPKOUT):
145*4882a593Smuzhiyun case (1 << CX20442_MIC):
146*4882a593Smuzhiyun case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
147*4882a593Smuzhiyun return (bool)(value & (1 << CX20442_AGC));
148*4882a593Smuzhiyun }
149*4882a593Smuzhiyun return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
150*4882a593Smuzhiyun }
151*4882a593Smuzhiyun
cx20442_write(struct snd_soc_component * component,unsigned int reg,unsigned int value)152*4882a593Smuzhiyun static int cx20442_write(struct snd_soc_component *component, unsigned int reg,
153*4882a593Smuzhiyun unsigned int value)
154*4882a593Smuzhiyun {
155*4882a593Smuzhiyun struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
156*4882a593Smuzhiyun int vls, vsp, old, len;
157*4882a593Smuzhiyun char buf[18];
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun if (reg >= 1)
160*4882a593Smuzhiyun return -EINVAL;
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun /* tty and write pointers required for talking to the modem
163*4882a593Smuzhiyun * are expected to be set by the line discipline initialization code */
164*4882a593Smuzhiyun if (!cx20442->tty || !cx20442->tty->ops->write)
165*4882a593Smuzhiyun return -EIO;
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun old = cx20442->reg_cache;
168*4882a593Smuzhiyun cx20442->reg_cache = value;
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun vls = cx20442_pm_to_v253_vls(value);
171*4882a593Smuzhiyun if (vls < 0)
172*4882a593Smuzhiyun return vls;
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun vsp = cx20442_pm_to_v253_vsp(value);
175*4882a593Smuzhiyun if (vsp < 0)
176*4882a593Smuzhiyun return vsp;
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun if ((vls == V253_VLS_T) ||
179*4882a593Smuzhiyun (vls == cx20442_pm_to_v253_vls(old))) {
180*4882a593Smuzhiyun if (vsp == cx20442_pm_to_v253_vsp(old))
181*4882a593Smuzhiyun return 0;
182*4882a593Smuzhiyun len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
183*4882a593Smuzhiyun } else if (vsp == cx20442_pm_to_v253_vsp(old))
184*4882a593Smuzhiyun len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
185*4882a593Smuzhiyun else
186*4882a593Smuzhiyun len = snprintf(buf, ARRAY_SIZE(buf),
187*4882a593Smuzhiyun "at+vls=%d;+vsp=%d\r", vls, vsp);
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
190*4882a593Smuzhiyun return -ENOMEM;
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun dev_dbg(component->dev, "%s: %s\n", __func__, buf);
193*4882a593Smuzhiyun if (cx20442->tty->ops->write(cx20442->tty, buf, len) != len)
194*4882a593Smuzhiyun return -EIO;
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun return 0;
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun /*
200*4882a593Smuzhiyun * Line discpline related code
201*4882a593Smuzhiyun *
202*4882a593Smuzhiyun * Any of the callback functions below can be used in two ways:
203*4882a593Smuzhiyun * 1) registerd by a machine driver as one of line discipline operations,
204*4882a593Smuzhiyun * 2) called from a machine's provided line discipline callback function
205*4882a593Smuzhiyun * in case when extra machine specific code must be run as well.
206*4882a593Smuzhiyun */
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun /* Modem init: echo off, digital speaker off, quiet off, voice mode */
209*4882a593Smuzhiyun static const char *v253_init = "ate0m0q0+fclass=8\r";
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun /* Line discipline .open() */
v253_open(struct tty_struct * tty)212*4882a593Smuzhiyun static int v253_open(struct tty_struct *tty)
213*4882a593Smuzhiyun {
214*4882a593Smuzhiyun int ret, len = strlen(v253_init);
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun /* Doesn't make sense without write callback */
217*4882a593Smuzhiyun if (!tty->ops->write)
218*4882a593Smuzhiyun return -EINVAL;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun /* Won't work if no codec pointer has been passed by a card driver */
221*4882a593Smuzhiyun if (!tty->disc_data)
222*4882a593Smuzhiyun return -ENODEV;
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun tty->receive_room = 16;
225*4882a593Smuzhiyun if (tty->ops->write(tty, v253_init, len) != len) {
226*4882a593Smuzhiyun ret = -EIO;
227*4882a593Smuzhiyun goto err;
228*4882a593Smuzhiyun }
229*4882a593Smuzhiyun /* Actual setup will be performed after the modem responds. */
230*4882a593Smuzhiyun return 0;
231*4882a593Smuzhiyun err:
232*4882a593Smuzhiyun tty->disc_data = NULL;
233*4882a593Smuzhiyun return ret;
234*4882a593Smuzhiyun }
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun /* Line discipline .close() */
v253_close(struct tty_struct * tty)237*4882a593Smuzhiyun static void v253_close(struct tty_struct *tty)
238*4882a593Smuzhiyun {
239*4882a593Smuzhiyun struct snd_soc_component *component = tty->disc_data;
240*4882a593Smuzhiyun struct cx20442_priv *cx20442;
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun tty->disc_data = NULL;
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun if (!component)
245*4882a593Smuzhiyun return;
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun cx20442 = snd_soc_component_get_drvdata(component);
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun /* Prevent the codec driver from further accessing the modem */
250*4882a593Smuzhiyun cx20442->tty = NULL;
251*4882a593Smuzhiyun component->card->pop_time = 0;
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun /* Line discipline .hangup() */
v253_hangup(struct tty_struct * tty)255*4882a593Smuzhiyun static int v253_hangup(struct tty_struct *tty)
256*4882a593Smuzhiyun {
257*4882a593Smuzhiyun v253_close(tty);
258*4882a593Smuzhiyun return 0;
259*4882a593Smuzhiyun }
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun /* Line discipline .receive_buf() */
v253_receive(struct tty_struct * tty,const unsigned char * cp,char * fp,int count)262*4882a593Smuzhiyun static void v253_receive(struct tty_struct *tty,
263*4882a593Smuzhiyun const unsigned char *cp, char *fp, int count)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun struct snd_soc_component *component = tty->disc_data;
266*4882a593Smuzhiyun struct cx20442_priv *cx20442;
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun if (!component)
269*4882a593Smuzhiyun return;
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun cx20442 = snd_soc_component_get_drvdata(component);
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun if (!cx20442->tty) {
274*4882a593Smuzhiyun /* First modem response, complete setup procedure */
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun /* Set up codec driver access to modem controls */
277*4882a593Smuzhiyun cx20442->tty = tty;
278*4882a593Smuzhiyun component->card->pop_time = 1;
279*4882a593Smuzhiyun }
280*4882a593Smuzhiyun }
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun /* Line discipline .write_wakeup() */
v253_wakeup(struct tty_struct * tty)283*4882a593Smuzhiyun static void v253_wakeup(struct tty_struct *tty)
284*4882a593Smuzhiyun {
285*4882a593Smuzhiyun }
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun struct tty_ldisc_ops v253_ops = {
288*4882a593Smuzhiyun .magic = TTY_LDISC_MAGIC,
289*4882a593Smuzhiyun .name = "cx20442",
290*4882a593Smuzhiyun .owner = THIS_MODULE,
291*4882a593Smuzhiyun .open = v253_open,
292*4882a593Smuzhiyun .close = v253_close,
293*4882a593Smuzhiyun .hangup = v253_hangup,
294*4882a593Smuzhiyun .receive_buf = v253_receive,
295*4882a593Smuzhiyun .write_wakeup = v253_wakeup,
296*4882a593Smuzhiyun };
297*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(v253_ops);
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun
300*4882a593Smuzhiyun /*
301*4882a593Smuzhiyun * Codec DAI
302*4882a593Smuzhiyun */
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun static struct snd_soc_dai_driver cx20442_dai = {
305*4882a593Smuzhiyun .name = "cx20442-voice",
306*4882a593Smuzhiyun .playback = {
307*4882a593Smuzhiyun .stream_name = "Playback",
308*4882a593Smuzhiyun .channels_min = 1,
309*4882a593Smuzhiyun .channels_max = 1,
310*4882a593Smuzhiyun .rates = SNDRV_PCM_RATE_8000,
311*4882a593Smuzhiyun .formats = SNDRV_PCM_FMTBIT_S16_LE,
312*4882a593Smuzhiyun },
313*4882a593Smuzhiyun .capture = {
314*4882a593Smuzhiyun .stream_name = "Capture",
315*4882a593Smuzhiyun .channels_min = 1,
316*4882a593Smuzhiyun .channels_max = 1,
317*4882a593Smuzhiyun .rates = SNDRV_PCM_RATE_8000,
318*4882a593Smuzhiyun .formats = SNDRV_PCM_FMTBIT_S16_LE,
319*4882a593Smuzhiyun },
320*4882a593Smuzhiyun };
321*4882a593Smuzhiyun
cx20442_set_bias_level(struct snd_soc_component * component,enum snd_soc_bias_level level)322*4882a593Smuzhiyun static int cx20442_set_bias_level(struct snd_soc_component *component,
323*4882a593Smuzhiyun enum snd_soc_bias_level level)
324*4882a593Smuzhiyun {
325*4882a593Smuzhiyun struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
326*4882a593Smuzhiyun int err = 0;
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun switch (level) {
329*4882a593Smuzhiyun case SND_SOC_BIAS_PREPARE:
330*4882a593Smuzhiyun if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY)
331*4882a593Smuzhiyun break;
332*4882a593Smuzhiyun if (IS_ERR(cx20442->por))
333*4882a593Smuzhiyun err = PTR_ERR(cx20442->por);
334*4882a593Smuzhiyun else
335*4882a593Smuzhiyun err = regulator_enable(cx20442->por);
336*4882a593Smuzhiyun break;
337*4882a593Smuzhiyun case SND_SOC_BIAS_STANDBY:
338*4882a593Smuzhiyun if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE)
339*4882a593Smuzhiyun break;
340*4882a593Smuzhiyun if (IS_ERR(cx20442->por))
341*4882a593Smuzhiyun err = PTR_ERR(cx20442->por);
342*4882a593Smuzhiyun else
343*4882a593Smuzhiyun err = regulator_disable(cx20442->por);
344*4882a593Smuzhiyun break;
345*4882a593Smuzhiyun default:
346*4882a593Smuzhiyun break;
347*4882a593Smuzhiyun }
348*4882a593Smuzhiyun
349*4882a593Smuzhiyun return err;
350*4882a593Smuzhiyun }
351*4882a593Smuzhiyun
cx20442_component_probe(struct snd_soc_component * component)352*4882a593Smuzhiyun static int cx20442_component_probe(struct snd_soc_component *component)
353*4882a593Smuzhiyun {
354*4882a593Smuzhiyun struct cx20442_priv *cx20442;
355*4882a593Smuzhiyun
356*4882a593Smuzhiyun cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
357*4882a593Smuzhiyun if (cx20442 == NULL)
358*4882a593Smuzhiyun return -ENOMEM;
359*4882a593Smuzhiyun
360*4882a593Smuzhiyun cx20442->por = regulator_get(component->dev, "POR");
361*4882a593Smuzhiyun if (IS_ERR(cx20442->por)) {
362*4882a593Smuzhiyun int err = PTR_ERR(cx20442->por);
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun dev_warn(component->dev, "failed to get POR supply (%d)", err);
365*4882a593Smuzhiyun /*
366*4882a593Smuzhiyun * When running on a non-dt platform and requested regulator
367*4882a593Smuzhiyun * is not available, regulator_get() never returns
368*4882a593Smuzhiyun * -EPROBE_DEFER as it is not able to justify if the regulator
369*4882a593Smuzhiyun * may still appear later. On the other hand, the board can
370*4882a593Smuzhiyun * still set full constraints flag at late_initcall in order
371*4882a593Smuzhiyun * to instruct regulator_get() to return a dummy one if
372*4882a593Smuzhiyun * sufficient. Hence, if we get -ENODEV here, let's convert
373*4882a593Smuzhiyun * it to -EPROBE_DEFER and wait for the board to decide or
374*4882a593Smuzhiyun * let Deferred Probe infrastructure handle this error.
375*4882a593Smuzhiyun */
376*4882a593Smuzhiyun if (err == -ENODEV)
377*4882a593Smuzhiyun err = -EPROBE_DEFER;
378*4882a593Smuzhiyun kfree(cx20442);
379*4882a593Smuzhiyun return err;
380*4882a593Smuzhiyun }
381*4882a593Smuzhiyun
382*4882a593Smuzhiyun cx20442->tty = NULL;
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun snd_soc_component_set_drvdata(component, cx20442);
385*4882a593Smuzhiyun component->card->pop_time = 0;
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun return 0;
388*4882a593Smuzhiyun }
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun /* power down chip */
cx20442_component_remove(struct snd_soc_component * component)391*4882a593Smuzhiyun static void cx20442_component_remove(struct snd_soc_component *component)
392*4882a593Smuzhiyun {
393*4882a593Smuzhiyun struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component);
394*4882a593Smuzhiyun
395*4882a593Smuzhiyun if (cx20442->tty) {
396*4882a593Smuzhiyun struct tty_struct *tty = cx20442->tty;
397*4882a593Smuzhiyun tty_hangup(tty);
398*4882a593Smuzhiyun }
399*4882a593Smuzhiyun
400*4882a593Smuzhiyun if (!IS_ERR(cx20442->por)) {
401*4882a593Smuzhiyun /* should be already in STANDBY, hence disabled */
402*4882a593Smuzhiyun regulator_put(cx20442->por);
403*4882a593Smuzhiyun }
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun snd_soc_component_set_drvdata(component, NULL);
406*4882a593Smuzhiyun kfree(cx20442);
407*4882a593Smuzhiyun }
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun static const struct snd_soc_component_driver cx20442_component_dev = {
410*4882a593Smuzhiyun .probe = cx20442_component_probe,
411*4882a593Smuzhiyun .remove = cx20442_component_remove,
412*4882a593Smuzhiyun .set_bias_level = cx20442_set_bias_level,
413*4882a593Smuzhiyun .read = cx20442_read_reg_cache,
414*4882a593Smuzhiyun .write = cx20442_write,
415*4882a593Smuzhiyun .dapm_widgets = cx20442_dapm_widgets,
416*4882a593Smuzhiyun .num_dapm_widgets = ARRAY_SIZE(cx20442_dapm_widgets),
417*4882a593Smuzhiyun .dapm_routes = cx20442_audio_map,
418*4882a593Smuzhiyun .num_dapm_routes = ARRAY_SIZE(cx20442_audio_map),
419*4882a593Smuzhiyun .idle_bias_on = 1,
420*4882a593Smuzhiyun .use_pmdown_time = 1,
421*4882a593Smuzhiyun .endianness = 1,
422*4882a593Smuzhiyun .non_legacy_dai_naming = 1,
423*4882a593Smuzhiyun };
424*4882a593Smuzhiyun
cx20442_platform_probe(struct platform_device * pdev)425*4882a593Smuzhiyun static int cx20442_platform_probe(struct platform_device *pdev)
426*4882a593Smuzhiyun {
427*4882a593Smuzhiyun return devm_snd_soc_register_component(&pdev->dev,
428*4882a593Smuzhiyun &cx20442_component_dev, &cx20442_dai, 1);
429*4882a593Smuzhiyun }
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun static struct platform_driver cx20442_platform_driver = {
432*4882a593Smuzhiyun .driver = {
433*4882a593Smuzhiyun .name = "cx20442-codec",
434*4882a593Smuzhiyun },
435*4882a593Smuzhiyun .probe = cx20442_platform_probe,
436*4882a593Smuzhiyun };
437*4882a593Smuzhiyun
438*4882a593Smuzhiyun module_platform_driver(cx20442_platform_driver);
439*4882a593Smuzhiyun
440*4882a593Smuzhiyun MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
441*4882a593Smuzhiyun MODULE_AUTHOR("Janusz Krzysztofik");
442*4882a593Smuzhiyun MODULE_LICENSE("GPL");
443*4882a593Smuzhiyun MODULE_ALIAS("platform:cx20442-codec");
444