[v2] ASoC: wm8741: Add differential mono mode support
diff mbox

Message ID 1430858791-11825-1-git-send-email-ce3a@gmx.de
State New
Headers show

Commit Message

Sergej Sawazki May 5, 2015, 8:46 p.m. UTC
The WM8741 DAC supports several differential output modes (stereo,
stereo reversed, mono left, mono right). Add platform data and DT
bindings to configure it.

Signed-off-by: Sergej Sawazki <ce3a@gmx.de>
---
 Documentation/devicetree/bindings/sound/wm8741.txt |  11 +++
 sound/soc/codecs/wm8741.c                          | 108 ++++++++++++++++++---
 sound/soc/codecs/wm8741.h                          |  10 ++
 3 files changed, 117 insertions(+), 12 deletions(-)

Comments

Sergej Sawazki May 10, 2015, 10:23 a.m. UTC | #1
On 05.05.2015 22:46, Sergej Sawazki wrote:
> The WM8741 DAC supports several differential output modes (stereo,
> stereo reversed, mono left, mono right). Add platform data and DT
> bindings to configure it.
>
> Signed-off-by: Sergej Sawazki <ce3a@gmx.de>
> ---
>   Documentation/devicetree/bindings/sound/wm8741.txt |  11 +++
>   sound/soc/codecs/wm8741.c                          | 108 ++++++++++++++++++---
>   sound/soc/codecs/wm8741.h                          |  10 ++
>   3 files changed, 117 insertions(+), 12 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/sound/wm8741.txt b/Documentation/devicetree/bindings/sound/wm8741.txt
> index 74bda58..a133154 100644
> --- a/Documentation/devicetree/bindings/sound/wm8741.txt
> +++ b/Documentation/devicetree/bindings/sound/wm8741.txt
> @@ -10,9 +10,20 @@ Required properties:
>     - reg : the I2C address of the device for I2C, the chip select
>             number for SPI.
>
> +Optional properties:
> +
> +  - diff-mode: Differential output mode configuration. Default value for field
> +    DIFF in register R8 (MODE_CONTROL_2). If absent, the default is 0, shall be:
> +    0 = stereo
> +    1 = mono left
> +    2 = stereo reversed
> +    3 = mono right
> +
>   Example:
>
>   codec: wm8741@1a {
>   	compatible = "wlf,wm8741";
>   	reg = <0x1a>;
> +
> +	diff-mode = <3>;
>   };
> diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
> index 9e71c76..cbf90ab 100644
> --- a/sound/soc/codecs/wm8741.c
> +++ b/sound/soc/codecs/wm8741.c
> @@ -41,6 +41,7 @@ static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
>
>   /* codec private data */
>   struct wm8741_priv {
> +	struct wm8741_platform_data pdata;
>   	struct regmap *regmap;
>   	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
>   	unsigned int sysclk;
> @@ -398,7 +399,7 @@ static struct snd_soc_dai_driver wm8741_dai = {
>   	.name = "wm8741",
>   	.playback = {
>   		.stream_name = "Playback",
> -		.channels_min = 2,  /* Mono modes not yet supported */
> +		.channels_min = 2,
>   		.channels_max = 2,
>   		.rates = WM8741_RATES,
>   		.formats = WM8741_FORMATS,
> @@ -416,6 +417,60 @@ static int wm8741_resume(struct snd_soc_codec *codec)
>   #define wm8741_resume NULL
>   #endif
>
> +static int wm8741_configure(struct snd_soc_codec *codec)
> +{
> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> +
> +	/* Configure differential mode */
> +	switch (wm8741->pdata.diff_mode) {
> +	case WM8741_DIFF_MODE_STEREO:
> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
> +	case WM8741_DIFF_MODE_MONO_LEFT:
> +	case WM8741_DIFF_MODE_MONO_RIGHT:
> +		snd_soc_update_bits(codec, WM8741_MODE_CONTROL_2,
> +				WM8741_DIFF_MASK,
> +				wm8741->pdata.diff_mode << WM8741_DIFF_SHIFT);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* Change some default settings - latch VU */
> +	snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
> +			WM8741_UPDATELL, WM8741_UPDATELL);
> +	snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
> +			WM8741_UPDATELM, WM8741_UPDATELM);
> +	snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
> +			WM8741_UPDATERL, WM8741_UPDATERL);
> +	snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION,
> +			WM8741_UPDATERM, WM8741_UPDATERM);
> +
> +	return 0;
> +}
> +
> +static int wm8741_add_controls(struct snd_soc_codec *codec)
> +{
> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> +
> +	switch (wm8741->pdata.diff_mode) {
> +	case WM8741_DIFF_MODE_STEREO:
> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
> +		snd_soc_add_codec_controls(codec, wm8741_snd_controls,
> +				ARRAY_SIZE(wm8741_snd_controls));
> +		break;
> +	case WM8741_DIFF_MODE_MONO_LEFT:
> +	case WM8741_DIFF_MODE_MONO_RIGHT:
> +		/* The machine driver is responsible for mixer controls
> +		 * if the codec is configured in differential mono mode.
> +		 */
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>   static int wm8741_probe(struct snd_soc_codec *codec)
>   {
>   	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> @@ -434,15 +489,17 @@ static int wm8741_probe(struct snd_soc_codec *codec)
>   		goto err_enable;
>   	}
>
> -	/* Change some default settings - latch VU */
> -	snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
> -			    WM8741_UPDATELL, WM8741_UPDATELL);
> -	snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
> -			    WM8741_UPDATELM, WM8741_UPDATELM);
> -	snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
> -			    WM8741_UPDATERL, WM8741_UPDATERL);
> -	snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION,
> -			    WM8741_UPDATERM, WM8741_UPDATERM);
> +	ret = wm8741_configure(codec);
> +	if (ret < 0) {
> +		dev_err(codec->dev, "Failed to change default settings\n");
> +		goto err_enable;
> +	}
> +
> +	ret = wm8741_add_controls(codec);
> +	if (ret < 0) {
> +		dev_err(codec->dev, "Failed to add controls\n");
> +		goto err_enable;
> +	}
>
>   	dev_dbg(codec->dev, "Successful registration\n");
>   	return ret;
> @@ -467,8 +524,6 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8741 = {
>   	.remove =	wm8741_remove,
>   	.resume =	wm8741_resume,
>
> -	.controls = wm8741_snd_controls,
> -	.num_controls = ARRAY_SIZE(wm8741_snd_controls),
>   	.dapm_widgets = wm8741_dapm_widgets,
>   	.num_dapm_widgets = ARRAY_SIZE(wm8741_dapm_widgets),
>   	.dapm_routes = wm8741_dapm_routes,
> @@ -493,6 +548,23 @@ static const struct regmap_config wm8741_regmap = {
>   	.readable_reg = wm8741_readable,
>   };
>
> +static int wm8741_set_pdata(struct device *dev, struct wm8741_priv *wm8741)
> +{
> +	const struct wm8741_platform_data *pdata = dev_get_platdata(dev);
> +	u32 diff_mode;
> +
> +	if (dev->of_node) {
> +		if (of_property_read_u32(dev->of_node, "diff-mode", &diff_mode)
> +				>= 0)
> +			wm8741->pdata.diff_mode = diff_mode;
> +	} else {
> +		if (pdata != NULL)
> +			memcpy(&wm8741->pdata, pdata, sizeof(wm8741->pdata));
> +	}
> +
> +	return 0;
> +}
> +
>   #if IS_ENABLED(CONFIG_I2C)
>   static int wm8741_i2c_probe(struct i2c_client *i2c,
>   			    const struct i2c_device_id *id)
> @@ -522,6 +594,12 @@ static int wm8741_i2c_probe(struct i2c_client *i2c,
>   		return ret;
>   	}
>
> +	wm8741_set_pdata(&i2c->dev, wm8741);
> +	if (ret != 0) {
> +		dev_err(&i2c->dev, "Failed to set pdata: %d\n", ret);
> +		return ret;
> +	}
> +
>   	i2c_set_clientdata(i2c, wm8741);
>
>   	ret = snd_soc_register_codec(&i2c->dev,
> @@ -582,6 +660,12 @@ static int wm8741_spi_probe(struct spi_device *spi)
>   		return ret;
>   	}
>
> +	wm8741_set_pdata(&spi->dev, wm8741);
> +	if (ret != 0) {
> +		dev_err(&spi->dev, "Failed to set pdata: %d\n", ret);
> +		return ret;
> +	}
> +
>   	spi_set_drvdata(spi, wm8741);
>
>   	ret = snd_soc_register_codec(&spi->dev,
> diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h
> index 56c1b1d..c8835f6 100644
> --- a/sound/soc/codecs/wm8741.h
> +++ b/sound/soc/codecs/wm8741.h
> @@ -194,6 +194,12 @@
>   #define WM8741_DITHER_SHIFT                          0  /* DITHER - [1:0] */
>   #define WM8741_DITHER_WIDTH                          2  /* DITHER - [1:0] */
>
> +/* DIFF field values */
> +#define WM8741_DIFF_MODE_STEREO                      0  /* stereo normal */
> +#define WM8741_DIFF_MODE_STEREO_REVERSED             2  /* stereo reversed */
> +#define WM8741_DIFF_MODE_MONO_LEFT                   1  /* mono left */
> +#define WM8741_DIFF_MODE_MONO_RIGHT                  3  /* mono right */
> +
>   /*
>    * R32 (0x20) - ADDITONAL_CONTROL_1
>    */
> @@ -208,4 +214,8 @@
>
>   #define  WM8741_SYSCLK 0
>
> +struct wm8741_platform_data {
> +	u32 diff_mode;   /* Differential Output Mode */
> +};
> +
>   #endif
>

Gentle ping.

Best regards,
Sergej
Charles Keepax May 11, 2015, 8:12 a.m. UTC | #2
On Tue, May 05, 2015 at 10:46:31PM +0200, Sergej Sawazki wrote:
> The WM8741 DAC supports several differential output modes (stereo,
> stereo reversed, mono left, mono right). Add platform data and DT
> bindings to configure it.
> 
> Signed-off-by: Sergej Sawazki <ce3a@gmx.de>
> ---
>  Documentation/devicetree/bindings/sound/wm8741.txt |  11 +++
>  sound/soc/codecs/wm8741.c                          | 108 ++++++++++++++++++---
>  sound/soc/codecs/wm8741.h                          |  10 ++
>  3 files changed, 117 insertions(+), 12 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/sound/wm8741.txt b/Documentation/devicetree/bindings/sound/wm8741.txt
> index 74bda58..a133154 100644
> --- a/Documentation/devicetree/bindings/sound/wm8741.txt
> +++ b/Documentation/devicetree/bindings/sound/wm8741.txt
> @@ -10,9 +10,20 @@ Required properties:
>    - reg : the I2C address of the device for I2C, the chip select
>            number for SPI.
>  
> +Optional properties:
> +
> +  - diff-mode: Differential output mode configuration. Default value for field
> +    DIFF in register R8 (MODE_CONTROL_2). If absent, the default is 0, shall be:
> +    0 = stereo
> +    1 = mono left
> +    2 = stereo reversed
> +    3 = mono right
> +
>  Example:
>  
>  codec: wm8741@1a {
>  	compatible = "wlf,wm8741";
>  	reg = <0x1a>;
> +
> +	diff-mode = <3>;
>  };
> diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
> index 9e71c76..cbf90ab 100644
> --- a/sound/soc/codecs/wm8741.c
> +++ b/sound/soc/codecs/wm8741.c
> @@ -41,6 +41,7 @@ static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
>  
>  /* codec private data */
>  struct wm8741_priv {
> +	struct wm8741_platform_data pdata;
>  	struct regmap *regmap;
>  	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
>  	unsigned int sysclk;
> @@ -398,7 +399,7 @@ static struct snd_soc_dai_driver wm8741_dai = {
>  	.name = "wm8741",
>  	.playback = {
>  		.stream_name = "Playback",
> -		.channels_min = 2,  /* Mono modes not yet supported */
> +		.channels_min = 2,
>  		.channels_max = 2,
>  		.rates = WM8741_RATES,
>  		.formats = WM8741_FORMATS,
> @@ -416,6 +417,60 @@ static int wm8741_resume(struct snd_soc_codec *codec)
>  #define wm8741_resume NULL
>  #endif
>  
> +static int wm8741_configure(struct snd_soc_codec *codec)
> +{
> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> +
> +	/* Configure differential mode */
> +	switch (wm8741->pdata.diff_mode) {
> +	case WM8741_DIFF_MODE_STEREO:
> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
> +	case WM8741_DIFF_MODE_MONO_LEFT:
> +	case WM8741_DIFF_MODE_MONO_RIGHT:
> +		snd_soc_update_bits(codec, WM8741_MODE_CONTROL_2,
> +				WM8741_DIFF_MASK,
> +				wm8741->pdata.diff_mode << WM8741_DIFF_SHIFT);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* Change some default settings - latch VU */
> +	snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
> +			WM8741_UPDATELL, WM8741_UPDATELL);
> +	snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
> +			WM8741_UPDATELM, WM8741_UPDATELM);
> +	snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
> +			WM8741_UPDATERL, WM8741_UPDATERL);
> +	snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION,
> +			WM8741_UPDATERM, WM8741_UPDATERM);
> +
> +	return 0;
> +}
> +
> +static int wm8741_add_controls(struct snd_soc_codec *codec)
> +{
> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> +
> +	switch (wm8741->pdata.diff_mode) {
> +	case WM8741_DIFF_MODE_STEREO:
> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
> +		snd_soc_add_codec_controls(codec, wm8741_snd_controls,
> +				ARRAY_SIZE(wm8741_snd_controls));
> +		break;
> +	case WM8741_DIFF_MODE_MONO_LEFT:
> +	case WM8741_DIFF_MODE_MONO_RIGHT:
> +		/* The machine driver is responsible for mixer controls
> +		 * if the codec is configured in differential mono mode.
> +		 */

Would it not be better to add controls but with a channel neutral
name and then the machine driver can use the name_prefix stuff to
stick left and right onto them? Seems a bit odd for the machine
driver to have to know exact register details of the CODEC and
manually add the volume controls?

Thanks,
Charles
Sergej Sawazki May 11, 2015, 9:12 a.m. UTC | #3
On 11.05.2015 at 10:12, Charles Keepax wrote:
> On Tue, May 05, 2015 at 10:46:31PM +0200, Sergej Sawazki wrote:
>> +static int wm8741_add_controls(struct snd_soc_codec *codec)
>> +{
>> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
>> +
>> +	switch (wm8741->pdata.diff_mode) {
>> +	case WM8741_DIFF_MODE_STEREO:
>> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
>> +		snd_soc_add_codec_controls(codec, wm8741_snd_controls,
>> +				ARRAY_SIZE(wm8741_snd_controls));
>> +		break;
>> +	case WM8741_DIFF_MODE_MONO_LEFT:
>> +	case WM8741_DIFF_MODE_MONO_RIGHT:
>> +		/* The machine driver is responsible for mixer controls
>> +		 * if the codec is configured in differential mono mode.
>> +		 */
> Would it not be better to add controls but with a channel neutral
> name and then the machine driver can use the name_prefix stuff to
> stick left and right onto them? Seems a bit odd for the machine
> driver to have to know exact register details of the CODEC and
> manually add the volume controls?
>
> Thanks,
> Charles

Charles, could you point me to an "name_prefix" example please?

Thanks,
Sergej
Charles Keepax May 11, 2015, 11:50 a.m. UTC | #4
On Mon, May 11, 2015 at 11:12:00AM +0200, Sergej Sawazki wrote:
> On 11.05.2015 at 10:12, Charles Keepax wrote:
> > On Tue, May 05, 2015 at 10:46:31PM +0200, Sergej Sawazki wrote:
> >> +static int wm8741_add_controls(struct snd_soc_codec *codec)
> >> +{
> >> +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
> >> +
> >> +	switch (wm8741->pdata.diff_mode) {
> >> +	case WM8741_DIFF_MODE_STEREO:
> >> +	case WM8741_DIFF_MODE_STEREO_REVERSED:
> >> +		snd_soc_add_codec_controls(codec, wm8741_snd_controls,
> >> +				ARRAY_SIZE(wm8741_snd_controls));
> >> +		break;
> >> +	case WM8741_DIFF_MODE_MONO_LEFT:
> >> +	case WM8741_DIFF_MODE_MONO_RIGHT:
> >> +		/* The machine driver is responsible for mixer controls
> >> +		 * if the codec is configured in differential mono mode.
> >> +		 */
> > Would it not be better to add controls but with a channel neutral
> > name and then the machine driver can use the name_prefix stuff to
> > stick left and right onto them? Seems a bit odd for the machine
> > driver to have to know exact register details of the CODEC and
> > manually add the volume controls?
> >
> > Thanks,
> > Charles
> 
> Charles, could you point me to an "name_prefix" example please?
> 
> Thanks,
> Sergej
> 

The wm9081's handling in sound/soc/samsung/bells.c would be a
reasonable example.

Thanks,
Charles
Mark Brown May 11, 2015, 5:21 p.m. UTC | #5
On Sun, May 10, 2015 at 12:23:28PM +0200, Sergej Sawazki wrote:
> On 05.05.2015 22:46, Sergej Sawazki wrote:

> >+struct wm8741_platform_data {
> >+	u32 diff_mode;   /* Differential Output Mode */
> >+};
> >+
> >  #endif

> Gentle ping.

Don't send content free pings, *especially* not content free pings
quoting the entire message with the text at the bottom of the mail.
Content free pings just add to e-mail volume and waste everyone's time.

It's been less than a week since you sent your patch, you need to allow
a reasonable amount of time in case people are busy, on holiday or
something.
Sergej Sawazki May 11, 2015, 8:27 p.m. UTC | #6
Am 11.05.2015 um 19:21 schrieb Mark Brown:
> On Sun, May 10, 2015 at 12:23:28PM +0200, Sergej Sawazki wrote:
>> On 05.05.2015 22:46, Sergej Sawazki wrote:
>
>>> +struct wm8741_platform_data {
>>> +	u32 diff_mode;   /* Differential Output Mode */
>>> +};
>>> +
>>>   #endif
>
>> Gentle ping.
>
> Don't send content free pings, *especially* not content free pings
> quoting the entire message with the text at the bottom of the mail.
> Content free pings just add to e-mail volume and waste everyone's time.
>
> It's been less than a week since you sent your patch, you need to allow
> a reasonable amount of time in case people are busy, on holiday or
> something.
>

Mark,
you are right. I am sorry and I promise to improve.
Thanks for your answer, nevertheless.
Sergej

Patch
diff mbox

diff --git a/Documentation/devicetree/bindings/sound/wm8741.txt b/Documentation/devicetree/bindings/sound/wm8741.txt
index 74bda58..a133154 100644
--- a/Documentation/devicetree/bindings/sound/wm8741.txt
+++ b/Documentation/devicetree/bindings/sound/wm8741.txt
@@ -10,9 +10,20 @@  Required properties:
   - reg : the I2C address of the device for I2C, the chip select
           number for SPI.
 
+Optional properties:
+
+  - diff-mode: Differential output mode configuration. Default value for field
+    DIFF in register R8 (MODE_CONTROL_2). If absent, the default is 0, shall be:
+    0 = stereo
+    1 = mono left
+    2 = stereo reversed
+    3 = mono right
+
 Example:
 
 codec: wm8741@1a {
 	compatible = "wlf,wm8741";
 	reg = <0x1a>;
+
+	diff-mode = <3>;
 };
diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
index 9e71c76..cbf90ab 100644
--- a/sound/soc/codecs/wm8741.c
+++ b/sound/soc/codecs/wm8741.c
@@ -41,6 +41,7 @@  static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
 
 /* codec private data */
 struct wm8741_priv {
+	struct wm8741_platform_data pdata;
 	struct regmap *regmap;
 	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
 	unsigned int sysclk;
@@ -398,7 +399,7 @@  static struct snd_soc_dai_driver wm8741_dai = {
 	.name = "wm8741",
 	.playback = {
 		.stream_name = "Playback",
-		.channels_min = 2,  /* Mono modes not yet supported */
+		.channels_min = 2,
 		.channels_max = 2,
 		.rates = WM8741_RATES,
 		.formats = WM8741_FORMATS,
@@ -416,6 +417,60 @@  static int wm8741_resume(struct snd_soc_codec *codec)
 #define wm8741_resume NULL
 #endif
 
+static int wm8741_configure(struct snd_soc_codec *codec)
+{
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+	/* Configure differential mode */
+	switch (wm8741->pdata.diff_mode) {
+	case WM8741_DIFF_MODE_STEREO:
+	case WM8741_DIFF_MODE_STEREO_REVERSED:
+	case WM8741_DIFF_MODE_MONO_LEFT:
+	case WM8741_DIFF_MODE_MONO_RIGHT:
+		snd_soc_update_bits(codec, WM8741_MODE_CONTROL_2,
+				WM8741_DIFF_MASK,
+				wm8741->pdata.diff_mode << WM8741_DIFF_SHIFT);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Change some default settings - latch VU */
+	snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
+			WM8741_UPDATELL, WM8741_UPDATELL);
+	snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
+			WM8741_UPDATELM, WM8741_UPDATELM);
+	snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
+			WM8741_UPDATERL, WM8741_UPDATERL);
+	snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION,
+			WM8741_UPDATERM, WM8741_UPDATERM);
+
+	return 0;
+}
+
+static int wm8741_add_controls(struct snd_soc_codec *codec)
+{
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+	switch (wm8741->pdata.diff_mode) {
+	case WM8741_DIFF_MODE_STEREO:
+	case WM8741_DIFF_MODE_STEREO_REVERSED:
+		snd_soc_add_codec_controls(codec, wm8741_snd_controls,
+				ARRAY_SIZE(wm8741_snd_controls));
+		break;
+	case WM8741_DIFF_MODE_MONO_LEFT:
+	case WM8741_DIFF_MODE_MONO_RIGHT:
+		/* The machine driver is responsible for mixer controls
+		 * if the codec is configured in differential mono mode.
+		 */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int wm8741_probe(struct snd_soc_codec *codec)
 {
 	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
@@ -434,15 +489,17 @@  static int wm8741_probe(struct snd_soc_codec *codec)
 		goto err_enable;
 	}
 
-	/* Change some default settings - latch VU */
-	snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
-			    WM8741_UPDATELL, WM8741_UPDATELL);
-	snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
-			    WM8741_UPDATELM, WM8741_UPDATELM);
-	snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
-			    WM8741_UPDATERL, WM8741_UPDATERL);
-	snd_soc_update_bits(codec, WM8741_DACRMSB_ATTENUATION,
-			    WM8741_UPDATERM, WM8741_UPDATERM);
+	ret = wm8741_configure(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to change default settings\n");
+		goto err_enable;
+	}
+
+	ret = wm8741_add_controls(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to add controls\n");
+		goto err_enable;
+	}
 
 	dev_dbg(codec->dev, "Successful registration\n");
 	return ret;
@@ -467,8 +524,6 @@  static struct snd_soc_codec_driver soc_codec_dev_wm8741 = {
 	.remove =	wm8741_remove,
 	.resume =	wm8741_resume,
 
-	.controls = wm8741_snd_controls,
-	.num_controls = ARRAY_SIZE(wm8741_snd_controls),
 	.dapm_widgets = wm8741_dapm_widgets,
 	.num_dapm_widgets = ARRAY_SIZE(wm8741_dapm_widgets),
 	.dapm_routes = wm8741_dapm_routes,
@@ -493,6 +548,23 @@  static const struct regmap_config wm8741_regmap = {
 	.readable_reg = wm8741_readable,
 };
 
+static int wm8741_set_pdata(struct device *dev, struct wm8741_priv *wm8741)
+{
+	const struct wm8741_platform_data *pdata = dev_get_platdata(dev);
+	u32 diff_mode;
+
+	if (dev->of_node) {
+		if (of_property_read_u32(dev->of_node, "diff-mode", &diff_mode)
+				>= 0)
+			wm8741->pdata.diff_mode = diff_mode;
+	} else {
+		if (pdata != NULL)
+			memcpy(&wm8741->pdata, pdata, sizeof(wm8741->pdata));
+	}
+
+	return 0;
+}
+
 #if IS_ENABLED(CONFIG_I2C)
 static int wm8741_i2c_probe(struct i2c_client *i2c,
 			    const struct i2c_device_id *id)
@@ -522,6 +594,12 @@  static int wm8741_i2c_probe(struct i2c_client *i2c,
 		return ret;
 	}
 
+	wm8741_set_pdata(&i2c->dev, wm8741);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to set pdata: %d\n", ret);
+		return ret;
+	}
+
 	i2c_set_clientdata(i2c, wm8741);
 
 	ret = snd_soc_register_codec(&i2c->dev,
@@ -582,6 +660,12 @@  static int wm8741_spi_probe(struct spi_device *spi)
 		return ret;
 	}
 
+	wm8741_set_pdata(&spi->dev, wm8741);
+	if (ret != 0) {
+		dev_err(&spi->dev, "Failed to set pdata: %d\n", ret);
+		return ret;
+	}
+
 	spi_set_drvdata(spi, wm8741);
 
 	ret = snd_soc_register_codec(&spi->dev,
diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h
index 56c1b1d..c8835f6 100644
--- a/sound/soc/codecs/wm8741.h
+++ b/sound/soc/codecs/wm8741.h
@@ -194,6 +194,12 @@ 
 #define WM8741_DITHER_SHIFT                          0  /* DITHER - [1:0] */
 #define WM8741_DITHER_WIDTH                          2  /* DITHER - [1:0] */
 
+/* DIFF field values */
+#define WM8741_DIFF_MODE_STEREO                      0  /* stereo normal */
+#define WM8741_DIFF_MODE_STEREO_REVERSED             2  /* stereo reversed */
+#define WM8741_DIFF_MODE_MONO_LEFT                   1  /* mono left */
+#define WM8741_DIFF_MODE_MONO_RIGHT                  3  /* mono right */
+
 /*
  * R32 (0x20) - ADDITONAL_CONTROL_1
  */
@@ -208,4 +214,8 @@ 
 
 #define  WM8741_SYSCLK 0
 
+struct wm8741_platform_data {
+	u32 diff_mode;   /* Differential Output Mode */
+};
+
 #endif