[v1,1/4] ASoc: pcm6240: Create pcm6240 codec driver code

Message ID 20240123111411.850-1-shenghao-ding@ti.com
State New
Headers
Series [v1,1/4] ASoc: pcm6240: Create pcm6240 codec driver code |

Commit Message

Ding, Shenghao Jan. 23, 2024, 11:14 a.m. UTC
  PCM6240 driver implements a flexible and configurable setting for register
and filter coefficients, to one, two or even multiple PCM6240 Family Audio
chips.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

Change in v1:
 - Create pcm6240 codec driver code
---
 sound/soc/codecs/pcm6240.c | 2062 ++++++++++++++++++++++++++++++++++++
 1 file changed, 2062 insertions(+)
 create mode 100644 sound/soc/codecs/pcm6240.c
  

Comments

Krzysztof Kozlowski Jan. 23, 2024, 11:25 a.m. UTC | #1
On 23/01/2024 12:14, Shenghao Ding wrote:
> PCM6240 driver implements a flexible and configurable setting for register
> and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> chips.
> 


> Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>

Subject: ASoC
Subject: rewrite to match something similar to other commits.

> 
> ---
> Change in v1:
>  - Create yaml file for pcm6240 codec driver

I don't understand. v1 is the first version. Against what is this change?

> ---
>  .../devicetree/bindings/sound/ti,pcm6240.yaml | 303 ++++++++++++++++++
>  1 file changed, 303 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> 
> diff --git a/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> new file mode 100644
> index 000000000000..59fd48aa4445
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> @@ -0,0 +1,303 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (C) 2022 - 2024 Texas Instruments Incorporated
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/ti,pcm6240.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments PCM6240 Family Audio ADC/DAC/Router
> +
> +maintainers:
> +  - Shenghao Ding <shenghao-ding@ti.com>
> +
> +description: |
> +  The PCM6240 Family driver offer a flexible architecture to set the device

Describe hardware, not Linux driver.

> +  number, registers and params for different filters in a bin file.

I don't understand entire sentence in context of hardware.

> +
> +  Specifications about the audio chip can be found at:
> +    https://www.ti.com/lit/gpn/tlv320adc3120
> +    https://www.ti.com/lit/gpn/tlv320adc5120
> +    https://www.ti.com/lit/gpn/tlv320adc6120
> +    https://www.ti.com/lit/gpn/dix4192
> +    https://www.ti.com/lit/gpn/pcm1690
> +    https://www.ti.com/lit/gpn/pcm3120-q1
> +    https://www.ti.com/lit/gpn/pcm3140-q1
> +    https://www.ti.com/lit/gpn/pcm5120-q1
> +    https://www.ti.com/lit/gpn/pcm6120-q1
> +    https://www.ti.com/lit/gpn/pcm6260-q1
> +    https://www.ti.com/lit/gpn/pcm9211
> +    https://www.ti.com/lit/gpn/pcmd3140
> +    https://www.ti.com/lit/gpn/pcmd3180
> +    https://www.ti.com/lit/gpn/taa5212
> +    https://www.ti.com/lit/gpn/tad5212
> +
> +properties:
> +  compatible:
> +    description: |
> +      ti,adc3120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
> +      digital converter (ADC) with 106-dB SNR.
> +
> +      ti,adc5120: 2-Channel, 768-kHz, Burr-BrownTM Audio ADC with 120-dB SNR.
> +
> +      ti,adc6120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
> +      digital converter (ADC) with 123-dB SNR.
> +
> +      ti,pcm1690: 113dB SNR, 24-Bit, 192-kHz Sampling, Enhanced Multi-Level
> +      ?S, Eight-Channel Audio Digital-to-Analog Converter with Differential
> +      Outputs.
> +
> +      ti,pcm3120: Automotive, stereo, 106-dB SNR, 768-kHz, low-power
> +      software-controlled audio ADC.
> +
> +      ti,pcm3140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
> +      with 106-dB SNR.
> +
> +      ti,pcm5120: Automotive, stereo, 120-dB SNR, 768-kHz, low-power
> +      software-controlled audio ADC.
> +
> +      ti,pcm5140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
> +      with 120-dB SNR.
> +
> +      ti,pcm6120: Automotive, stereo, 123-dB SNR, 768-kHz, low-power
> +      software-controlled audio ADC.
> +
> +      ti,pcm6140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM Audio ADC
> +      with 123-dB SNR.
> +
> +      ti,pcm6240: Automotive 4-ch audio ADC with integrated programmable mic
> +      bias, boost and input diagnostics.
> +
> +      ti,pcm6260: Automotive 6-ch audio ADC with integrated programmable mic
> +      bias, boost and input diagnostics.
> +
> +      ti,pcm9211: 216-kHz Digital Audio Interface Transceiver (DIX)
> +      With Stereo ADC and Routing.
> +
> +      ti,pcmd3140: Four-channel PDM-input to TDM or I�S output converter.
> +
> +      ti,pcmd3180: Eight-channel pulse-density-modulation input to TDM or
> +      I�S output converter.
> +
> +      ti,taa5212: Low-power high-performance stereo audio ADC with 118-dB
> +      dynamic range.
> +
> +      ti,tad5212: Low-power stereo audio DAC with 120-dB dynamic range.
> +    enum:
> +      - ti,adc3120
> +      - ti,adc5120
> +      - ti,adc6120
> +      - ti,dix4192
> +      - ti,pcm1690
> +      - ti,pcm3120
> +      - ti,pcm3140
> +      - ti,pcm5120
> +      - ti,pcm5140
> +      - ti,pcm6120
> +      - ti,pcm6140
> +      - ti,pcm6240
> +      - ti,pcm6260
> +      - ti,pcm9211
> +      - ti,pcmd3140
> +      - ti,pcmd3180
> +      - ti,pcmd512x
> +      - ti,taa5212
> +      - ti,taa5412
> +      - ti,tad5212
> +      - ti,tad5412

And none of them are compatible with something?

> +
> +  reg:
> +    description:
> +      I2C address, in multiple pcmdevices case, all the i2c address
> +      aggregate as one Audio Device to support multiple audio slots.
> +    maxItems: 4
> +    minItems: 1

minItems, then maxItems.

Please open existing bindings and look how it is done there.


> +
> +  reset-gpios:
> +    maxItems: 1
> +    description:
> +      A GPIO line handling reset of the chip. As the line is active high,
> +      it should be marked GPIO_ACTIVE_HIGH.

Drop description, it's obvious. Or write something useful.

> +
> +  interrupts:
> +    maxItems: 1
> +    description:
> +      Invalid only for ti,pcm1690 because of no INT pin.
> +
> +  '#sound-dai-cells':
> +    const: 0
> +
> +required:
> +  - compatible
> +  - reg
> +
> +allOf:
> +  - $ref: dai-common.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm1690
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1
> +          items:
> +            minimum: 0x4c
> +            maximum: 0x4f

Why do you repeat the reg constraints? This does not seem needed.

> +        interrupts: false
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm3140
> +              - ti,pcm5140
> +              - ti,pcm6140
> +              - ti,pcmd3180
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1

Drop entire if

> +          items:
> +            minimum: 0x4c
> +            maximum: 0x4f
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,adc3120
> +              - ti,adc5120
> +              - ti,adc6120
> +              - ti,pcm3120
> +              - ti,pcm5120
> +              - ti,pcm6120
> +              - ti,pcmd3140
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address.

Just drop description, it is obvious.

