diff mbox

[RFC,1/3] ASoC: Add support for Conexant CX20442-11 voice modem codec

Message ID 200907220522.29221.jkrzyszt@tis.icnet.pl (mailing list archive)
State Awaiting Upstream, archived
Headers show

Commit Message

Janusz Krzysztofik July 22, 2009, 3:22 a.m. UTC
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.

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>
---
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

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Mark Brown July 22, 2009, 10:10 a.m. UTC | #1
On Wed, Jul 22, 2009 at 05:22:28AM +0200, Janusz Krzysztofik wrote:
> 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.

> 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>

This patch is basically fine from an ASoC point of view so I've applied
it.

You'll want to run checkpatch over it and send a followup fixing the
issues it identifies.  Looking at this I suspect that it may be possible
to refactor things so that more of the line discipline interaction is
pushed down into the CODEC driver but that can also be done as a
followup.

Since this (and all the other descriptive text that follows it):

> 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

...is useful documentation I've pulled it into the commit message.  It
would also be nice to add some documentation somewhere explaining how
the hw_write() function is expected to be set up since it's a little
surprising (if the line discipline stuff were pushed down into the code
that'd become a lot more obvious).
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Janusz Krzysztofik July 22, 2009, 2:01 p.m. UTC | #2
Hi Mark,

Mark Brown wrote:
> On Wed, Jul 22, 2009 at 05:22:28AM +0200, Janusz Krzysztofik wrote:
>> 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 patch is basically fine from an ASoC point of view so I've applied
> it.

Thanks!

> You'll want to run checkpatch over it and send a followup fixing the

Sorry for not doing this before submitting. Will send the followup soon.

> issues it identifies.  Looking at this I suspect that it may be possible
> to refactor things so that more of the line discipline interaction is
> pushed down into the CODEC driver but that can also be done as a
> followup.

I have put the line discipline code into the machine driver for two reasons:
1. For me, the actual code is much more machine than codec specific.
2. It was much easier to update the codec structure instance with 
machine's provided hw_write() rather than fiddle with adding a new 
member to the structure definition for passing a machine callback to the 
line discipline functions.

If you think we can find any use cases for drivers supporting 
codec/modem combination other than CX20442 on Amstrad Delta, I can agree 
with you on moving the line discipline code out of the machine driver, 
but instead of pushing it down to the codec I would rather se it as a 
separate module.  Anyway, I think that some kind of support on the ASoC 
top level for this case could be helpfull.

> would also be nice to add some documentation somewhere explaining how
> the hw_write() function is expected to be set up since it's a little
> surprising (if the line discipline stuff were pushed down into the code
> that'd become a lot more obvious).

I'll add a few words to the followup.

Thanks,
Janusz
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Janusz Krzysztofik July 28, 2009, 2:59 p.m. UTC | #3
Wednesday 22 July 2009 12:10:28 Mark Brown wrote:
> On Wed, Jul 22, 2009 at 05:22:28AM +0200, Janusz Krzysztofik wrote:
> > 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.
> >
> You'll want to run checkpatch over it and send a followup fixing the
> issues it identifies.  Looking at this I suspect that it may be possible
> to refactor things so that more of the line discipline interaction is
> pushed down into the CODEC driver but that can also be done as a
> followup.
>
> would also be nice to add some documentation somewhere explaining how
> the hw_write() function is expected to be set up since it's a little
> surprising (if the line discipline stuff were pushed down into the code
> that'd become a lot more obvious).

Mark,
I am ready with a followup that corrects those minor issues, as well as with a 
new, corrected version of the machine driver. However, before I submit them, 
I'd like to discuss again your idea of pushing down the line discipline code 
into the codec driver.

As you may have noticed, my codec driver was ready for adopting it to one or 
more additional buses of different types, in a way similiar to those 
implemented in other codec drivers. There was a room for a hypothetical 
cx20442_xxx_probe() function, invoked by a hypothetical xxx bus driver after 
the bus comes up or a device is detected on it. That function could then set 
up its own codec->control_data and codec->hw_write pointers for talking to 
the codec via a hypothetical xxx bus adapter using its write method. In 
particular, that could be a line discipline provided acceess to the codec 
assosociated modem.

Following that model, the line discipline code didn't seam to belong to a 
codec driver for me. If it could be recognized as a new bus dirver, it should 
be pushed down probably into a separate module. However, if it was rather 
some kind of a bus adapter driver, it seamed to belong to the machine code.

As I had got no positive feedback to my RFC on inventing a new bus (votes were 
rather neutral, and I agreed with them[1]), I decided to put the line 
discipline code into the machine driver.

If you see things somehow different, please let me know, I can try to follow 
your ideas and rearrange the code.

Cheers,
Janusz

[1] http://www.spinics.net/lists/linux-serial/msg01856.html
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown July 28, 2009, 3:07 p.m. UTC | #4
On Tue, Jul 28, 2009 at 04:59:33PM +0200, Janusz Krzysztofik wrote:

> Following that model, the line discipline code didn't seam to belong to a 
> codec driver for me. If it could be recognized as a new bus dirver, it should 
> be pushed down probably into a separate module. However, if it was rather 
> some kind of a bus adapter driver, it seamed to belong to the machine code.

The way it looks with the current code the line discipline itself is
going to apply equally well to any serial port that's used - I'm not
clear what's system-specific about it.  The bit that connects the line
discipline to the line is a different matter but the bit that interacts
with the TTY layer after registration is working through a pluggable
abstraction layer and shouldn't, I'd expect, be specific to the
particular serial port.
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Janusz Krzysztofik July 28, 2009, 4:07 p.m. UTC | #5
Tuesday 28 July 2009 17:07:27 Mark Brown wrote:
> On Tue, Jul 28, 2009 at 04:59:33PM +0200, Janusz Krzysztofik wrote:
> > Following that model, the line discipline code didn't seam to belong to a
> > codec driver for me. If it could be recognized as a new bus dirver, it
> > should be pushed down probably into a separate module. However, if it was
> > rather some kind of a bus adapter driver, it seamed to belong to the
> > machine code.
>
> The way it looks with the current code the line discipline itself is
> going to apply equally well to any serial port that's used - I'm not
> clear what's system-specific about it.  The bit that connects the line
> discipline to the line is a different matter but the bit that interacts
> with the TTY layer after registration is working through a pluggable
> abstraction layer and shouldn't, I'd expect, be specific to the
> particular serial port.

Mark,
I think I can't tell anything more before I try to implement it in a way you 
suggest, so I will.

What I would like to retain from the current codec driver are those two modes 
of operation: without and with the line discipline activated. Initially, the 
codec would be registered as a platform device, as it is now. Activation of 
the line discipline would deregister the codec device from the platform bus 
and reregister it again, now as the line discipline provided device. Is this 
possible with current ASoC framework?

Thanks,
Janusz
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown July 28, 2009, 4:11 p.m. UTC | #6
On Tue, Jul 28, 2009 at 06:07:29PM +0200, Janusz Krzysztofik wrote:

> What I would like to retain from the current codec driver are those two modes 
> of operation: without and with the line discipline activated. Initially, the 
> codec would be registered as a platform device, as it is now. Activation of 
> the line discipline would deregister the codec device from the platform bus 
> and reregister it again, now as the line discipline provided device. Is this 
> possible with current ASoC framework?

No, not currently.  But why would you do that, anyway?

Like I say, I'd suggest always keeping the controls registered and just
masking and unmasking from user space - the control indexes get used so
removing them will just confuse things.
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Janusz Krzysztofik July 28, 2009, 4:36 p.m. UTC | #7
Tuesday 28 July 2009 18:11:32 Mark Brown wrote:
> On Tue, Jul 28, 2009 at 06:07:29PM +0200, Janusz Krzysztofik wrote:
> > now. Activation of the line discipline would deregister the codec device
> > from the platform bus and reregister it again, now as the line discipline
> > provided device. Is this possible with current ASoC framework?
>
> No, not currently.  But why would you do that, anyway?
>
> Like I say, I'd suggest always keeping the controls registered and just
> masking and unmasking from user space - the control indexes get used so
> removing them will just confuse things.

However, in order for the machine driver to unmask/mask a machine specific 
control, the codec driver must drop the machine driver a message about the 
line discipline being activeted/deactivated, right? The only such existing 
feedback I can see the codec is able to use is via snd_soc_init_card() that 
calls snd_soc_dai_link.init() in turn. Am I missing something?

Janusz
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown July 28, 2009, 4:39 p.m. UTC | #8
On Tue, Jul 28, 2009 at 06:36:01PM +0200, Janusz Krzysztofik wrote:
> Tuesday 28 July 2009 18:11:32 Mark Brown wrote:

> > Like I say, I'd suggest always keeping the controls registered and just
> > masking and unmasking from user space - the control indexes get used so
> > removing them will just confuse things.

> However, in order for the machine driver to unmask/mask a machine specific 
> control, the codec driver must drop the machine driver a message about the 
> line discipline being activeted/deactivated, right? The only such existing 
> feedback I can see the codec is able to use is via snd_soc_init_card() that 
> calls snd_soc_dai_link.init() in turn. Am I missing something?

Well, the machine driver can look up controls by name (this is going to
be required in order to find the controls anyway...).  Besides, given
that the machine driver is already entirely CODEC-specific there'd no
problem with it calling into CODEC-specific APIs to do things.
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

--- linux-2.6.31-rc1/sound/soc/codecs/Kconfig.orig	2009-07-04 16:40:00.000000000 +0200
+++ linux-2.6.31-rc1/sound/soc/codecs/Kconfig	2009-07-22 01:01:08.000000000 +0200
@@ -17,6 +17,7 @@  config SND_SOC_ALL_CODECS
 	select SND_SOC_AK4104 if SPI_MASTER
 	select SND_SOC_AK4535 if I2C
 	select SND_SOC_CS4270 if I2C
+	select SND_SOC_CX20442
 	select SND_SOC_PCM3008
 	select SND_SOC_SPDIF
 	select SND_SOC_SSM2602 if I2C
@@ -86,6 +87,9 @@  config SND_SOC_CS4270_VD33_ERRATA
 	bool
 	depends on SND_SOC_CS4270
 
+config SND_SOC_CX20442
+	tristate
+
 config SND_SOC_L3
        tristate
 
--- linux-2.6.31-rc1/sound/soc/codecs/Makefile.orig	2009-07-04 16:40:00.000000000 +0200
+++ linux-2.6.31-rc1/sound/soc/codecs/Makefile	2009-07-22 01:01:08.000000000 +0200
@@ -4,6 +4,7 @@  snd-soc-ad73311-objs := ad73311.o
 snd-soc-ak4104-objs := ak4104.o
 snd-soc-ak4535-objs := ak4535.o
 snd-soc-cs4270-objs := cs4270.o
+snd-soc-cx20442-objs := cx20442.o
 snd-soc-l3-objs := l3.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
@@ -41,6 +42,7 @@  obj-$(CONFIG_SND_SOC_AD73311) += snd-soc
 obj-$(CONFIG_SND_SOC_AK4104)	+= snd-soc-ak4104.o
 obj-$(CONFIG_SND_SOC_AK4535)	+= snd-soc-ak4535.o
 obj-$(CONFIG_SND_SOC_CS4270)	+= snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CX20442)	+= snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_L3)	+= snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_PCM3008)	+= snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)	+= snd-soc-spdif.o
