aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/cx20442.c
diff options
context:
space:
mode:
authorJanusz Krzysztofik <jkrzyszt@tis.icnet.pl>2009-07-21 23:22:28 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-07-23 06:08:29 -0400
commit459dc35233c88d9eb7c5d0e6c086122751e64750 (patch)
tree8e2e4f645a2e23ce80b11d52a46d0e18d8c99ac3 /sound/soc/codecs/cx20442.c
parentc30853df9834bccc2841798b0ab8f151929ae500 (diff)
ASoC: Add support for Conexant CX20442-11 voice modem codec
This patch adds support for Conexant CX20442-11 voice modem codec, suitable for use by the ASoC board driver for Amstrad E3 (Delta) videophone. Related sound card driver will follow. This codec is an optional part of the Conexant SmartV three chip modem design. As such, documentation for its proprietary digital audio interface is not available. However, on Amstrad Delta board, thanks to Mark Underwood who created an initial, omap-alsa based sound driver a few years ago[1], the codec has been discovered to be accessible not only from the modem side, but also over the OMAP McBSP based CPU DAI. Thus, the driver can be used by any sound card that can access the codec DAI directly. The DAI configuration parameters (sample rate and format, number of channels) has been selected out empirically for best user experience. The codec analogue interface consists of two pairs of analogue I/O pins: speakerphone interface or telephone handset/headset interface. Furthermore, it seams to provide two operation modes for speakerphone I/O: standard and advanced, with automatic gain control and echo cancelation. Even if the codec control interface is unknown and not available, all those interfaces and modes can be selected over the modem chip using V.253 commands. The driver is able to issue necessary commands over a suitable hw_write function if provided by a sound card driver. Otherwise, the codec can be controlled over the modem from userspace while inactive. Even if nothig is known about the codec internal power management capabilities, DAPM widgets has been used to model the codec audio map. Automatically performed powering up/down of those virtual widgets results in corresponding V.253 commands being issued. Some driver features/oddities may be board specific, but I have no way to verify that with any board other than Amstrad Delta. [1] http://www.earth.li/pipermail/e3-hacking/2006-April/000481.html Created and tested against linux-2.6.31-rc3. Applies and works with linux-omap-2.6 commit 7c5cb7862d32cb344be7831d466535d5255e35ac as well. Signed-off-by: Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/cx20442.c')
-rw-r--r--sound/soc/codecs/cx20442.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c
new file mode 100644
index 000000000000..f64483c75b5d
--- /dev/null
+++ b/sound/soc/codecs/cx20442.c
@@ -0,0 +1,395 @@
1/*
2 * cx20442.c -- CX20442 ALSA Soc Audio driver
3 *
4 * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
5 *
6 * Initially based on sound/soc/codecs/wm8400.c
7 * Copyright 2008, 2009 Wolfson Microelectronics PLC.
8 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version.
14 */
15
16#include <sound/core.h>
17#include <sound/initval.h>
18#include <sound/soc-dapm.h>
19
20#include "cx20442.h"
21
22
23struct cx20442_priv {
24 struct snd_soc_codec codec;
25 u8 reg_cache[1];
26};
27
28#define CX20442_PM 0x0
29
30#define CX20442_TELIN 0
31#define CX20442_TELOUT 1
32#define CX20442_MIC 2
33#define CX20442_SPKOUT 3
34#define CX20442_AGC 4
35
36static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
37 SND_SOC_DAPM_OUTPUT("TELOUT"),
38 SND_SOC_DAPM_OUTPUT("SPKOUT"),
39 SND_SOC_DAPM_OUTPUT("AGCOUT"),
40
41 SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
42
43 SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
44 SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
45 SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
46
47 SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
48 SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
49
50 SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
51
52 SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
53 SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
54
55 SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
56
57 SND_SOC_DAPM_INPUT("TELIN"),
58 SND_SOC_DAPM_INPUT("MIC"),
59 SND_SOC_DAPM_INPUT("AGCIN"),
60};
61
62static const struct snd_soc_dapm_route cx20442_audio_map[] = {
63 {"TELOUT", NULL, "TELOUT Amp"},
64
65 {"SPKOUT", NULL, "SPKOUT Mixer"},
66 {"SPKOUT Mixer", NULL, "SPKOUT Amp"},
67
68 {"TELOUT Amp", NULL, "DAC"},
69 {"SPKOUT Amp", NULL, "DAC"},
70
71 {"SPKOUT Mixer", NULL, "SPKOUT AGC"},
72 {"SPKOUT AGC", NULL, "AGCIN"},
73
74 {"AGCOUT", NULL, "MIC AGC"},
75 {"MIC AGC", NULL, "MIC"},
76
77 {"MIC Bias", NULL, "MIC"},
78 {"Input Mixer", NULL, "MIC Bias"},
79
80 {"TELIN Bias", NULL, "TELIN"},
81 {"Input Mixer", NULL, "TELIN Bias"},
82
83 {"ADC", NULL, "Input Mixer"},
84};
85
86static int cx20442_add_widgets(struct snd_soc_codec *codec)
87{
88 snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets,
89 ARRAY_SIZE(cx20442_dapm_widgets));
90
91 snd_soc_dapm_add_routes(codec, cx20442_audio_map,
92 ARRAY_SIZE(cx20442_audio_map));
93
94 snd_soc_dapm_new_widgets(codec);
95 return 0;
96}
97
98static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
99 unsigned int reg)
100{
101 u8 *reg_cache = codec->reg_cache;
102
103 if (reg >= codec->reg_cache_size)
104 return -EINVAL;
105
106 return reg_cache[reg];
107}
108
109enum v253_vls {
110 V253_VLS_NONE = 0,
111 V253_VLS_T,
112 V253_VLS_L,
113 V253_VLS_LT,
114 V253_VLS_S,
115 V253_VLS_ST,
116 V253_VLS_M,
117 V253_VLS_MST,
118 V253_VLS_S1,
119 V253_VLS_S1T,
120 V253_VLS_MS1T,
121 V253_VLS_M1,
122 V253_VLS_M1ST,
123 V253_VLS_M1S1T,
124 V253_VLS_H,
125 V253_VLS_HT,
126 V253_VLS_MS,
127 V253_VLS_MS1,
128 V253_VLS_M1S,
129 V253_VLS_M1S1,
130 V253_VLS_TEST,
131};
132
133static int cx20442_pm_to_v253_vls(u8 value)
134{
135 switch(value & ~(1 << CX20442_AGC)) {
136 case 0:
137 return V253_VLS_T;
138 case (1 << CX20442_SPKOUT):
139 return V253_VLS_S1;
140 case (1 << CX20442_MIC):
141 return V253_VLS_M1;
142 case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
143 return V253_VLS_M1S1;
144 case (1 << CX20442_TELOUT):
145 case (1 << CX20442_TELIN):
146 case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
147 return V253_VLS_L;
148 case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
149 return V253_VLS_NONE;
150 }
151 return -EINVAL;
152}
153static int cx20442_pm_to_v253_vsp(u8 value)
154{
155 switch(value & ~(1 << CX20442_AGC)) {
156 case (1 << CX20442_SPKOUT):
157 case (1 << CX20442_MIC):
158 case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
159 return (bool)(value & (1 << CX20442_AGC));
160 }
161 return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
162}
163
164static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
165 unsigned int value)
166{
167 u8 *reg_cache = codec->reg_cache;
168 int vls, vsp, old, len;
169 char buf[18];
170
171 if (reg >= codec->reg_cache_size)
172 return -EINVAL;
173
174 if (!codec->hw_write || !codec->control_data)
175 return -EIO;
176
177 old = reg_cache[reg];
178 reg_cache[reg] = value;
179
180 vls = cx20442_pm_to_v253_vls(value);
181 if (vls < 0)
182 return vls;
183
184 vsp = cx20442_pm_to_v253_vsp(value);
185 if (vsp < 0 )
186 return vsp;
187
188 if ((vls == V253_VLS_T) ||
189 (vls == cx20442_pm_to_v253_vls(old))) {
190 if (vsp == cx20442_pm_to_v253_vsp(old))
191 return 0;
192 len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
193 } else if (vsp == cx20442_pm_to_v253_vsp(old))
194 len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
195 else
196 len = snprintf(buf, ARRAY_SIZE(buf),
197 "at+vls=%d;+vsp=%d\r", vls, vsp);
198
199 if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
200 return -ENOMEM;
201
202 if (codec->hw_write(codec->control_data, buf, len) != len)
203 return -EIO;
204
205 return 0;
206}
207
208struct snd_soc_dai cx20442_dai = {
209 .name = "CX20442",
210 .playback = {
211 .stream_name = "Playback",
212 .channels_min = 1,
213 .channels_max = 1,
214 .rates = SNDRV_PCM_RATE_8000,
215 .formats = SNDRV_PCM_FMTBIT_S16_LE,
216 },
217 .capture = {
218 .stream_name = "Capture",
219 .channels_min = 1,
220 .channels_max = 1,
221 .rates = SNDRV_PCM_RATE_8000,
222 .formats = SNDRV_PCM_FMTBIT_S16_LE,
223 },
224};
225EXPORT_SYMBOL_GPL(cx20442_dai);
226
227static struct snd_soc_codec *cx20442_codec;
228
229static int cx20442_codec_probe(struct platform_device *pdev)
230{
231 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
232 struct snd_soc_codec *codec;
233 int ret;
234
235 if(!cx20442_codec) {
236 dev_err(&pdev->dev, "cx20442 not yet discovered\n");
237 return -ENODEV;
238 }
239 codec = cx20442_codec;
240
241 socdev->card->codec = codec;
242
243 /* register pcms */
244 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
245 if (ret < 0) {
246 dev_err(&pdev->dev, "failed to create pcms\n");
247 goto pcm_err;
248 }
249
250 cx20442_add_widgets(codec);
251
252 ret = snd_soc_init_card(socdev);
253 if (ret < 0) {
254 dev_err(&pdev->dev, "failed to register card\n");
255 goto card_err;
256 }
257
258 return ret;
259
260card_err:
261 snd_soc_free_pcms(socdev);
262 snd_soc_dapm_free(socdev);
263pcm_err:
264 return ret;
265}
266
267/* power down chip */
268static int cx20442_codec_remove(struct platform_device *pdev)
269{
270 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
271
272 snd_soc_free_pcms(socdev);
273 snd_soc_dapm_free(socdev);
274
275 return 0;
276}
277
278struct snd_soc_codec_device cx20442_codec_dev = {
279 .probe = cx20442_codec_probe,
280 .remove = cx20442_codec_remove,
281};
282EXPORT_SYMBOL_GPL(cx20442_codec_dev);
283
284static int cx20442_register(struct cx20442_priv *cx20442)
285{
286 struct snd_soc_codec *codec = &cx20442->codec;
287 int ret;
288
289 mutex_init(&codec->mutex);
290 INIT_LIST_HEAD(&codec->dapm_widgets);
291 INIT_LIST_HEAD(&codec->dapm_paths);
292
293 codec->name = "CX20442";
294 codec->owner = THIS_MODULE;
295 codec->private_data = cx20442;
296
297 codec->dai = &cx20442_dai;
298 codec->num_dai = 1;
299
300 codec->reg_cache = &cx20442->reg_cache;
301 codec->reg_cache_size = ARRAY_SIZE(cx20442->reg_cache);
302 codec->read = cx20442_read_reg_cache;
303 codec->write = cx20442_write;
304
305 codec->bias_level = SND_SOC_BIAS_OFF;
306
307 cx20442_dai.dev = codec->dev;
308
309 cx20442_codec = codec;
310
311 ret = snd_soc_register_codec(codec);
312 if (ret != 0) {
313 //dev_err(&dev->dev, "Failed to register codec: %d\n", ret);
314 goto err;
315 }
316
317 ret = snd_soc_register_dai(&cx20442_dai);
318 if (ret != 0) {
319 //dev_err(&dev->dev, "Failed to register DAI: %d\n", ret);
320 goto err_codec;
321 }
322
323 return 0;
324
325err_codec:
326 snd_soc_unregister_codec(codec);
327err:
328 cx20442_codec = NULL;
329 kfree(cx20442);
330 return ret;
331}
332
333static void cx20442_unregister(struct cx20442_priv *cx20442)
334{
335 snd_soc_unregister_dai(&cx20442_dai);
336 snd_soc_unregister_codec(&cx20442->codec);
337
338 cx20442_codec = NULL;
339 kfree(cx20442);
340}
341
342static int cx20442_platform_probe(struct platform_device *pdev)
343{
344 struct cx20442_priv *cx20442;
345 struct snd_soc_codec *codec;
346
347 cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
348 if (cx20442 == NULL)
349 return -ENOMEM;
350
351 codec = &cx20442->codec;
352
353 codec->control_data = NULL;
354 codec->hw_write = NULL;
355 codec->pop_time = 0;
356
357 codec->dev = &pdev->dev;
358 platform_set_drvdata(pdev, cx20442);
359
360 return cx20442_register(cx20442);
361}
362
363static int __exit cx20442_platform_remove(struct platform_device *pdev)
364{
365 struct cx20442_priv *cx20442 = platform_get_drvdata(pdev);
366
367 cx20442_unregister(cx20442);
368 return 0;
369}
370
371static struct platform_driver cx20442_platform_driver = {
372 .driver = {
373 .name = "cx20442",
374 .owner = THIS_MODULE,
375 },
376 .probe = cx20442_platform_probe,
377 .remove = __exit_p(cx20442_platform_remove),
378};
379
380static int __init cx20442_init(void)
381{
382 return platform_driver_register(&cx20442_platform_driver);
383}
384module_init(cx20442_init);
385
386static void __exit cx20442_exit(void)
387{
388 platform_driver_unregister(&cx20442_platform_driver);
389}
390module_exit(cx20442_exit);
391
392MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
393MODULE_AUTHOR("Janusz Krzysztofik");
394MODULE_LICENSE("GPL");
395MODULE_ALIAS("platform:cx20442-codec");