> +          maxItems: 1
> +          items:
> +            maximum: 0x4e
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,dix4192
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1
> +          items:
> +            minimum: 0x70
> +            maximum: 0x73
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm6240
> +              - ti,pcm6260
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1
> +          items:
> +            minimum: 0x48
> +            maximum: 0x4b
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,pcm9211
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1
> +          items:
> +            minimum: 0x40
> +            maximum: 0x43
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - ti,taa5212
> +              - ti,taa5412
> +              - ti,tad5212
> +              - ti,tad5412
> +    then:
> +      properties:
> +        reg:
> +          description:
> +            I2C address, in multiple pcmdevices case, all the i2c address
> +            aggregate as one Audio Device to support multiple audio slots.
> +          maxItems: 4
> +          minItems: 1
> +          items:
> +            minimum: 0x50
> +            maximum: 0x53
> +additionalProperties: false
> +
> +examples:
> +  - |
> +   #include <dt-bindings/gpio/gpio.h>
> +   i2c {
> +     /* example for two devices with interrupt support */
> +     #address-cells = <1>;
> +     #size-cells = <0>;
> +     two: pcmdevice@48 {

Node names should be generic. See also an explanation and list of
examples (not exhaustive) in DT specification:
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation


> +       compatible = "ti,pcm6240";
> +       reg = <0x48>, /* primary-device */
> +            <0x4b>; /* secondary-device */
> +       #sound-dai-cells = <0>;
> +       reset-gpios = < &gpio1 10 GPIO_ACTIVE_HIGH >;

Drop redundant spaces.



Best regards,
Krzysztof
  
Krzysztof Kozlowski Jan. 23, 2024, 11:25 a.m. UTC | #2
On 23/01/2024 12:14, Shenghao Ding wrote:
> PCM6240 driver implements a flexible and configurable setting for register
> and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> chips.
> 
> Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>
> 
> ---
> Change in v1:
>  - Add compile item for pcm6240 codec driver

So before you added dead code? No, please add a working code, so squash
the patches.

Best regards,
Krzysztof
  
Mark Brown Jan. 23, 2024, 3:01 p.m. UTC | #3
On Tue, Jan 23, 2024 at 12:25:04PM +0100, Krzysztof Kozlowski wrote:
> On 23/01/2024 12:14, Shenghao Ding wrote:

> > ---
> > Change in v1:
> >  - Create yaml file for pcm6240 codec driver

> I don't understand. v1 is the first version. Against what is this change?

This appears to be a perfectly clear description of the contents of the
first version, it's a change against the tree before the patch is
applied.  It's a bit unusual to include a per version changelog on the
first version but not a problem.

> > +    enum:
> > +      - ti,adc3120
> > +      - ti,adc5120
> > +      - ti,adc6120
> > +      - ti,dix4192
> > +      - ti,pcm1690
> > +      - ti,pcm3120
> > +      - ti,pcm3140
> > +      - ti,pcm5120
> > +      - ti,pcm5140
> > +      - ti,pcm6120
> > +      - ti,pcm6140
> > +      - ti,pcm6240
> > +      - ti,pcm6260
> > +      - ti,pcm9211
> > +      - ti,pcmd3140
> > +      - ti,pcmd3180
> > +      - ti,pcmd512x
> > +      - ti,taa5212
> > +      - ti,taa5412
> > +      - ti,tad5212
> > +      - ti,tad5412

> And none of them are compatible with something?

No idea about these specific chips but that would be entirely normal for
CODECs, even where things are subsets there's often some tweaks needed
to initialisation or whatever.

> > +     two: pcmdevice@48 {

> Node names should be generic. See also an explanation and list of
> examples (not exhaustive) in DT specification:
> https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation

Please be more specific about what you're looking to see there.
pcmdevice doesn't seem particularly more specific than something like
dsp, it certainly seems within what the text describes.
  
Krzysztof Kozlowski Jan. 23, 2024, 3:04 p.m. UTC | #4
On 23/01/2024 16:01, Mark Brown wrote:
> On Tue, Jan 23, 2024 at 12:25:04PM +0100, Krzysztof Kozlowski wrote:
>> On 23/01/2024 12:14, Shenghao Ding wrote:
> 
>>> ---
>>> Change in v1:
>>>  - Create yaml file for pcm6240 codec driver
> 
>> I don't understand. v1 is the first version. Against what is this change?
> 
> This appears to be a perfectly clear description of the contents of the
> first version, it's a change against the tree before the patch is
> applied.  It's a bit unusual to include a per version changelog on the
> first version but not a problem.
> 
>>> +    enum:
>>> +      - ti,adc3120
>>> +      - ti,adc5120
>>> +      - ti,adc6120
>>> +      - ti,dix4192
>>> +      - ti,pcm1690
>>> +      - ti,pcm3120
>>> +      - ti,pcm3140
>>> +      - ti,pcm5120
>>> +      - ti,pcm5140
>>> +      - ti,pcm6120
>>> +      - ti,pcm6140
>>> +      - ti,pcm6240
>>> +      - ti,pcm6260
>>> +      - ti,pcm9211
>>> +      - ti,pcmd3140
>>> +      - ti,pcmd3180
>>> +      - ti,pcmd512x
>>> +      - ti,taa5212
>>> +      - ti,taa5412
>>> +      - ti,tad5212
>>> +      - ti,tad5412
> 
>> And none of them are compatible with something?
> 
> No idea about these specific chips but that would be entirely normal for
> CODECs, even where things are subsets there's often some tweaks needed
> to initialisation or whatever.

I want to double check with the author.

> 
>>> +     two: pcmdevice@48 {
> 
>> Node names should be generic. See also an explanation and list of
>> examples (not exhaustive) in DT specification:
>> https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
> 
> Please be more specific about what you're looking to see there.
> pcmdevice doesn't seem particularly more specific than something like
> dsp, it certainly seems within what the text describes.

pcm, codec, audio-codec
"device" seems redundant, because almost everything is some sort of device.

Best regards,
Krzysztof
  
Mark Brown Jan. 23, 2024, 7:13 p.m. UTC | #5
On Tue, Jan 23, 2024 at 12:25:33PM +0100, Krzysztof Kozlowski wrote:
> On 23/01/2024 12:14, Shenghao Ding wrote:
> > PCM6240 driver implements a flexible and configurable setting for register
> > and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> > chips.

> So before you added dead code? No, please add a working code, so squash
> the patches.

This is a fairly normal way of adding completely new files that are
split into multiple commits for whatever reason, and given that the
main C file is 2600 lines and the header another couple of hundred I'm
not going to object to something that makes them a bit easier to digest.
This is especially true for a driver like this which is handling some
hardware that's a bit interesting and therefore has more complicated
code, it's not all big data tables like many ASoC drivers.

It'd be even nicer to have things done a bit more incrementally in
logical blocks but every little helps.
  
Ding, Shenghao Jan. 24, 2024, 10:10 a.m. UTC | #6
> -----Original Message-----
> From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Sent: Tuesday, January 23, 2024 11:04 PM
> To: Mark Brown <broonie@kernel.org>
> Cc: Ding, Shenghao <shenghao-ding@ti.com>; conor+dt@kernel.org;
> robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin <kevin-
> lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>; devicetree@vger.kernel.org;
> lgirdwood@gmail.com; perex@perex.cz; pierre-
> louis.bossart@linux.intel.com; 13916275206@139.com; linux-
> sound@vger.kernel.org; linux-kernel@vger.kernel.org;
> liam.r.girdwood@intel.com; soyer@irl.hu; Huang, Jonathan
> <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter <pdjuandi@ti.com>;
> McPherson, Jeff <j-mcpherson@ti.com>; Navada Kanyana, Mukund
> <navada@ti.com>
> Subject: [EXTERNAL] Re: [PATCH v1 4/4] ASoc: dt-bindings: Create yaml file
> for pcm6240 codec driver
> 
> On 23/01/2024 16: 01, Mark Brown wrote: > On Tue, Jan 23, 2024 at
> 12: 25: 04PM +0100, Krzysztof Kozlowski wrote: >> On 23/01/2024 12: 14,
> Shenghao Ding wrote: > >>> --- >>> Change in v1: >>> - Create yaml
> ZjQcmQRYFpfptBannerStart This Message Is From an External Sender This
> message came from outside your organization.
> 
> ZjQcmQRYFpfptBannerEnd
> On 23/01/2024 16:01, Mark Brown wrote:
> > On Tue, Jan 23, 2024 at 12:25:04PM +0100, Krzysztof Kozlowski wrote:
> >> On 23/01/2024 12:14, Shenghao Ding wrote:
> >
> >>> ---
> >>> Change in v1:
> >>>  - Create yaml file for pcm6240 codec driver
> >
> >> I don't understand. v1 is the first version. Against what is this change?
> >
> > This appears to be a perfectly clear description of the contents of
> > the first version, it's a change against the tree before the patch is
> > applied.  It's a bit unusual to include a per version changelog on the
> > first version but not a problem.
> >
> >>> +    enum:
> >>> +      - ti,adc3120
> >>> +      - ti,adc5120
> >>> +      - ti,adc6120
> >>> +      - ti,dix4192
> >>> +      - ti,pcm1690
> >>> +      - ti,pcm3120
> >>> +      - ti,pcm3140
> >>> +      - ti,pcm5120
> >>> +      - ti,pcm5140
> >>> +      - ti,pcm6120
> >>> +      - ti,pcm6140
> >>> +      - ti,pcm6240
> >>> +      - ti,pcm6260
> >>> +      - ti,pcm9211
> >>> +      - ti,pcmd3140
> >>> +      - ti,pcmd3180
> >>> +      - ti,pcmd512x
> >>> +      - ti,taa5212
> >>> +      - ti,taa5412
> >>> +      - ti,tad5212
> >>> +      - ti,tad5412
> >
> >> And none of them are compatible with something?
> >
> > No idea about these specific chips but that would be entirely normal
> > for CODECs, even where things are subsets there's often some tweaks
> > needed to initialisation or whatever.
> 
> I want to double check with the author.
> 
All these chips have only a small feature of codec, such as ADC or DAC,
but their audio performance is far superior to the codec's, and cost is lower 
than codec, and easier to program than codec.
Simply one or two register settings can enable them to work. Init for 
these chips are hardware reset or software reset.
As to some audio filter params for internal filters, it is up to the special 
user cases, which can be saved into the bin file.  The default value also 
can work well.
> >
> >>> +     two: pcmdevice@48 {
> >
> >> Node names should be generic. See also an explanation and list of
> >> examples (not exhaustive) in DT specification:
> >> https://urldefense.com/v3/__https://devicetree-specification.readthed
> >> ocs.io/en/latest/chapter2-devicetree-basics.html*generic-names-
> recomm
> >>
> endation__;Iw!!G3vK!UaR7OlVhIZucD0CMyBe4iiYyWCl4k_ExXJ6HHG3Fz3qy4
> OPWH
> >> esNTs-57iZ8MjCUYfaxVLq8OOGOuOYvkNsth-H6y1cO$
> >
> > Please be more specific about what you're looking to see there.
> > pcmdevice doesn't seem particularly more specific than something like
> > dsp, it certainly seems within what the text describes.
> 
> pcm, codec, audio-codec
> "device" seems redundant, because almost everything is some sort of device.
> 
> Best regards,
> Krzysztof
  
Ding, Shenghao Jan. 25, 2024, 7:39 a.m. UTC | #7
> -----Original Message-----
> From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Sent: Tuesday, January 23, 2024 7:25 PM
> To: Ding, Shenghao <shenghao-ding@ti.com>; broonie@kernel.org;
> conor+dt@kernel.org
> Cc: robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin
> <kevin-lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>;
> devicetree@vger.kernel.org; lgirdwood@gmail.com; perex@perex.cz;
> pierre-louis.bossart@linux.intel.com; 13916275206@139.com; linux-
> sound@vger.kernel.org; linux-kernel@vger.kernel.org;
> liam.r.girdwood@intel.com; soyer@irl.hu; Huang, Jonathan
> <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter <pdjuandi@ti.com>;
> McPherson, Jeff <j-mcpherson@ti.com>; Navada Kanyana, Mukund
> <navada@ti.com>
> Subject: [EXTERNAL] Re: [PATCH v1 4/4] ASoc: dt-bindings: Create yaml file
> for pcm6240 codec driver
> 
> On 23/01/2024 12: 14, Shenghao Ding wrote: > PCM6240 driver implements
> a flexible and configurable setting for register > and filter coefficients, to one,
> two or even multiple PCM6240 Family Audio > chips. > > Signed-off-by:
> Shenghao ZjQcmQRYFpfptBannerStart This Message Is From an External
> Sender This message came from outside your organization.
> 
> ZjQcmQRYFpfptBannerEnd
> On 23/01/2024 12:14, Shenghao Ding wrote:
> > PCM6240 driver implements a flexible and configurable setting for
> > register and filter coefficients, to one, two or even multiple PCM6240
> > Family Audio chips.
> >
> 
> 
> > Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>
> 
> Subject: ASoC
> Subject: rewrite to match something similar to other commits.
> 
> >
> > ---
> > Change in v1:
> >  - Create yaml file for pcm6240 codec driver
> 
> I don't understand. v1 is the first version. Against what is this change?
> 
> > ---
> >  .../devicetree/bindings/sound/ti,pcm6240.yaml | 303
> > ++++++++++++++++++
> >  1 file changed, 303 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> > b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> > new file mode 100644
> > index 000000000000..59fd48aa4445
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/sound/ti,pcm6240.yaml
> > @@ -0,0 +1,303 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) # Copyright
> > +(C) 2022 - 2024 Texas Instruments Incorporated %YAML 1.2
> > +---
> > +$id:
> > +https://urldefense.com/v3/__http://devicetree.org/schemas/sound/ti,pc
> > +m6240.yaml*__;Iw!!G3vK!QZhdCaAh-suxnL97gv8Mo9tJ-
> fyaOPhGTFhz_k3OChSZUx
> > +mcQn32yvufRdtd0iMVyU68kXV-mSkyrVHRCzlwdqtrvfRV$
> > +$schema:
> > +https://urldefense.com/v3/__http://devicetree.org/meta-
> schemas/core.y
> > +aml*__;Iw!!G3vK!QZhdCaAh-suxnL97gv8Mo9tJ-
> fyaOPhGTFhz_k3OChSZUxmcQn32y
> > +vufRdtd0iMVyU68kXV-mSkyrVHRCzlwdo9WSaIa$
> > +
> > +title: Texas Instruments PCM6240 Family Audio ADC/DAC/Router
> > +
> > +maintainers:
> > +  - Shenghao Ding <shenghao-ding@ti.com>
> > +
> > +description: |
> > +  The PCM6240 Family driver offer a flexible architecture to set the
> > +device
> 
> Describe hardware, not Linux driver.
> 
> > +  number, registers and params for different filters in a bin file.
> 
> I don't understand entire sentence in context of hardware.
> 
> > +
> > +  Specifications about the audio chip can be found at:
> > +    https://www.ti.com/lit/gpn/tlv320adc3120
> > +    https://www.ti.com/lit/gpn/tlv320adc5120
> > +    https://www.ti.com/lit/gpn/tlv320adc6120
> > +    https://www.ti.com/lit/gpn/dix4192
> > +    https://www.ti.com/lit/gpn/pcm1690
> > +    https://www.ti.com/lit/gpn/pcm3120-q1
> > +    https://www.ti.com/lit/gpn/pcm3140-q1
> > +    https://www.ti.com/lit/gpn/pcm5120-q1
> > +    https://www.ti.com/lit/gpn/pcm6120-q1
> > +    https://www.ti.com/lit/gpn/pcm6260-q1
> > +    https://www.ti.com/lit/gpn/pcm9211
> > +    https://www.ti.com/lit/gpn/pcmd3140
> > +    https://www.ti.com/lit/gpn/pcmd3180
> > +    https://www.ti.com/lit/gpn/taa5212
> > +    https://www.ti.com/lit/gpn/tad5212
> > +
> > +properties:
> > +  compatible:
> > +    description: |
> > +      ti,adc3120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
> > +      digital converter (ADC) with 106-dB SNR.
> > +
> > +      ti,adc5120: 2-Channel, 768-kHz, Burr-BrownTM Audio ADC with 120-
> dB SNR.
> > +
> > +      ti,adc6120: Stereo-channel, 768-kHz, Burr-Brown™ audio analog-to-
> > +      digital converter (ADC) with 123-dB SNR.
> > +
> > +      ti,pcm1690: 113dB SNR, 24-Bit, 192-kHz Sampling, Enhanced Multi-
> Level
> > +      ?S, Eight-Channel Audio Digital-to-Analog Converter with Differential
> > +      Outputs.
> > +
> > +      ti,pcm3120: Automotive, stereo, 106-dB SNR, 768-kHz, low-power
> > +      software-controlled audio ADC.
> > +
> > +      ti,pcm3140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM
> Audio ADC
> > +      with 106-dB SNR.
> > +
> > +      ti,pcm5120: Automotive, stereo, 120-dB SNR, 768-kHz, low-power
> > +      software-controlled audio ADC.
> > +
> > +      ti,pcm5140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM
> Audio ADC
> > +      with 120-dB SNR.
> > +
> > +      ti,pcm6120: Automotive, stereo, 123-dB SNR, 768-kHz, low-power
> > +      software-controlled audio ADC.
> > +
> > +      ti,pcm6140: Automotive, Quad-Channel, 768-kHz, Burr-BrownTM
> Audio ADC
> > +      with 123-dB SNR.
> > +
> > +      ti,pcm6240: Automotive 4-ch audio ADC with integrated
> programmable mic
> > +      bias, boost and input diagnostics.
> > +
> > +      ti,pcm6260: Automotive 6-ch audio ADC with integrated
> programmable mic
> > +      bias, boost and input diagnostics.
> > +
> > +      ti,pcm9211: 216-kHz Digital Audio Interface Transceiver (DIX)
> > +      With Stereo ADC and Routing.
> > +
> > +      ti,pcmd3140: Four-channel PDM-input to TDM or I S output converter.
> > +
> > +      ti,pcmd3180: Eight-channel pulse-density-modulation input to TDM or
> > +      I S output converter.
> > +
> > +      ti,taa5212: Low-power high-performance stereo audio ADC with 118-
> dB
> > +      dynamic range.
> > +
> > +      ti,tad5212: Low-power stereo audio DAC with 120-dB dynamic range.
> > +    enum:
> > +      - ti,adc3120
> > +      - ti,adc5120
> > +      - ti,adc6120
> > +      - ti,dix4192
> > +      - ti,pcm1690
> > +      - ti,pcm3120
> > +      - ti,pcm3140
> > +      - ti,pcm5120
> > +      - ti,pcm5140
> > +      - ti,pcm6120
> > +      - ti,pcm6140
> > +      - ti,pcm6240
> > +      - ti,pcm6260
> > +      - ti,pcm9211
> > +      - ti,pcmd3140
> > +      - ti,pcmd3180
> > +      - ti,pcmd512x
> > +      - ti,taa5212
> > +      - ti,taa5412
> > +      - ti,tad5212
> > +      - ti,tad5412
> 
> And none of them are compatible with something?
> 
> > +
> > +  reg:
> > +    description:
> > +      I2C address, in multiple pcmdevices case, all the i2c address
> > +      aggregate as one Audio Device to support multiple audio slots.
> > +    maxItems: 4
> > +    minItems: 1
> 
> minItems, then maxItems.
> 
> Please open existing bindings and look how it is done there.
> 
> 
> > +
> > +  reset-gpios:
> > +    maxItems: 1
> > +    description:
> > +      A GPIO line handling reset of the chip. As the line is active high,
> > +      it should be marked GPIO_ACTIVE_HIGH.
> 
> Drop description, it's obvious. Or write something useful.
> 
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +    description:
> > +      Invalid only for ti,pcm1690 because of no INT pin.
> > +
> > +  '#sound-dai-cells':
> > +    const: 0
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +
> > +allOf:
> > +  - $ref: dai-common.yaml#
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            enum:
> > +              - ti,pcm1690
> > +    then:
> > +      properties:
> > +        reg:
> > +          description:
> > +            I2C address, in multiple pcmdevices case, all the i2c address
> > +            aggregate as one Audio Device to support multiple audio slots.
> > +          maxItems: 4
> > +          minItems: 1
> > +          items:
> > +            minimum: 0x4c
> > +            maximum: 0x4f
> 
> Why do you repeat the reg constraints? This does not seem needed.
> 
> > +        interrupts: false
> > +
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            enum:
> > +              - ti,pcm3140
> > +              - ti,pcm5140
> > +              - ti,pcm6140
> > +              - ti,pcmd3180
> > +    then:
> > +      properties:
> > +        reg:
> > +          description:
> > +            I2C address, in multiple pcmdevices case, all the i2c address
> > +            aggregate as one Audio Device to support multiple audio slots.
> > +          maxItems: 4
> > +          minItems: 1
> 
> Drop entire if
How to convey ti,pcm1690 does not support interrupt, and others support if I remove this if
> 
> > +          items:
> > +            minimum: 0x4c
> > +            maximum: 0x4f
> > +

BR
Shenghao Ding
  
Krzysztof Kozlowski Jan. 25, 2024, 7:50 a.m. UTC | #8
On 25/01/2024 08:39, Ding, Shenghao wrote:
>>
>> Why do you repeat the reg constraints? This does not seem needed.
>>
>>> +        interrupts: false
>>> +
>>> +  - if:
>>> +      properties:
>>> +        compatible:
>>> +          contains:
>>> +            enum:
>>> +              - ti,pcm3140
>>> +              - ti,pcm5140
>>> +              - ti,pcm6140
>>> +              - ti,pcmd3180
>>> +    then:
>>> +      properties:
>>> +        reg:
>>> +          description:
>>> +            I2C address, in multiple pcmdevices case, all the i2c address
>>> +            aggregate as one Audio Device to support multiple audio slots.
>>> +          maxItems: 4
>>> +          minItems: 1
>>

You did not respond to any of other comments, therefore I assume you
agree with them 100% and you will implement them fully.


>> Drop entire if
> How to convey ti,pcm1690 does not support interrupt, and others support if I remove this if

How? There is no pcm1690 here.

BTW, add missing line breaks, especially before next blocks like
additionalProperties.

>>
>>> +          items:
>>> +            minimum: 0x4c
>>> +            maximum: 0x4f
>>> +
> 
> BR
> Shenghao Ding
> 

Best regards,
Krzysztof
  
Ding, Shenghao Jan. 25, 2024, 8:46 a.m. UTC | #9
> -----Original Message-----
> From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Sent: Thursday, January 25, 2024 3:50 PM
> To: Ding, Shenghao <shenghao-ding@ti.com>; broonie@kernel.org;
> conor+dt@kernel.org
> Cc: robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin
> <kevin-lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>;
> devicetree@vger.kernel.org; lgirdwood@gmail.com; perex@perex.cz;
> pierre-louis.bossart@linux.intel.com; 13916275206@139.com; linux-
> sound@vger.kernel.org; linux-kernel@vger.kernel.org;
> liam.r.girdwood@intel.com; soyer@irl.hu; Huang, Jonathan
> <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter <pdjuandi@ti.com>;
> McPherson, Jeff <j-mcpherson@ti.com>; Navada Kanyana, Mukund
> <navada@ti.com>
> Subject: Re: [EXTERNAL] Re: [PATCH v1 4/4] ASoc: dt-bindings: Create yaml
> file for pcm6240 codec driver
> 
> On 25/01/2024 08: 39, Ding, Shenghao wrote: >> >> Why do you repeat the
> reg constraints? This does not seem needed. >> >>> + interrupts: false >>>
> + >>> + - if: >>> + properties: >>> ZjQcmQRYFpfptBannerStart This message
> was sent from outside of Texas Instruments.
> Do not click links or open attachments unless you recognize the source of
> this email and know the content is safe.
> 
> ZjQcmQRYFpfptBannerEnd
> On 25/01/2024 08:39, Ding, Shenghao wrote:
> >>
> >> Why do you repeat the reg constraints? This does not seem needed.
> >>
> >>> +        interrupts: false
> >>> +
> >>> +  - if:
> >>> +      properties:
> >>> +        compatible:
> >>> +          contains:
> >>> +            enum:
> >>> +              - ti,pcm3140
> >>> +              - ti,pcm5140
> >>> +              - ti,pcm6140
> >>> +              - ti,pcmd3180
> >>> +    then:
> >>> +      properties:
> >>> +        reg:
> >>> +          description:
> >>> +            I2C address, in multiple pcmdevices case, all the i2c address
> >>> +            aggregate as one Audio Device to support multiple audio slots.
> >>> +          maxItems: 4
> >>> +          minItems: 1
> >>
> 
> You did not respond to any of other comments, therefore I assume you
> agree with them 100% and you will implement them fully.
> 
> 
> >> Drop entire if
> > How to convey ti,pcm1690 does not support interrupt, and others
> > support if I remove this if
> 
> How? There is no pcm1690 here.
How can others know that pcm3140, pcm5140, pcm6140 and pcmd3180
Support i2c address from 0x4c to 0x4f, if this if branch was removed.
> 
> BTW, add missing line breaks, especially before next blocks like
> additionalProperties.
Accepted
> 
> >>
> >>> +          items:
> >>> +            minimum: 0x4c
> >>> +            maximum: 0x4f
> >>> +
> >
> > BR
> > Shenghao Ding
> >
> 
> Best regards,
> Krzysztof
  
Krzysztof Kozlowski Jan. 25, 2024, 8:57 a.m. UTC | #10
On 25/01/2024 09:46, Ding, Shenghao wrote:
> 
> 
>> -----Original Message-----
>> From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>> Sent: Thursday, January 25, 2024 3:50 PM
>> To: Ding, Shenghao <shenghao-ding@ti.com>; broonie@kernel.org;
>> conor+dt@kernel.org
>> Cc: robh+dt@kernel.org; andriy.shevchenko@linux.intel.com; Lu, Kevin
>> <kevin-lu@ti.com>; Xu, Baojun <baojun.xu@ti.com>;
>> devicetree@vger.kernel.org; lgirdwood@gmail.com; perex@perex.cz;
>> pierre-louis.bossart@linux.intel.com; 13916275206@139.com; linux-
>> sound@vger.kernel.org; linux-kernel@vger.kernel.org;
>> liam.r.girdwood@intel.com; soyer@irl.hu; Huang, Jonathan
>> <jkhuang3@ti.com>; tiwai@suse.de; Djuandi, Peter <pdjuandi@ti.com>;
>> McPherson, Jeff <j-mcpherson@ti.com>; Navada Kanyana, Mukund
>> <navada@ti.com>
>> Subject: Re: [EXTERNAL] Re: [PATCH v1 4/4] ASoc: dt-bindings: Create yaml
>> file for pcm6240 codec driver
>>
>> On 25/01/2024 08: 39, Ding, Shenghao wrote: >> >> Why do you repeat the
>> reg constraints? This does not seem needed. >> >>> + interrupts: false >>>
>> + >>> + - if: >>> + properties: >>> ZjQcmQRYFpfptBannerStart This message
>> was sent from outside of Texas Instruments.
>> Do not click links or open attachments unless you recognize the source of
>> this email and know the content is safe.
>>
>> ZjQcmQRYFpfptBannerEnd
>> On 25/01/2024 08:39, Ding, Shenghao wrote:
>>>>
>>>> Why do you repeat the reg constraints? This does not seem needed.
>>>>
>>>>> +        interrupts: false
>>>>> +
>>>>> +  - if:
>>>>> +      properties:
>>>>> +        compatible:
>>>>> +          contains:
>>>>> +            enum:
>>>>> +              - ti,pcm3140
>>>>> +              - ti,pcm5140
>>>>> +              - ti,pcm6140
>>>>> +              - ti,pcmd3180
>>>>> +    then:
>>>>> +      properties:
>>>>> +        reg:
>>>>> +          description:
>>>>> +            I2C address, in multiple pcmdevices case, all the i2c address
>>>>> +            aggregate as one Audio Device to support multiple audio slots.
>>>>> +          maxItems: 4
>>>>> +          minItems: 1
>>>>
>>
>> You did not respond to any of other comments, therefore I assume you
>> agree with them 100% and you will implement them fully.
>>
>>
>>>> Drop entire if
>>> How to convey ti,pcm1690 does not support interrupt, and others
>>> support if I remove this if
>>
>> How? There is no pcm1690 here.
> How can others know that pcm3140, pcm5140, pcm6140 and pcmd3180
> Support i2c address from 0x4c to 0x4f, if this if branch was removed.

They do not need anything to support any specific I2C address... If ever
wondering, open some recent bindings and take a look how it is done there.


Best regards,
Krzysztof
  
Andy Shevchenko Jan. 28, 2024, 3:11 p.m. UTC | #11
On Tue, Jan 23, 2024 at 07:14:07PM +0800, Shenghao Ding wrote:
> PCM6240 driver implements a flexible and configurable setting for register
> and filter coefficients, to one, two or even multiple PCM6240 Family Audio
> chips.

..

> +#include <linux/of_gpio.h>


No new code is supposed to use this header.
Also the header inclusion here is a mess, please use IWYU principle
(Include What You Use).

..

> +static struct pcmdevice_mixer_control adc5120_analog_gain_ctl[] = {
> +	{
> +		.shift = 1,
> +		.reg = ADC5120_REG_CH1_ANALOG_GAIN,
> +		.max = 0x54,
> +		.invert = 0,

Strictly speaking assignments to 0, false, NULL are unnecessary for static
variables, but I'm not against this as it might make cleaner to see what's
going on here. Btw, shouldn't these global variables be const?

> +	},
> +	{
> +		.shift = 1,
> +		.reg = ADC5120_REG_CH2_ANALOG_GAIN,
> +		.max = 0x54,
> +		.invert = 0,
> +	}
> +};

..

> +static int pcmdev_change_dev(struct pcmdevice_priv *pcm_priv,
> +	unsigned short dev_no)
> +{
> +	struct i2c_client *client = (struct i2c_client *)pcm_priv->client;

Why do you need casting? Is that variable is not void *?

> +	struct regmap *map = pcm_priv->regmap;

> +	int ret = 0;

Unneeded assignment, just return 0 directly.

> +
> +	if (client->addr != pcm_priv->addr[dev_no]) {
> +		client->addr = pcm_priv->addr[dev_no];
> +		/* All pcmdevices share the same regmap, clear the page
> +		 * inside regmap once switching to another pcmdevice.
> +		 * Register 0 at any pages inside pcmdevice is the same
> +		 * one for page-switching.
> +		 */
> +		ret = regmap_write(map, PCMDEVICE_PAGE_SELECT, 0);
> +		if (ret < 0)
> +			dev_err(pcm_priv->dev, "%s, E=%d\n", __func__, ret);
> +	}
> +
> +	return ret;
> +}

..

> +static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev,
> +	unsigned int dev_no, unsigned int reg, unsigned int *val)
> +{

> +	int ret = -EINVAL;

Unneeded assignment, use this value directly.

> +	if (dev_no < pcm_dev->ndev) {
> +		struct regmap *map = pcm_dev->regmap;
> +
> +		ret = pcmdev_change_dev(pcm_dev, dev_no);
> +		if (ret < 0) {
> +			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
> +			goto out;
> +		}
> +
> +		ret = regmap_read(map, reg, val);
> +		if (ret < 0)
> +			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
> +	} else

Besides broken style, this 'else' becomes redundant after proposed changes.

> +		dev_err(pcm_dev->dev, "%s, no such channel(%d)\n", __func__,
> +			dev_no);
> +
> +
> +out:

Useless label.

> +	return ret;
> +}

I believe you may reduce code size by ~2-3% just by refactoring it in a better
way. (Some examples are given above)

..

> +static int pcmdev_dev_bulk_write(struct pcmdevice_priv *pcm_dev,
> +	unsigned int dev_no, unsigned int reg, unsigned char *data,
> +	unsigned int len)

Ditto and so on!

..

> +	struct snd_soc_component *codec
> +		= snd_soc_kcontrol_component(kcontrol);

= on the previous line, but you may just put all on one line.

..

> +	struct pcmdevice_priv *pcm_dev =
> +		snd_soc_component_get_drvdata(codec);

Ditto, and so on...


> +	ucontrol->value.integer.value[0] = pcm_dev->cur_conf;
> +
> +	return 0;
> +}

..

> +{
> +	struct snd_soc_component *codec
> +		= snd_soc_kcontrol_component(kcontrol);
> +	struct pcmdevice_priv *pcm_dev =
> +		snd_soc_component_get_drvdata(codec);

> +	int ret = 0;

Useless variable, return number directly.

> +
> +	if (pcm_dev->cur_conf != ucontrol->value.integer.value[0]) {
> +		pcm_dev->cur_conf = ucontrol->value.integer.value[0];
> +		ret = 1;
> +	}
> +
> +	return ret;
> +}

..

> +	unsigned int mask = (1 << fls(max)) - 1;

BIT() ?

..

> +	mutex_lock(&pcm_dev->codec_lock);

Why not using cleanup.h?

> +	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
> +	if (rc) {
> +		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
> +			__func__, rc);
> +		goto out;
> +	}
> +
> +	val = (val >> shift) & mask;
> +	val = (val > max) ? max : val;
> +	val = mc->invert ? max - val : val;
> +	ucontrol->value.integer.value[0] = val;

> +out:
> +	mutex_unlock(&pcm_dev->codec_lock);

With cleanup.h becomes redundant.

> +	return rc;

Be consistent with name, ret, rc, what else?

..

> +};
> +