--- /dev/null	2009-06-24 10:33:03.437003511 +0200
+++ linux-2.6.31-rc1/sound/soc/codecs/cx20442.h	2009-07-22 01:01:08.000000000 +0200
@@ -0,0 +1,19 @@ 
+/*
+ * cx20442.h  --  audio driver for CX20442
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef _CX20442_CODEC_H
+#define _CX20442_CODEC_H
+
+extern struct snd_soc_dai cx20442_dai;
+extern struct snd_soc_codec_device cx20442_codec_dev;
+
+#endif
--- /dev/null	2009-06-24 10:33:03.437003511 +0200
+++ linux-2.6.31-rc1/sound/soc/codecs/cx20442.c	2009-07-22 01:01:08.000000000 +0200
@@ -0,0 +1,395 @@ 
+/*
+ * cx20442.c  --  CX20442 ALSA Soc Audio driver
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * Initially based on sound/soc/codecs/wm8400.c
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+
+#include "cx20442.h"
+
+
+struct cx20442_priv {
+	struct snd_soc_codec codec;
+	u8 reg_cache[1];
+};
+
+#define CX20442_PM		0x0
+
+#define CX20442_TELIN		0
+#define CX20442_TELOUT		1
+#define CX20442_MIC		2
+#define CX20442_SPKOUT		3
+#define CX20442_AGC		4
+
+static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("TELOUT"),
+	SND_SOC_DAPM_OUTPUT("SPKOUT"),
+	SND_SOC_DAPM_OUTPUT("AGCOUT"),
+
+	SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
+	SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
+
+	SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+	SND_SOC_DAPM_INPUT("TELIN"),
+	SND_SOC_DAPM_INPUT("MIC"),
+	SND_SOC_DAPM_INPUT("AGCIN"),
+};
+
+static const struct snd_soc_dapm_route cx20442_audio_map[] = {
+	{"TELOUT", NULL, "TELOUT Amp"},
+
+	{"SPKOUT", NULL, "SPKOUT Mixer"},
+	{"SPKOUT Mixer", NULL, "SPKOUT Amp"},
+
+	{"TELOUT Amp", NULL, "DAC"},
+	{"SPKOUT Amp", NULL, "DAC"},
+
+	{"SPKOUT Mixer", NULL, "SPKOUT AGC"},
+	{"SPKOUT AGC", NULL, "AGCIN"},
+
+	{"AGCOUT", NULL, "MIC AGC"},
+	{"MIC AGC", NULL, "MIC"},
+
+	{"MIC Bias", NULL, "MIC"},
+	{"Input Mixer", NULL, "MIC Bias"},
+
+	{"TELIN Bias", NULL, "TELIN"},
+	{"Input Mixer", NULL, "TELIN Bias"},
+
+	{"ADC", NULL, "Input Mixer"},
+};
+
+static int cx20442_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets,
+				  ARRAY_SIZE(cx20442_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, cx20442_audio_map,
+				ARRAY_SIZE(cx20442_audio_map));
+
+	snd_soc_dapm_new_widgets(codec);
+	return 0;
+}
+
+static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
+							unsigned int reg)
+{
+	u8 *reg_cache = codec->reg_cache;
+
+	if (reg >= codec->reg_cache_size)
+		return -EINVAL;
+
+	return reg_cache[reg];
+}
+
+enum v253_vls {
+	V253_VLS_NONE = 0,
+	V253_VLS_T,
+	V253_VLS_L,
+	V253_VLS_LT,
+	V253_VLS_S,
+	V253_VLS_ST,
+	V253_VLS_M,
+	V253_VLS_MST,
+	V253_VLS_S1,
+	V253_VLS_S1T,
+	V253_VLS_MS1T,
+	V253_VLS_M1,
+	V253_VLS_M1ST,
+	V253_VLS_M1S1T,
+	V253_VLS_H,
+	V253_VLS_HT,
+	V253_VLS_MS,
+	V253_VLS_MS1,
+	V253_VLS_M1S,
+	V253_VLS_M1S1,
+	V253_VLS_TEST,
+};
+
+static int cx20442_pm_to_v253_vls(u8 value)
+{
+	switch(value & ~(1 << CX20442_AGC)) {
+	case 0:
+		return V253_VLS_T;
+	case (1 << CX20442_SPKOUT):
+		return V253_VLS_S1;
+	case (1 << CX20442_MIC):
+		return V253_VLS_M1;
+	case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+		return V253_VLS_M1S1;
+	case (1 << CX20442_TELOUT):
+	case (1 << CX20442_TELIN):
+	case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
+		return V253_VLS_L;
+	case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
+		return V253_VLS_NONE;
+	}
+	return -EINVAL;
+}
+static int cx20442_pm_to_v253_vsp(u8 value)
+{
+	switch(value & ~(1 << CX20442_AGC)) {
+	case (1 << CX20442_SPKOUT):
+	case (1 << CX20442_MIC):
+	case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+		return (bool)(value & (1 << CX20442_AGC));
+	}
+	return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
+}
+
+static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
+							unsigned int value)
+{
+	u8 *reg_cache = codec->reg_cache;
+	int vls, vsp, old, len;
+	char buf[18];
+
+	if (reg >= codec->reg_cache_size)
+		return -EINVAL;
+
+	if (!codec->hw_write || !codec->control_data)
+		return -EIO;
+
+	old = reg_cache[reg];
+	reg_cache[reg] = value;
+
+	vls = cx20442_pm_to_v253_vls(value);
+	if (vls < 0)
+		return vls;
+
+	vsp = cx20442_pm_to_v253_vsp(value);
+	if (vsp < 0 )
+		return vsp;
+
+	if ((vls == V253_VLS_T) ||
+			(vls == cx20442_pm_to_v253_vls(old))) {
+		if (vsp == cx20442_pm_to_v253_vsp(old))
+			return 0;
+		len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
+	} else if (vsp == cx20442_pm_to_v253_vsp(old))
+		len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
+	else
+		len = snprintf(buf, ARRAY_SIZE(buf),
+					"at+vls=%d;+vsp=%d\r", vls, vsp);
+
+	if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
+		return -ENOMEM;
+
+	if (codec->hw_write(codec->control_data, buf, len) != len)
+		return -EIO;
+
+	return 0;
+}
+
+struct snd_soc_dai cx20442_dai = {
+	.name = "CX20442",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+};
+EXPORT_SYMBOL_GPL(cx20442_dai);
+
+static struct snd_soc_codec *cx20442_codec;
+
+static int cx20442_codec_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	int ret;
+
+	if(!cx20442_codec) {
+		dev_err(&pdev->dev, "cx20442 not yet discovered\n");
+		return -ENODEV;
+	}
+	codec = cx20442_codec;
+
+	socdev->card->codec = codec;
+
+	/* register pcms */
+	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create pcms\n");
+		goto pcm_err;
+	}
+
+	cx20442_add_widgets(codec);
+
+	ret = snd_soc_init_card(socdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to register card\n");
+		goto card_err;
+	}
+
+	return ret;
+
+card_err:
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+pcm_err:
+	return ret;
+}
+
+/* power down chip */
+static int cx20442_codec_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+
+	return 0;
+}
+
+struct snd_soc_codec_device cx20442_codec_dev = {
+	.probe = 	cx20442_codec_probe,
+	.remove = 	cx20442_codec_remove,
+};
+EXPORT_SYMBOL_GPL(cx20442_codec_dev);
+
+static int cx20442_register(struct cx20442_priv *cx20442)
+{
+	struct snd_soc_codec *codec = &cx20442->codec;
+	int ret;
+
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+
+	codec->name = "CX20442";
+	codec->owner = THIS_MODULE;
+	codec->private_data = cx20442;
+
+	codec->dai = &cx20442_dai;
+	codec->num_dai = 1;
+
+	codec->reg_cache = &cx20442->reg_cache;
+	codec->reg_cache_size = ARRAY_SIZE(cx20442->reg_cache);
+	codec->read = cx20442_read_reg_cache;
+	codec->write = cx20442_write;
+
+	codec->bias_level = SND_SOC_BIAS_OFF;
+
+	cx20442_dai.dev = codec->dev;
+
+	cx20442_codec = codec;
+
+	ret = snd_soc_register_codec(codec);
+	if (ret != 0) {
+		//dev_err(&dev->dev, "Failed to register codec: %d\n", ret);
+		goto err;
+	}
+
+	ret = snd_soc_register_dai(&cx20442_dai);
+	if (ret != 0) {
+		//dev_err(&dev->dev, "Failed to register DAI: %d\n", ret);
+		goto err_codec;
+	}
+
+	return 0;
+
+err_codec:
+	snd_soc_unregister_codec(codec);
+err:
+	cx20442_codec = NULL;
+	kfree(cx20442);
+	return ret;
+}
+
+static void cx20442_unregister(struct cx20442_priv *cx20442)
+{
+	snd_soc_unregister_dai(&cx20442_dai);
+	snd_soc_unregister_codec(&cx20442->codec);
+
+	cx20442_codec = NULL;
+	kfree(cx20442);
+}
+
+static int cx20442_platform_probe(struct platform_device *pdev)
+{
+	struct cx20442_priv *cx20442;
+	struct snd_soc_codec *codec;
+
+	cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
+	if (cx20442 == NULL)
+		return -ENOMEM;
+
+	codec = &cx20442->codec;
+
+	codec->control_data = NULL;
+	codec->hw_write = NULL;
+	codec->pop_time = 0;
+
+	codec->dev = &pdev->dev;
+	platform_set_drvdata(pdev, cx20442);
+
+	return cx20442_register(cx20442);
+}
+
+static int __exit cx20442_platform_remove(struct platform_device *pdev)
+{
+	struct cx20442_priv *cx20442 = platform_get_drvdata(pdev);
+
+	cx20442_unregister(cx20442);
+	return 0;
+}
+
+static struct platform_driver cx20442_platform_driver = {
+	.driver = {
+		.name = "cx20442",
+		.owner = THIS_MODULE,
+		},
+	.probe = cx20442_platform_probe,
+	.remove = __exit_p(cx20442_platform_remove),
+};
+
+static int __init cx20442_init(void)
+{
+	return platform_driver_register(&cx20442_platform_driver);
+}
+module_init(cx20442_init);
+
+static void __exit cx20442_exit(void)
+{
+	platform_driver_unregister(&cx20442_platform_driver);
+}
+module_exit(cx20442_exit);
+
+MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
+MODULE_AUTHOR("Janusz Krzysztofik");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cx20442-codec");