Unnecessary blank line.

> +module_i2c_driver(pcmdevice_i2c_driver);
  

Patch

diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c
new file mode 100644
index 000000000000..d9e4d57dc115
--- /dev/null
+++ b/sound/soc/codecs/pcm6240.c
@@ -0,0 +1,2062 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments PCM6240 Family Audio ADC/DAC/Router
+//
+// Copyright (C) 2022 - 2024 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The PCM6240 driver implements a flexible and configurable
+// algo coefficient setting for one, two, or even multiple
+// PCM6240 Family chips.
+//
+// Author: Shenghao Ding <shenghao-ding@ti.com>
+//
+
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "pcm6240.h"
+
+static const struct i2c_device_id pcmdevice_i2c_id[] = {
+	{ "adc3120",  ADC3120  },
+	{ "adc5120",  ADC5120  },
+	{ "adc6120",  ADC6120  },
+	{ "dix4192",  DIX4192  },
+	{ "pcm1690",  PCM1690  },
+	{ "pcm3120",  PCM3120  },
+	{ "pcm3140",  PCM3140  },
+	{ "pcm5120",  PCM5120  },
+	{ "pcm5140",  PCM5140  },
+	{ "pcm6120",  PCM6120  },
+	{ "pcm6140",  PCM6140  },
+	{ "pcm6240",  PCM6240  },
+	{ "pcm6260",  PCM6260  },
+	{ "pcm9211",  PCM9211  },
+	{ "pcmd3140", PCMD3140 },
+	{ "pcmd3180", PCMD3180 },
+	{ "pcmd512x", PCMD512X },
+	{ "taa5212",  TAA5212  },
+	{ "taa5412",  TAA5412  },
+	{ "tad5212",  TAD5212  },
+	{ "tad5412",  TAD5412  },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pcmdevice_i2c_id);
+
+static struct pcmdevice_mixer_control adc5120_analog_gain_ctl[] = {
+	{
+		.shift = 1,
+		.reg = ADC5120_REG_CH1_ANALOG_GAIN,
+		.max = 0x54,
+		.invert = 0,
+	},
+	{
+		.shift = 1,
+		.reg = ADC5120_REG_CH2_ANALOG_GAIN,
+		.max = 0x54,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control adc5120_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = ADC5120_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = ADC5120_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm1690_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH7_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM1690_REG_CH8_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6240_analog_gain_ctl[] = {
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH1_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH2_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH3_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6240_REG_CH4_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6240_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6240_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6260_analog_gain_ctl[] = {
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH1_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH2_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH3_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH4_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH5_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	},
+	{
+		.shift = 2,
+		.reg = PCM6260_REG_CH6_ANALOG_GAIN,
+		.max = 0x42,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm6260_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM6260_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcm9211_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCM9211_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCM9211_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcmd3140_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3140_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control pcmd3180_digital_gain_ctl[] = {
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH1_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH2_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH3_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH4_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH5_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH6_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH7_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = PCMD3180_REG_CH8_DIGITAL_GAIN,
+		.max = 0xff,
+		.invert = 0,
+	}
+};
+
+static struct pcmdevice_mixer_control taa5412_digital_volume_ctl[] = {
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH1_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH2_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH3_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH4_DIGITAL_VOLUME,
+		.max = 0xff,
+		.invert = 0,
+	},
+};
+
+static struct pcmdevice_mixer_control taa5412_fine_gain_ctl[] = {
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH1_FINE_GAIN,
+		.max = 0xf,
+		.invert = 0,
+	},
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH2_FINE_GAIN,
+		.max = 0xf,
+		.invert = 0,
+	},
+	{
+		.shift = 4,
+		.reg = TAA5412_REG_CH3_FINE_GAIN,
+		.max = 0xf,
+		.invert = 4,
+	},
+	{
+		.shift = 0,
+		.reg = TAA5412_REG_CH4_FINE_GAIN,
+		.max = 0xf,
+		.invert = 4,
+	},
+};
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3180_dig_gain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3140_dig_gain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_fine_dig_gain_tlv,
+	-12750, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_dig_gain_tlv,
+	-25500, 0);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm9211_dig_gain_tlv,
+	-11450, 2000);
+static const DECLARE_TLV_DB_MINMAX_MUTE(adc5120_fgain_tlv,
+	-10050, 2700);
+static const DECLARE_TLV_DB_LINEAR(adc5120_chgain_tlv, 0, 4200);
+static const DECLARE_TLV_DB_MINMAX_MUTE(pcm6260_fgain_tlv,
+	-10000, 2700);
+static const DECLARE_TLV_DB_LINEAR(pcm6260_chgain_tlv, 0, 4200);
+static const DECLARE_TLV_DB_MINMAX_MUTE(taa5412_dig_vol_tlv,
+	-8050, 4700);
+static const DECLARE_TLV_DB_LINEAR(taa5412_fine_gain_tlv,
+	-80, 70);
+
+static int pcmdev_change_dev(struct pcmdevice_priv *pcm_priv,
+	unsigned short dev_no)
+{
+	struct i2c_client *client = (struct i2c_client *)pcm_priv->client;
+	struct regmap *map = pcm_priv->regmap;
+	int ret = 0;
+
+	if (client->addr != pcm_priv->addr[dev_no]) {
+		client->addr = pcm_priv->addr[dev_no];
+		/* All pcmdevices share the same regmap, clear the page
+		 * inside regmap once switching to another pcmdevice.
+		 * Register 0 at any pages inside pcmdevice is the same
+		 * one for page-switching.
+		 */
+		ret = regmap_write(map, PCMDEVICE_PAGE_SELECT, 0);
+		if (ret < 0)
+			dev_err(pcm_priv->dev, "%s, E=%d\n", __func__, ret);
+	}
+
+	return ret;
+}
+
+static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int *val)
+{
+	int ret = -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		struct regmap *map = pcm_dev->regmap;
+
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+			goto out;
+		}
+
+		ret = regmap_read(map, reg, val);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, no such channel(%d)\n", __func__,
+			dev_no);
+
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_bulk_write(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned char *data,
+	unsigned int len)
+{
+	int ret = -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		struct regmap *map = pcm_dev->regmap;
+
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+			goto out;
+		}
+
+		ret = regmap_bulk_write(map, reg, data, len);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "bulk_write ERROR, E=%d\n",
+				ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such channel(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_update_bits(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int mask,
+	unsigned int value)
+{
+	struct regmap *map = pcm_dev->regmap;
+	int ret =  -EINVAL;
+
+	if (dev_no < pcm_dev->ndev) {
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n",
+				__func__, ret);
+			goto out;
+		}
+
+		ret = regmap_update_bits(map, reg, mask, value);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "update_bits ERROR, E=%d\n",
+				ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such device(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdev_dev_write(struct pcmdevice_priv *pcm_dev,
+	unsigned int dev_no, unsigned int reg, unsigned int value)
+{
+	struct regmap *map = pcm_dev->regmap;
+	int ret = 0;
+
+	if (dev_no < pcm_dev->ndev) {
+		ret = pcmdev_change_dev(pcm_dev, dev_no);
+		if (ret < 0) {
+			dev_err(pcm_dev->dev, "%s, E=%d\n",
+				__func__, ret);
+			goto out;
+		}
+
+		ret = regmap_write(map, reg, value);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s, E=%d\n", __func__, ret);
+	} else
+		dev_err(pcm_dev->dev, "%s, ERROR: no such device(%d)\n",
+			__func__, dev_no);
+
+out:
+	return ret;
+}
+
+static int pcmdevice_info_profile(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = max(0, pcm_dev->regbin.ncfgs - 1);
+
+	return 0;
+}
+
+static int pcmdevice_get_profile_id(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = pcm_dev->cur_conf;
+
+	return 0;
+}
+
+static int pcmdevice_set_profile_id(
+	struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec
+		= snd_soc_kcontrol_component(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(codec);
+	int ret = 0;
+
+	if (pcm_dev->cur_conf != ucontrol->value.integer.value[0]) {
+		pcm_dev->cur_conf = ucontrol->value.integer.value[0];
+		ret = 1;
+	}
+
+	return ret;
+}
+
+static int pcmdevice_info_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mc->max;
+	return 0;
+}
+
+static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcmdevice_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg, val_mask,
+		val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL,
+		&val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read mode, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+	if (!(val & PCM1690_REG_MODE_CTRL_DAMS_MSK)) {
+		dev_info(pcm_dev->dev,
+			"%s: set to wide-range mode, before using this ctrl\n",
+			__func__);
+		ucontrol->value.integer.value[0] = -25500;
+		goto out;
+	}
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcm1690_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg,
+		PCM1690_REG_MODE_CTRL_DAMS_MSK | val_mask,
+		PCM1690_REG_MODE_CTRL_DAMS_WIDE_RANGE | val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_get_finevolsw(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	int rc = 0;
+	unsigned int val;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	rc = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL,
+		&val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read mode, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+	if (val & PCM1690_REG_MODE_CTRL_DAMS_MSK) {
+		dev_info(pcm_dev->dev,
+			"%s: Set to fine mode, before using this ctrl\n",
+			__func__);
+		ucontrol->value.integer.value[0] = -12750;
+		goto out;
+	}
+	rc = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
+	if (rc) {
+		dev_err(pcm_dev->dev, "%s:read, ERROR, E=%d\n",
+			__func__, rc);
+		goto out;
+	}
+
+	val = (val >> shift) & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	ucontrol->value.integer.value[0] = val;
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return rc;
+}
+
+static int pcm1690_put_finevolsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcmdevice_priv *pcm_dev =
+		snd_soc_component_get_drvdata(component);
+	struct pcmdevice_mixer_control *mc =
+		(struct pcmdevice_mixer_control *)kcontrol->private_value;
+	int err = 0;
+	unsigned int reg = mc->reg;
+	unsigned int dev_no = mc->dev_no;
+	int max = mc->max;
+	unsigned int val, val_mask;
+	unsigned int shift = mc->shift;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	val = ucontrol->value.integer.value[0] & mask;
+	val = (val > max) ? max : val;
+	val = mc->invert ? max - val : val;
+	val_mask = mask << shift;
+	val = val << shift;
+	err = pcmdev_dev_update_bits(pcm_dev, dev_no, reg,
+		PCM1690_REG_MODE_CTRL_DAMS_MSK | val_mask,
+		PCM1690_REG_MODE_CTRL_DAMS_FINE_STEP | val);
+	if (err) {
+		dev_err(pcm_dev->dev, "%s:update_bits, ERROR, E=%d\n",
+			__func__, err);
+		goto out;
+	}
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return err;
+}
+
+static int pcm1690_ctrl_add(struct pcmdevice_priv *pcm_dev)
+{
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	struct pcmdev_ctrl_info fine_dig_ctl_info = {0};
+	struct pcmdev_ctrl_info dig_ctl_info = {0};
+	struct snd_kcontrol_new *pcmdev_controls;
+	int nr_controls = 1, ret = 0, mix_index = 0, i, chn;
+	char *name;
+
+	dig_ctl_info.gain = pcm1690_dig_gain_tlv;
+	dig_ctl_info.pcmdev_ctrl = pcm1690_digital_gain_ctl;
+	dig_ctl_info.ctrl_array_size =
+		ARRAY_SIZE(pcm1690_digital_gain_ctl);
+
+	fine_dig_ctl_info.gain = pcm1690_fine_dig_gain_tlv;
+	fine_dig_ctl_info.pcmdev_ctrl = pcm1690_digital_gain_ctl;
+	fine_dig_ctl_info.ctrl_array_size =
+		ARRAY_SIZE(pcm1690_digital_gain_ctl);
+	nr_controls += pcm_dev->ndev * (dig_ctl_info.ctrl_array_size +
+		fine_dig_ctl_info.ctrl_array_size);
+
+	pcmdev_controls = devm_kzalloc(pcm_dev->dev,
+		nr_controls * sizeof(*pcmdev_controls),
+		GFP_KERNEL);
+	if (!pcmdev_controls) {
+		pcm_dev->pcm_ctrl.pcmdevice_profile_controls = NULL;
+		ret = -ENOMEM;
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.pcmdevice_profile_controls = pcmdev_controls;
+	/* Create a mixer item for selecting the active profile */
+	name = devm_kzalloc(pcm_dev->dev,
+		SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "PCMDEVICE Profile id");
+	pcmdev_controls[mix_index].name = name;
+	pcmdev_controls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	pcmdev_controls[mix_index].info = pcmdevice_info_profile;
+	pcmdev_controls[mix_index].get = pcmdevice_get_profile_id;
+	pcmdev_controls[mix_index].put = pcmdevice_set_profile_id;
+	mix_index++;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev,
+				"%s: mix_index: %d nr_controls: %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= dig_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index,
+					nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-digi-gain",
+				pcm_dev->dev_name, adap->nr, i, chn);
+			pcmdev_controls[mix_index].tlv.p = dig_ctl_info.gain;
+			dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = i;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcm1690_get_volsw;
+			pcmdev_controls[mix_index].put = pcm1690_put_volsw;
+			mix_index++;
+		}
+	}
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev,
+				"%s: mix_index = %d nr_controls = %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= fine_dig_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index, nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-fine-digi-gain",
+				pcm_dev->dev_name, adap->nr, i, chn);
+			pcmdev_controls[mix_index].tlv.p =
+				fine_dig_ctl_info.gain;
+			fine_dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = i;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&fine_dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get =
+				pcm1690_get_finevolsw;
+			pcmdev_controls[mix_index].put =
+				pcm1690_put_finevolsw;
+			mix_index++;
+		}
+	}
+
+	ret = snd_soc_add_component_controls(pcm_dev->component,
+		pcmdev_controls,
+		nr_controls < mix_index ? nr_controls : mix_index);
+	if (ret) {
+		dev_err(pcm_dev->dev,
+			"%s: add_component_controls error = %d\n",
+			__func__, ret);
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.nr_controls =
+		nr_controls < mix_index ? nr_controls : mix_index;
+out:
+	return ret;
+}
+
+static void pcm9211_sw_rst(struct pcmdevice_priv *pcm_dev)
+{
+	int ret, i;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		ret = pcmdev_dev_update_bits(pcm_dev, i,
+			PCM9211_REG_SW_CTRL, PCM9211_REG_SW_CTRL_MRST_MSK,
+			PCM9211_REG_SW_CTRL_MRST);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s: dev %d swreset fail, %d\n",
+				__func__, i, ret);
+	}
+}
+
+static void pcmdevice_sw_rst(struct pcmdevice_priv *pcm_dev)
+{
+	int ret, i;
+
+	for (i = 0; i < pcm_dev->ndev; i++) {
+		ret = pcmdev_dev_write(pcm_dev, i, PCMDEVICE_REG_SWRESET,
+			PCMDEVICE_REG_SWRESET_RESET);
+		if (ret < 0)
+			dev_err(pcm_dev->dev, "%s: dev %d swreset fail, %d\n",
+				__func__, i, ret);
+	}
+}
+
+static struct pcmdevice_config_info *pcmdevice_add_config(void *ctxt,
+	const unsigned char *config_data, unsigned int config_size,
+	int *status)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	struct pcmdevice_config_info *cfg_info;
+	struct pcmdevice_block_data **bk_da;
+	unsigned int config_offset = 0, i;
+
+	cfg_info = kzalloc(sizeof(struct pcmdevice_config_info), GFP_KERNEL);
+	if (!cfg_info) {
+		*status = -ENOMEM;
+		dev_err(pcm_dev->dev, "add config: cfg_info alloc failed!\n");
+		goto out;
+	}
+
+	if (pcm_dev->regbin.fw_hdr.binary_version_num >= 0x105) {
+		if (config_offset + 64 > (int)config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev, "add config: Out of boundary\n");
+			goto out;
+		}
+		memcpy(cfg_info->cfg_name, &config_data[config_offset], 64);
+		config_offset += 64;
+	}
+
+	if (config_offset + 4 > config_size) {
+		*status = -EINVAL;
+		dev_err(pcm_dev->dev, "add config: Out of boundary\n");
+		goto out;
+	}
+	cfg_info->nblocks =
+		be32_to_cpup((__be32 *)&config_data[config_offset]);
+	config_offset += 4;
+
+	bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks,
+		sizeof(struct pcmdevice_block_data *), GFP_KERNEL);
+	if (!bk_da) {
+		*status = -ENOMEM;
+		goto out;
+	}
+	cfg_info->real_nblocks = 0;
+	for (i = 0; i < cfg_info->nblocks; i++) {
+		if (config_offset + 12 > config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev,
+				"%s: Out of boundary: i = %d nblocks = %u!\n",
+				__func__, i, cfg_info->nblocks);
+			break;
+		}
+		bk_da[i] = kzalloc(sizeof(struct pcmdevice_block_data),
+			GFP_KERNEL);
+		if (!bk_da[i]) {
+			*status = -ENOMEM;
+			break;
+		}
+		bk_da[i]->dev_idx = config_data[config_offset];
+		config_offset++;
+
+		bk_da[i]->block_type = config_data[config_offset];
+		config_offset++;
+
+		if (bk_da[i]->block_type == PCMDEVICE_BIN_BLK_PRE_POWER_UP) {
+			if (bk_da[i]->dev_idx == 0)
+				cfg_info->active_dev =
+					(1 << pcm_dev->ndev) - 1;
+			else
+				cfg_info->active_dev =
+					1 << (bk_da[i]->dev_idx - 1);
+		}
+
+		bk_da[i]->yram_checksum =
+			be16_to_cpup((__be16 *)&config_data[config_offset]);
+		config_offset += 2;
+		bk_da[i]->block_size =
+			be32_to_cpup((__be32 *)&config_data[config_offset]);
+		config_offset += 4;
+
+		bk_da[i]->n_subblks =
+			be32_to_cpup((__be32 *)&config_data[config_offset]);
+
+		config_offset += 4;
+
+		if (config_offset + bk_da[i]->block_size > config_size) {
+			*status = -EINVAL;
+			dev_err(pcm_dev->dev,
+				"%s: Out of boundary: i = %d blks = %u!\n",
+				__func__, i, cfg_info->nblocks);
+			break;
+		}
+
+		bk_da[i]->regdata = kmemdup(&config_data[config_offset],
+			bk_da[i]->block_size, GFP_KERNEL);
+		if (!bk_da[i]->regdata) {
+			*status = -ENOMEM;
+			goto out;
+		}
+		config_offset += bk_da[i]->block_size;
+		cfg_info->real_nblocks += 1;
+	}
+out:
+	return cfg_info;
+}
+
+static int pcmdev_ctrl_add(struct pcmdevice_priv *pcm_dev)
+{
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	struct pcmdev_ctrl_info analog_ctl_info = {0};
+	struct pcmdev_ctrl_info dig_ctl_info = {0};
+	struct snd_kcontrol_new *pcmdev_controls;
+	int nr_controls = 1, ret = 0;
+	int mix_index = 0, dev, chn;
+	char *name;
+
+	switch (pcm_dev->chip_id) {
+	case ADC3120:
+	case ADC5120:
+	case ADC6120:
+	case PCM3120:
+	case PCM5120:
+	case PCM6120:
+		dig_ctl_info.gain = adc5120_fgain_tlv;
+		analog_ctl_info.gain = adc5120_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = adc5120_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(adc5120_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = adc5120_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(adc5120_digital_gain_ctl);
+		break;
+	case PCM3140:
+	case PCM5140:
+	case PCM6140:
+	case PCM6240:
+		dig_ctl_info.gain = pcm6260_fgain_tlv;
+		analog_ctl_info.gain = pcm6260_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = pcm6240_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6240_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = pcm6240_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6240_digital_gain_ctl);
+		break;
+	case PCM6260:
+		dig_ctl_info.gain = pcm6260_fgain_tlv;
+		analog_ctl_info.gain = pcm6260_chgain_tlv;
+		analog_ctl_info.pcmdev_ctrl = pcm6260_analog_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6260_analog_gain_ctl);
+		dig_ctl_info.pcmdev_ctrl = pcm6260_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm6260_digital_gain_ctl);
+		break;
+	case PCM9211:
+		dig_ctl_info.gain = pcm9211_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcm9211_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcm9211_digital_gain_ctl);
+		break;
+	case PCMD3140:
+		dig_ctl_info.gain = pcmd3140_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcmd3140_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcmd3140_digital_gain_ctl);
+		break;
+	case PCMD3180:
+		dig_ctl_info.gain = pcmd3180_dig_gain_tlv;
+		dig_ctl_info.pcmdev_ctrl = pcmd3180_digital_gain_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(pcmd3180_digital_gain_ctl);
+		break;
+	case TAA5212:
+	case TAA5412:
+		analog_ctl_info.gain = taa5412_fine_gain_tlv;
+		analog_ctl_info.pcmdev_ctrl = taa5412_fine_gain_ctl;
+		analog_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(taa5412_fine_gain_ctl);
+		dig_ctl_info.gain = taa5412_dig_vol_tlv;
+		dig_ctl_info.pcmdev_ctrl = taa5412_digital_volume_ctl;
+		dig_ctl_info.ctrl_array_size =
+			ARRAY_SIZE(taa5412_digital_volume_ctl);
+		break;
+	}
+
+	nr_controls += pcm_dev->ndev * (dig_ctl_info.ctrl_array_size +
+		analog_ctl_info.ctrl_array_size);
+	pcmdev_controls = devm_kzalloc(pcm_dev->dev,
+		nr_controls * sizeof(struct snd_kcontrol_new),
+		GFP_KERNEL);
+	if (!pcmdev_controls) {
+		ret = -ENOMEM;
+		pcm_dev->pcm_ctrl.pcmdevice_profile_controls = NULL;
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.pcmdevice_profile_controls = pcmdev_controls;
+	/* Create a mixer item for selecting the active profile */
+	name = devm_kzalloc(pcm_dev->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+		GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "PCMDEVICE Profile id");
+	pcmdev_controls[mix_index].name = name;
+	pcmdev_controls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	pcmdev_controls[mix_index].info = pcmdevice_info_profile;
+	pcmdev_controls[mix_index].get = pcmdevice_get_profile_id;
+	pcmdev_controls[mix_index].put = pcmdevice_set_profile_id;
+	mix_index++;
+
+	if (analog_ctl_info.ctrl_array_size == 0)
+		goto dig_ctrl;
+
+	for (dev = 0; dev < pcm_dev->ndev; dev++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev, "mix_idx: %d nr_controls = %d\n",
+				mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= analog_ctl_info.ctrl_array_size;
+			chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx = %d nr_controls = %d\n",
+					__func__, mix_index,
+					nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				(pcm_dev->chip_id != TAA5412) ?
+				"%s-i2c-%d-dev%d-ch%d-ana-gain" :
+				"%s-i2c-%d-dev%d-ch%d-fine-gain",
+				pcm_dev->dev_name, adap->nr, dev, chn);
+			pcmdev_controls[mix_index].tlv.p =
+				analog_ctl_info.gain;
+			analog_ctl_info.pcmdev_ctrl[chn - 1].dev_no = dev;
+			pcmdev_controls[mix_index].private_value =
+				(unsigned long)
+				&analog_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcmdevice_get_volsw;
+			pcmdev_controls[mix_index].put = pcmdevice_put_volsw;
+			mix_index++;
+		}
+	}
+
+dig_ctrl:
+	if (dig_ctl_info.ctrl_array_size == 0)
+		goto add_ctrls;
+
+	for (dev = 0; dev < pcm_dev->ndev; dev++) {
+		if (mix_index >= nr_controls) {
+			dev_dbg(pcm_dev->dev, "%s: mix_idx: %d nr_ctrls: %d\n",
+				__func__, mix_index, nr_controls);
+			break;
+		}
+		for (chn = 1; chn <= dig_ctl_info.ctrl_array_size; chn++) {
+			if (mix_index >= nr_controls) {
+				dev_dbg(pcm_dev->dev,
+					"%s: mix_idx: %d nr_ctrls: %d\n",
+					__func__, mix_index, nr_controls);
+				break;
+			}
+			name = devm_kzalloc(pcm_dev->dev,
+				SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
+			if (!name) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
+				"%s-i2c-%d-dev%d-ch%d-digi-gain",
+				pcm_dev->dev_name, adap->nr, dev, chn);
+			pcmdev_controls[mix_index].tlv.p = dig_ctl_info.gain;
+			dig_ctl_info.pcmdev_ctrl[chn - 1].dev_no = dev;
+			pcmdev_controls[mix_index].private_value =
+					(unsigned long)
+					&dig_ctl_info.pcmdev_ctrl[chn - 1];
+			pcmdev_controls[mix_index].name = name;
+			pcmdev_controls[mix_index].access =
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			pcmdev_controls[mix_index].iface =
+				SNDRV_CTL_ELEM_IFACE_MIXER;
+			pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
+			pcmdev_controls[mix_index].get = pcmdevice_get_volsw;
+			pcmdev_controls[mix_index].put = pcmdevice_put_volsw;
+			mix_index++;
+		}
+	}
+
+add_ctrls:
+	ret = snd_soc_add_component_controls(pcm_dev->component,
+		pcmdev_controls,
+		nr_controls < mix_index ? nr_controls : mix_index);
+	if (ret) {
+		dev_err(pcm_dev->dev, "%s: add_controls error = %d\n",
+			__func__, ret);
+		goto out;
+	}
+	pcm_dev->pcm_ctrl.nr_controls =
+		nr_controls < mix_index ? nr_controls : mix_index;
+out:
+	return ret;
+}
+
+static int pcmdevice_create_controls(struct pcmdevice_priv *pcm_dev)
+{
+	int ret;
+
+	if (pcm_dev->chip_id == PCM1690)
+		ret = pcm1690_ctrl_add(pcm_dev);
+	else
+		ret = pcmdev_ctrl_add(pcm_dev);
+
+	return ret;
+}
+
+static void pcmdevice_config_info_remove(void *pContext)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *) pContext;
+	struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
+	struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
+	int i, j;
+
+	if (!cfg_info)
+		return;
+	for (i = 0; i < regbin->ncfgs; i++) {
+		if (!cfg_info[i])
+			continue;
+		if (cfg_info[i]->blk_data) {
+			for (j = 0; j < (int)cfg_info[i]->real_nblocks; j++) {
+				if (!cfg_info[i]->blk_data[j])
+					continue;
+				kfree(cfg_info[i]->blk_data[j]->regdata);
+				kfree(cfg_info[i]->blk_data[j]);
+			}
+			kfree(cfg_info[i]->blk_data);
+		}
+		kfree(cfg_info[i]);
+	}
+	kfree(cfg_info);
+}
+
+static void pcmdev_regbin_ready(const struct firmware *fmw, void *ctxt)
+{
+	struct pcmdevice_config_info **cfg_info;
+	struct pcmdevice_priv *pcm_dev = ctxt;
+	struct pcmdevice_regbin_hdr *fw_hdr;
+	struct pcmdevice_regbin *regbin;
+	unsigned int total_config_sz = 0;
+	int offset = 0, ret = 0, i;
+	unsigned char *buf;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	regbin = &(pcm_dev->regbin);
+	fw_hdr = &(regbin->fw_hdr);
+	if (!fmw || !fmw->data) {
+		dev_err(pcm_dev->dev, "Failed to read %s\n",
+			pcm_dev->regbin_name);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	buf = (unsigned char *)fmw->data;
+
+	fw_hdr->img_sz = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+	if (fw_hdr->img_sz != fmw->size) {
+		dev_err(pcm_dev->dev, "File size not match, %d %u",
+			(int)fmw->size, fw_hdr->img_sz);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	fw_hdr->checksum = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+	fw_hdr->binary_version_num = be32_to_cpup((__be32 *)&buf[offset]);
+	if (fw_hdr->binary_version_num < 0x103) {
+		dev_err(pcm_dev->dev, "Bin version 0x%04x is out of date",
+			fw_hdr->binary_version_num);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	offset += 4;
+	fw_hdr->drv_fw_version = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 8;
+	fw_hdr->plat_type = buf[offset];
+	offset += 1;
+	fw_hdr->dev_family = buf[offset];
+	offset += 1;
+	fw_hdr->reserve = buf[offset];
+	offset += 1;
+	fw_hdr->ndev = buf[offset];
+	offset += 1;
+	if (fw_hdr->ndev != pcm_dev->ndev) {
+		dev_err(pcm_dev->dev, "Invalid ndev(%u)\n", fw_hdr->ndev);
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	if (offset + PCMDEVICE_DEVICE_SUM > fw_hdr->img_sz) {
+		dev_err(pcm_dev->dev, "regbin_ready: Out of boundary!\n");
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+
+	for (i = 0; i < PCMDEVICE_DEVICE_SUM; i++, offset++)
+		fw_hdr->devs[i] = buf[offset];
+
+	fw_hdr->nconfig = be32_to_cpup((__be32 *)&buf[offset]);
+	offset += 4;
+
+	for (i = 0; i < PCMDEVICE_CONFIG_SUM; i++) {
+		fw_hdr->config_size[i] = be32_to_cpup((__be32 *)&buf[offset]);
+		offset += 4;
+		total_config_sz += fw_hdr->config_size[i];
+	}
+
+	if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) {
+		dev_err(pcm_dev->dev, "Bin file error!\n");
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL);
+	if (!cfg_info) {
+		pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+		goto out;
+	}
+	regbin->cfg_info = cfg_info;
+	regbin->ncfgs = 0;
+	for (i = 0; i < (int)fw_hdr->nconfig; i++) {
+		cfg_info[i] = pcmdevice_add_config(ctxt, &buf[offset],
+				fw_hdr->config_size[i], &ret);
+		if (ret) {
+			/* In case the bin file is partially destroyed. */
+			if (regbin->ncfgs == 0)
+				pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
+			break;
+		}
+		offset += (int)fw_hdr->config_size[i];
+		regbin->ncfgs += 1;
+	}
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_OK)
+		pcmdevice_create_controls(pcm_dev);
+out:
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED)
+		pcmdevice_config_info_remove(pcm_dev);
+
+	mutex_unlock(&pcm_dev->codec_lock);
+	if (fmw)
+		release_firmware(fmw);
+}
+
+static int pcmdevice_codec_probe(struct snd_soc_component *codec)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+	struct i2c_adapter *adap = pcm_dev->client->adapter;
+	int ret;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	pcm_dev->component = codec;
+	pcm_dev->fw_state = PCMDEVICE_FW_LOAD_OK;
+	strscpy(pcm_dev->dev_name, pcmdevice_i2c_id[pcm_dev->chip_id].name,
+		sizeof(pcm_dev->dev_name));
+
+	/* device-name[defined in pcmdevice_i2c_id]-i2c-bus_id[0,1,...,N]-
+	 * sum[1,2,...,4]dev-reg.bin stores the firmware including register
+	 * setting and params for different filters inside chips, it must be
+	 * copied into firmware folder. The same types of pcmdevices sitting
+	 * on the same i2c bus will be aggregated as one single codec,
+	 * all of them share the same bin file.
+	 */
+	scnprintf(pcm_dev->regbin_name, PCMDEVICE_REGBIN_FILENAME_LEN,
+		"%s-i2c-%d-%udev-reg.bin", pcm_dev->dev_name, adap->nr,
+		pcm_dev->ndev);
+
+	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+		pcm_dev->regbin_name, pcm_dev->dev, GFP_KERNEL, pcm_dev,
+		pcmdev_regbin_ready);
+	if (ret) {
+		dev_err(pcm_dev->dev, "load %s error = %d\n",
+			pcm_dev->regbin_name, ret);
+		goto out;
+	}
+
+	if (pcm_dev->hw_rst) {
+		gpiod_set_value_cansleep(pcm_dev->hw_rst, 0);
+		usleep_range(500, 1000);
+		gpiod_set_value_cansleep(pcm_dev->hw_rst, 1);
+	} else {
+		if (pcm_dev->sw_rst)
+			pcm_dev->sw_rst(pcm_dev);
+	}
+
+out:
+	mutex_unlock(&pcm_dev->codec_lock);
+	return ret;
+}
+
+
+static void pcmdevice_codec_remove(struct snd_soc_component *codec)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+
+	if (!pcm_dev)
+		return;
+	mutex_lock(&pcm_dev->codec_lock);
+	pcmdevice_config_info_remove(pcm_dev);
+	mutex_unlock(&pcm_dev->codec_lock);
+}
+
+static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("ASI1 OUT", "ASI1 Capture",
+		0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_OUTPUT("OUT"),
+	SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route pcmdevice_audio_map[] = {
+	{"OUT", NULL, "ASI"},
+	{"ASI1 OUT", NULL, "MIC"},
+};
+
+static const struct snd_soc_component_driver
+	soc_codec_driver_pcmdevice = {
+	.probe			= pcmdevice_codec_probe,
+	.remove			= pcmdevice_codec_remove,
+	.dapm_widgets		= pcmdevice_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(pcmdevice_dapm_widgets),
+	.dapm_routes		= pcmdevice_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(pcmdevice_audio_map),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 0,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static int pcmdevice_process_block(void *ctxt, unsigned char *data,
+	unsigned char dev_idx, int sublocksize)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	int subblk_offset = 2, chn, chnend, rc;
+	unsigned char subblk_typ = data[1];
+
+	if (dev_idx) {
+		chn = dev_idx - 1;
+		chnend = dev_idx;
+	} else {
+		chn = 0;
+		chnend = pcm_dev->ndev;
+	}
+
+	for (; chn < chnend; chn++) {
+		switch (subblk_typ) {
+		case PCMDEVICE_CMD_SING_W: {
+			unsigned short len = be16_to_cpup((__be16 *)&data[2]);
+			int i = 0;
+
+			subblk_offset += 2;
+			if (subblk_offset + 4 * len > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"process_block: Out of boundary\n");
+				break;
+			}
+
+			for (i = 0; i < len; i++) {
+				rc = pcmdev_dev_write(pcm_dev, chn,
+					PCMDEVICE_REG(data[subblk_offset + 1],
+						data[subblk_offset + 2]),
+					data[subblk_offset + 3]);
+				if (rc < 0)
+					dev_err(pcm_dev->dev,
+						"single write error\n");
+
+				subblk_offset += 4;
+			}
+		}
+		break;
+		case PCMDEVICE_CMD_BURST: {
+			unsigned short len = be16_to_cpup((__be16 *)&data[2]);
+
+			subblk_offset += 2;
+			if (subblk_offset + 4 + len > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"BURST Out of boundary\n");
+				break;
+			}
+			if (len % 4) {
+				dev_err(pcm_dev->dev,
+					"Bst-len(%u)not div by 4\n", len);
+				break;
+			}
+			rc = pcmdev_dev_bulk_write(pcm_dev, chn,
+				PCMDEVICE_REG(data[subblk_offset + 1],
+				data[subblk_offset + 2]),
+				&(data[subblk_offset + 4]), len);
+			if (rc < 0)
+				dev_err(pcm_dev->dev,
+					"bulk_write error = %d\n", rc);
+
+			subblk_offset += (len + 4);
+		}
+			break;
+		case PCMDEVICE_CMD_DELAY: {
+			unsigned int delay_time = 0;
+
+			if (subblk_offset + 2 > sublocksize) {
+				dev_err(pcm_dev->dev,
+					"deley Out of boundary\n");
+				break;
+			}
+			delay_time = be16_to_cpup((__be16 *)&data[2]) * 1000;
+			usleep_range(delay_time, delay_time + 50);
+			subblk_offset += 2;
+		}
+			break;
+		case PCMDEVICE_CMD_FIELD_W:
+		if (subblk_offset + 6 > sublocksize) {
+			dev_err(pcm_dev->dev,
+				"process_block: bit write Out of memory\n");
+			break;
+		}
+			rc = pcmdev_dev_update_bits(pcm_dev, chn,
+				PCMDEVICE_REG(data[subblk_offset + 3],
+				data[subblk_offset + 4]),
+				data[subblk_offset + 1],
+				data[subblk_offset + 5]);
+		if (rc < 0)
+			dev_err(pcm_dev->dev,
+				"process_block: update_bits error = %d\n", rc);
+
+		subblk_offset += 6;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return subblk_offset;
+}
+
+
+static void pcmdevice_select_cfg_blk(void *ctxt, int conf_no,
+	unsigned char block_type)
+{
+	struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
+	struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
+	struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
+	struct pcmdevice_block_data **blk_data;
+	int j, k, chn, chnend;
+
+	if (conf_no >= regbin->ncfgs || conf_no < 0 || NULL == cfg_info) {
+		dev_err(pcm_dev->dev, "conf_no should be less than %u\n",
+			regbin->ncfgs);
+		goto out;
+	}
+	blk_data = cfg_info[conf_no]->blk_data;
+
+	for (j = 0; j < (int)cfg_info[conf_no]->real_nblocks; j++) {
+		unsigned int length = 0, rc = 0;
+
+		if (block_type > 5 || block_type < 2) {
+			dev_err(pcm_dev->dev,
+				"block_type should be in range from 2 to 5\n");
+			goto out;
+		}
+		if (block_type != blk_data[j]->block_type)
+			continue;
+
+		for (k = 0; k < (int)blk_data[j]->n_subblks; k++) {
+			if (blk_data[j]->dev_idx) {
+				chn = blk_data[j]->dev_idx - 1;
+				chnend = blk_data[j]->dev_idx;
+			} else {
+				chn = 0;
+				chnend = pcm_dev->ndev;
+			}
+
+			rc = pcmdevice_process_block(pcm_dev,
+				blk_data[j]->regdata + length,
+				blk_data[j]->dev_idx,
+				blk_data[j]->block_size - length);
+			length += rc;
+			if (blk_data[j]->block_size < length) {
+				dev_err(pcm_dev->dev,
+					"%s: %u %u out of boundary\n",
+					__func__, length,
+					blk_data[j]->block_size);
+				break;
+			}
+		}
+		if (length != blk_data[j]->block_size)
+			dev_err(pcm_dev->dev, "%s: %u %u size is not same\n",
+				__func__, length, blk_data[j]->block_size);
+	}
+
+out:
+	return;
+}
+
+static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct snd_soc_component *codec = dai->component;
+	struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
+	unsigned char block_type;
+
+	if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) {
+		dev_err(pcm_dev->dev, "DSP bin file not loaded\n");
+		return -EINVAL;
+	}
+
+	if (mute)
+		block_type = PCMDEVICE_BIN_BLK_PRE_SHUTDOWN;
+	else
+		block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP;
+
+	mutex_lock(&pcm_dev->codec_lock);
+	pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type);
+	mutex_unlock(&pcm_dev->codec_lock);
+	return 0;
+}
+
+static int pcmdevice_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(codec_dai);
+
+	pcm_dev->sysclk = freq;
+
+	return 0;
+}
+
+static int pcmdevice_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(dai);
+	unsigned int fsrate;
+	unsigned int slot_width;
+	int bclk_rate;
+	int rc = 0;
+
+	fsrate = params_rate(params);
+	switch (fsrate) {
+	case 48000:
+		break;
+	case 44100:
+		break;
+	default:
+		dev_err(pcm_dev->dev,
+			"%s: incorrect sample rate = %u\n",
+			__func__, fsrate);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	slot_width = params_width(params);
+	switch (slot_width) {
+	case 16:
+		break;
+	case 20:
+		break;
+	case 24:
+		break;
+	case 32:
+		break;
+	default:
+		dev_err(pcm_dev->dev, "%s: incorrect slot width = %u\n",
+			__func__, slot_width);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	bclk_rate = snd_soc_params_to_bclk(params);
+	if (bclk_rate < 0) {
+		dev_err(pcm_dev->dev, "%s: incorrect bclk rate = %d\n",
+			__func__, bclk_rate);
+		rc = bclk_rate;
+		goto out;
+	}
+
+out:
+	return rc;
+}
+
+static int pcmdevice_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *codec = dai->component;
+	struct pcmdevice_priv *pcm_priv = snd_soc_component_get_drvdata(codec);
+	int ret = 0;
+
+	if (pcm_priv->fw_state != PCMDEVICE_FW_LOAD_OK) {
+		dev_err(pcm_priv->dev, "DSP bin file not loaded\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dai_ops pcmdevice_dai_ops = {
+	.mute_stream = pcmdevice_mute,
+	.startup = pcmdevice_startup,
+	.hw_params = pcmdevice_hw_params,
+	.set_sysclk = pcmdevice_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver pcmdevice_dai_driver[] = {
+	{
+		.name = "pcmdevice-codec",
+		.capture = {
+			.stream_name	 = "Capture",
+			.channels_min	 = 2,
+			.channels_max	 = PCMDEVICE_MAX_CHANNELS,
+			.rates		 = PCMDEVICE_RATES,
+			.formats	 = PCMDEVICE_FORMATS,
+		},
+		.playback = {
+			.stream_name	 = "Playback",
+			.channels_min	 = 2,
+			.channels_max	 = PCMDEVICE_MAX_CHANNELS,
+			.rates		 = PCMDEVICE_RATES,
+			.formats	 = PCMDEVICE_FORMATS,
+		},
+		.ops = &pcmdevice_dai_ops,
+		.symmetric_rate = 1,
+	}
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id pcmdevice_of_match[] = {
+	{ .compatible = "ti,adc3120"  },
+	{ .compatible = "ti,adc5120"  },
+	{ .compatible = "ti,adc6120"  },
+	{ .compatible = "ti,dix4192"  },
+	{ .compatible = "ti,pcm1690"  },
+	{ .compatible = "ti,pcm3120"  },
+	{ .compatible = "ti,pcm3140"  },
+	{ .compatible = "ti,pcm5120"  },
+	{ .compatible = "ti,pcm5140"  },
+	{ .compatible = "ti,pcm6120"  },
+	{ .compatible = "ti,pcm6140"  },
+	{ .compatible = "ti,pcm6240"  },
+	{ .compatible = "ti,pcm6260"  },
+	{ .compatible = "ti,pcm9211"  },
+	{ .compatible = "ti,pcmd3140" },
+	{ .compatible = "ti,pcmd3180" },
+	{ .compatible = "ti,pcmd512x" },
+	{ .compatible = "ti,taa5212"  },
+	{ .compatible = "ti,taa5412"  },
+	{ .compatible = "ti,tad5212"  },
+	{ .compatible = "ti,tad5412"  },
+	{},
+};
+MODULE_DEVICE_TABLE(of, pcmdevice_of_match);
+#endif
+
+static const struct regmap_range_cfg pcmdevice_ranges[] = {
+	{
+		.range_min = 0,
+		.range_max = 256 * 128,
+		.selector_reg = PCMDEVICE_PAGE_SELECT,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 128,
+	},
+};
+
+static const struct regmap_config pcmdevice_i2c_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_RBTREE,
+	.ranges = pcmdevice_ranges,
+	.num_ranges = ARRAY_SIZE(pcmdevice_ranges),
+	.max_register = 256 * 128,
+};
+
+static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev)
+{
+	if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
+		gpio_free(pcm_dev->irq_info.gpio);
+		free_irq(pcm_dev->irq_info.nmb, pcm_dev);
+	}
+	mutex_destroy(&pcm_dev->codec_lock);
+}
+
+static int pcmdevice_i2c_probe(struct i2c_client *i2c)
+{
+	const struct i2c_device_id *id = i2c_match_id(pcmdevice_i2c_id, i2c);
+	struct pcmdevice_priv *pcm_dev;
+	struct device_node *np;
+	unsigned int dev_addrs[PCMDEVICE_MAX_DEVICES];
+	int ret = 0, i = 0, ndev = 0;
+#ifdef CONFIG_OF
+	const __be32 *reg, *reg_end;
+	int len, sw, aw;
+#endif
+
+	pcm_dev = devm_kzalloc(&i2c->dev, sizeof(*pcm_dev), GFP_KERNEL);
+	if (!pcm_dev) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	pcm_dev->chip_id = (id != NULL) ? id->driver_data : 0;
+
+	pcm_dev->dev = &i2c->dev;
+	pcm_dev->client = i2c;
+
+	if (pcm_dev->chip_id >= MAX_DEVICE)
+		pcm_dev->chip_id = 0;
+
+	pcm_dev->regmap = devm_regmap_init_i2c(i2c,
+		&pcmdevice_i2c_regmap);
+	if (IS_ERR(pcm_dev->regmap)) {
+		ret = PTR_ERR(pcm_dev->regmap);
+		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto out;
+	}
+
+	np = pcm_dev->dev->of_node;
+#ifdef CONFIG_OF
+	aw = of_n_addr_cells(np);
+	sw = of_n_size_cells(np);
+	if (sw == 0) {
+		reg = (const __be32 *)of_get_property(np,
+			"reg", &len);
+		reg_end = reg + len/sizeof(*reg);
+		ndev = 0;
+		do {
+			dev_addrs[ndev] = of_read_number(reg, aw);
+			reg += aw;
+			ndev++;
+		} while (reg < reg_end);
+	} else {
+		ndev = 1;
+		dev_addrs[0] = i2c->addr;
+	}
+#else
+	ndev = 1;
+	dev_addrs[0] = i2c->addr;
+#endif
+	pcm_dev->irq_info.gpio = of_irq_get(np, 0);
+
+	for (i = 0; i < ndev; i++)
+		pcm_dev->addr[i] = dev_addrs[i];
+
+	pcm_dev->ndev = ndev;
+
+	pcm_dev->hw_rst = devm_gpiod_get_optional(&i2c->dev,
+			"reset-gpios", GPIOD_OUT_HIGH);
+	if (IS_ERR(pcm_dev->hw_rst))
+		dev_dbg(&i2c->dev, "%s: No reset GPIO, no side-effect\n",
+			__func__);
+
+	if (pcm_dev->chip_id == PCM9211 || pcm_dev->chip_id == PCM1690)
+		pcm_dev->sw_rst = pcm9211_sw_rst;
+	else
+		pcm_dev->sw_rst = pcmdevice_sw_rst;
+
+	if (pcm_dev->chip_id == PCM1690)
+		goto skip_interrupt;
+	if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
+		dev_dbg(pcm_dev->dev, "irq-gpio = %d",
+			pcm_dev->irq_info.gpio);
+
+		ret = gpio_request(pcm_dev->irq_info.gpio, "PCMDEV-IRQ");
+		if (!ret) {
+			int gpio = pcm_dev->irq_info.gpio;
+
+			gpio_direction_input(gpio);
+			pcm_dev->irq_info.nmb = gpio_to_irq(gpio);
+
+		} else
+			dev_err(pcm_dev->dev, "%s: GPIO %d request error\n",
+				__func__, pcm_dev->irq_info.gpio);
+	} else
+		dev_err(pcm_dev->dev, "Looking up irq-gpio failed %d\n",
+			pcm_dev->irq_info.gpio);
+
+
+skip_interrupt:
+	mutex_init(&pcm_dev->codec_lock);
+
+	i2c_set_clientdata(i2c, pcm_dev);
+
+	ret = devm_snd_soc_register_component(&i2c->dev,
+		&soc_codec_driver_pcmdevice, pcmdevice_dai_driver,
+		ARRAY_SIZE(pcmdevice_dai_driver));
+	if (ret < 0)
+		dev_err(&i2c->dev, "probe register comp failed %d\n", ret);
+
+out:
+	if (ret < 0)
+		pcmdevice_remove(pcm_dev);
+	return ret;
+}
+
+static void pcmdevice_i2c_remove(struct i2c_client *i2c)
+{
+	struct pcmdevice_priv *pcm_dev = i2c_get_clientdata(i2c);
+
+	pcmdevice_remove(pcm_dev);
+}
+
+static struct i2c_driver pcmdevice_i2c_driver = {
+	.driver = {
+		.name = "pcmdevice-codec",
+		.of_match_table = of_match_ptr(pcmdevice_of_match),
+	},
+	.probe	= pcmdevice_i2c_probe,
+	.remove = pcmdevice_i2c_remove,
+	.id_table = pcmdevice_i2c_id,
+};
+
+module_i2c_driver(pcmdevice_i2c_driver);
+
+MODULE_AUTHOR("Shenghao Ding <shenghao-ding@ti.com>");
+MODULE_DESCRIPTION("ASoC PCM6240 Family Audio ADC/DAC/Router Driver");
+MODULE_LICENSE("GPL");