ASoC: nau8825: Add driver for headset chip Nuvoton 8825
diff mbox

Message ID 1438038837-339-1-git-send-email-anatol.pomozov@gmail.com
State New
Headers show

Commit Message

anatol.pomozov@gmail.com July 27, 2015, 11:13 p.m. UTC
Signed-off-by: Anatol Pomozov <anatol.pomozov@gmail.com>
---
 .../devicetree/bindings/sound/nau8825.txt          |  14 +
 sound/soc/codecs/Kconfig                           |   4 +
 sound/soc/codecs/Makefile                          |   2 +
 sound/soc/codecs/nau8825.c                         | 860 +++++++++++++++++++++
 sound/soc/codecs/nau8825.h                         | 258 +++++++
 5 files changed, 1138 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/nau8825.txt
 create mode 100644 sound/soc/codecs/nau8825.c
 create mode 100644 sound/soc/codecs/nau8825.h

Comments

Mark Brown July 31, 2015, 6:27 p.m. UTC | #1
On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:

Looks mostly good, a few things below though:

> +		break;
> +	case 1:
> +		dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n");

This is far too noisy - these should be dev_dbg() at most.

> +		} else {
> +			dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
> +			nau8825_eject_jack(nau8825);
> +		}

Things like this that aren't supposed to happen are fine but normal
operation shouild be quiet.

> +	/* VMID Enable and Tieoff */
> +	regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060);

You're leavinng VMID enabled all the timme?

> +	/* Jack Detect pull up (High=eject, Low=insert) */
> +	regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800);

This seems like it should be a board setting?

> +	/* Setup SAR ADC */
> +	regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);

Lots of magic numbers in these things...  some of them I can guess
what's going on but this one is a bit obscure, perhaps it should be user
controllable?

> +	/* Setup ADC x128 OSR */
> +	regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
> +	/* Setup DAC x128 OSR */
> +	regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);

I'd expect this to be user controllable.

> +static int nau8825_i2c_probe(struct i2c_client *i2c,
> +	const struct i2c_device_id *id)
> +{
> +	struct device *dev = &i2c->dev;
> +	struct nau8825 *nau8825;
> +	int ret, value;
> +
> +	nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL);
> +	if (!nau8825)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(i2c, nau8825);
> +
> +	nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config);
> +	if (IS_ERR(nau8825->regmap))
> +		return PTR_ERR(nau8825->regmap);
> +	nau8825->irq = i2c->irq;
> +	nau8825->dev = dev;
> +
> +	nau8825_reset_chip(nau8825->regmap);
> +	ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
> +			ret);
> +		return ret;
> +	}
> +	if ((value & NAU8825_SOFTWARE_ID_MASK) !=
> +			NAU8825_SOFTWARE_ID_NAU8825) {
> +		dev_err(dev, "Not a NAU8825 chip\n");
> +		return -ENODEV;
> +	}
> +
> +	return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver,
> +			&nau8825_dai, 1);
> +}

I'd expect any initial register initialistion to happen here (if only so
we save power until the card registers).
anatol.pomozov@gmail.com Aug. 4, 2015, 3:13 a.m. UTC | #2
Hi

On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown <broonie@kernel.org> wrote:
> On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
>
> Looks mostly good, a few things below though:
>
>> +             break;
>> +     case 1:
>> +             dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n");
>
> This is far too noisy - these should be dev_dbg() at most.

Done

>
>> +             } else {
>> +                     dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
>> +                     nau8825_eject_jack(nau8825);
>> +             }
>
> Things like this that aren't supposed to happen are fine but normal
> operation shouild be quiet.
>
>> +     /* VMID Enable and Tieoff */
>> +     regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060);
>
> You're leavinng VMID enabled all the timme?

Currently yes. One of the next steps is to look at the power
management for this chip (set_bias and suspend/resume). I need to work
with Nuvoton HW engineers to find correct sequence of steps.

>
>> +     /* Jack Detect pull up (High=eject, Low=insert) */
>> +     regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800);
>
> This seems like it should be a board setting?

Created DTS property for it. Thanks.

>
>> +     /* Setup SAR ADC */
>> +     regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
>
> Lots of magic numbers in these things...

These init config settings recommended by Nuvoton engineers. I found
that chip requires quite a lot of initial preconfiguration to get it
into operating state.

> some of them I can guess
> what's going on but this one is a bit obscure, perhaps it should be user
> controllable?

This setting configures SARADC (ADC for sensing button presses). It
sets sample rate, series resistor and ADC voltage range. It does not
look like they need to be user-controllable.

>
>> +     /* Setup ADC x128 OSR */Moved to
>> +     regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
>> +     /* Setup DAC x128 OSR */
>> +     regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
>
> I'd expect this to be user controllable.

The oversampling configuration is important for chip audio quality.
There is audible hissing without these settings.

My understanding that all users need to set these values and it is
better to move it to the driver initialization sequence.

>> +static int nau8825_i2c_probe(struct i2c_client *i2c,
>> +     const struct i2c_device_id *id)
>> +{
>> +     struct device *dev = &i2c->dev;
>> +     struct nau8825 *nau8825;
>> +     int ret, value;
>> +
>> +     nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL);
>> +     if (!nau8825)
>> +             return -ENOMEM;
>> +
>> +     i2c_set_clientdata(i2c, nau8825);
>> +
>> +     nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config);
>> +     if (IS_ERR(nau8825->regmap))
>> +             return PTR_ERR(nau8825->regmap);
>> +     nau8825->irq = i2c->irq;
>> +     nau8825->dev = dev;
>> +
>> +     nau8825_reset_chip(nau8825->regmap);
>> +     ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value);
>> +     if (ret < 0) {
>> +             dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
>> +                     ret);
>> +             return ret;
>> +     }
>> +     if ((value & NAU8825_SOFTWARE_ID_MASK) !=
>> +                     NAU8825_SOFTWARE_ID_NAU8825) {
>> +             dev_err(dev, "Not a NAU8825 chip\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver,
>> +                     &nau8825_dai, 1);
>> +}
>
> I'd expect any initial register initialistion to happen here (if only so
> we save power until the card registers).

Moved most of the initialization here. The only part is left in
codec_probe() is interruption initialization via I2S master mode
toggling. That toggling trick depends on MCLK signal to initialize
interruption block correctly. In our case (a tegra SoC device) MCLK is
initialized later at audio platform driver probe.
Mark Brown Aug. 4, 2015, 9:47 a.m. UTC | #3
On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:
> On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown <broonie@kernel.org> wrote:
> > On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:

> >> +     /* Setup SAR ADC */
> >> +     regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);

> > Lots of magic numbers in these things...

> These init config settings recommended by Nuvoton engineers. I found
> that chip requires quite a lot of initial preconfiguration to get it
> into operating state.

Random register write lists from the vendor for documented registers are
not generally a good thing to rely on too much, they frequently set up
use case specific things in the middle of other perhaps useful changes.

> > some of them I can guess
> > what's going on but this one is a bit obscure, perhaps it should be user
> > controllable?

> This setting configures SARADC (ADC for sensing button presses). It
> sets sample rate, series resistor and ADC voltage range. It does not
> look like they need to be user-controllable.

Those sound like they may well be system dependent, the sample rate
will most likely feed back into the sensitivity of the detection (how
long the button needs to be pressed for and so on) and the other bits
sound like they could change depending on the configuration of the
accessory.

> >> +     /* Setup ADC x128 OSR */Moved to
> >> +     regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
> >> +     /* Setup DAC x128 OSR */
> >> +     regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);

> > I'd expect this to be user controllable.

> The oversampling configuration is important for chip audio quality.
> There is audible hissing without these settings.

> My understanding that all users need to set these values and it is
> better to move it to the driver initialization sequence.

That sounds like spectacularly poor quality of implementation, but even
if there's a noticeable reduction in audio quality the control should
still be given to the user, they may have an application where power is
much more important than audio quality (eg, if they are doing some form
of recognition on the audio then all they need is something good enough
for their algorithm).

> > I'd expect any initial register initialistion to happen here (if only so
> > we save power until the card registers).

> Moved most of the initialization here. The only part is left in
> codec_probe() is interruption initialization via I2S master mode
> toggling. That toggling trick depends on MCLK signal to initialize
> interruption block correctly. In our case (a tegra SoC device) MCLK is
> initialized later at audio platform driver probe.

If you need to control MCLK you should be using the clock API to get
MCLK which you can do in the driver model probe.  Relying on the machine
driver to control MCLK for you in this way is at best fragile.
anatol.pomozov@gmail.com Aug. 11, 2015, 12:32 a.m. UTC | #4
Hi

On Tue, Aug 4, 2015 at 2:47 AM, Mark Brown <broonie@kernel.org> wrote:
> On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:
>> On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown <broonie@kernel.org> wrote:
>> > On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
>
>> >> +     /* Setup SAR ADC */
>> >> +     regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
>
>> > Lots of magic numbers in these things...

Okay, I moved the magic number to board configuration options. Now
most of these numbers can be configured via dts.

>> >> +     /* Setup ADC x128 OSR */Moved to
>> >> +     regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
>> >> +     /* Setup DAC x128 OSR */
>> >> +     regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
>
>> > I'd expect this to be user controllable.
>
>> The oversampling configuration is important for chip audio quality.
>> There is audible hissing without these settings.
>
>> My understanding that all users need to set these values and it is
>> better to move it to the driver initialization sequence.
>
> That sounds like spectacularly poor quality of implementation, but even
> if there's a noticeable reduction in audio quality the control should
> still be given to the user, they may have an application where power is
> much more important than audio quality (eg, if they are doing some form
> of recognition on the audio then all they need is something good enough
> for their algorithm).

Moved it to ALSA controls. Though I still feel a bit uncomfortable
that the default control value is not usable.
I have to set "ADC Decimation Rate" and "DAC Oversampling Rate"
controls in userspace from its defaults to "128".

>> > I'd expect any initial register initialistion to happen here (if only so
>> > we save power until the card registers).
>
>> Moved most of the initialization here. The only part is left in
>> codec_probe() is interruption initialization via I2S master mode
>> toggling. That toggling trick depends on MCLK signal to initialize
>> interruption block correctly. In our case (a tegra SoC device) MCLK is
>> initialized later at audio platform driver probe.
>
> If you need to control MCLK you should be using the clock API to get
> MCLK which you can do in the driver model probe.  Relying on the machine
> driver to control MCLK for you in this way is at best fragile.

After discussing it with my teammates who works on Intel+NAU8825
platform I found that they use MCLK only at playback/capture time. The
rest of the time VCO is suppose to be sysclk source. Thus using
get_clk() is not flexible for all cases.

What I propose is to initialize driver with VCO as a sysclk source.
And later a platform driver can change it to MCLK. Added set_sysclk()
stub that works well with VCO->MCKL transition, tested it on TegraX1.
Follow-up changes should improve VCO/FLL configuration.

> > +     codec: nau8825@1a {
> > +             compatible = "nuvoton,nau8825";
> > +             reg = <0x1a>;
> > +             nuvoton,jkdet-pullup = "true";
>
> ...this isn't how boolean properties are done, the property simply needs
> to exist.

Fixed.

>
> > +static int nau8825_output_driver_event(struct snd_soc_dapm_widget *w,
> > +     struct snd_kcontrol *kcontrol, int event)
> > +{
> > +     struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
> > +     struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
> > +     struct regmap *regmap = nau8825->regmap;
> > +
> > +     if (SND_SOC_DAPM_EVENT_ON(event)) {
> > +             /* Power Up L and R Output Drivers */
> > +             regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3c,
> > +                     0x3c);
> > +             /* Power up Main Output Drivers (The main driver must be turned
> > +                on after the predriver to avoid pops) */
> > +             regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3,
> > +                     0x3);
> > +     } else {
> > +             /* Power Down L and R Output Drivers */
> > +             regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3f,
> > +                     0x0);
>
> You should be able to implement this with _S widgets without requiring
> explicit code, they're designed for exactly this situation.

I've decided to split this function into 6 different widgets. And it
seems enough. No audible pop/clicks here.

>
> > +int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
> > +                             struct snd_soc_jack *jack)
> > +{
> > +     struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
> > +
> > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
> > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
> > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
> > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
>
> The driver shouldn't do this - it's up to the system integration to
> define how the buttons are mapped.

I was following code from ts3a227e driver that does the same.

>
> > +
> > +     nau8825->jack = jack;
> > +
> > +     return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
>
> This function doesn't appear to affect the hardware.  I would expect
> that the jack detection hardware is turned off when not in use.

Disabling jack detection means disable HeadsetCompletion IRQ. But the
IRQ handler does a bunch of manual stuff like grounding/ungrounding
jack pins. If IRQ handler is not called then neither playback nor
capture will not work.


At this point this function just sets jack structure needed later in
IRQ handler.

>
> > +static void nau8825_setup_buttons(struct regmap *regmap)
> > +{
> > +     /* Setup Button Detect (Debounce, number of buttons, and Hysteresis) */
> > +     regmap_write(regmap, NAU8825_REG_KEYDET_CTRL, 0x7311);
> > +
> > +     /* Setup 4 buttons impedane according to Android specification
> > +      * https://source.android.com/accessories/headset-spec.html
> > +      * Button 0 - 0-70 Ohm
> > +      * Button 1 - 110-180 Ohm
> > +      * Button 2 - 210-290 Ohm
> > +      * Button 3 - 360-680 Ohm
> > +      */
> > +     regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_1, 0x0f1f);
> > +     regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_2, 0x325f);
> > +}
>
> This looks like system configuration...

Moved to DTS.

>
> > +     /* The interrupt clock is gated by x1[10:8],
> > +      * one of them needs to be enabled all the time for
> > +      * interrupts to happen. */
> > +     snd_soc_dapm_force_enable_pin(&codec->dapm, "DDACR");
> > +     snd_soc_dapm_sync(&codec->dapm);
>
> This should be tied to jack detection, not done unconditionally.
Mark Brown Aug. 11, 2015, 4:39 p.m. UTC | #5
On Mon, Aug 10, 2015 at 05:32:32PM -0700, Anatol Pomozov wrote:
> On Tue, Aug 4, 2015 at 2:47 AM, Mark Brown <broonie@kernel.org> wrote:
> > On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:

> >> >> +     /* Setup ADC x128 OSR */Moved to
> >> >> +     regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
> >> >> +     /* Setup DAC x128 OSR */
> >> >> +     regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);

> >> > I'd expect this to be user controllable.

> >> The oversampling configuration is important for chip audio quality.
> >> There is audible hissing without these settings.

> >> My understanding that all users need to set these values and it is
> >> better to move it to the driver initialization sequence.

> > That sounds like spectacularly poor quality of implementation, but even
> > if there's a noticeable reduction in audio quality the control should
> > still be given to the user, they may have an application where power is

> Moved it to ALSA controls. Though I still feel a bit uncomfortable
> that the default control value is not usable.
> I have to set "ADC Decimation Rate" and "DAC Oversampling Rate"
> controls in userspace from its defaults to "128".

Setting the default isn't too bad in this case (especially with a
comment explaining why) - that doesn't preclude also providing user
configuration.  

> > If you need to control MCLK you should be using the clock API to get
> > MCLK which you can do in the driver model probe.  Relying on the machine
> > driver to control MCLK for you in this way is at best fragile.

> After discussing it with my teammates who works on Intel+NAU8825
> platform I found that they use MCLK only at playback/capture time. The
> rest of the time VCO is suppose to be sysclk source. Thus using
> get_clk() is not flexible for all cases.

I'm sorry, I don't understand.  What makes you say that "using get_clk()
is not flexible for all cases", I can't quite parse that bit?

> > > +int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
> > > +                             struct snd_soc_jack *jack)
> > > +{
> > > +     struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);


> > > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
> > > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
> > > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
> > > +     snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);

> > The driver shouldn't do this - it's up to the system integration to
> > define how the buttons are mapped.

> I was following code from ts3a227e driver that does the same.

That device is specifically tied to Chromebooks which have a defined
mapping, as opposed to being a generic device which is usable on a range
of devices.

> > > +     nau8825->jack = jack;

> > > +     return 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);

> > This function doesn't appear to affect the hardware.  I would expect
> > that the jack detection hardware is turned off when not in use.

> Disabling jack detection means disable HeadsetCompletion IRQ. But the
> IRQ handler does a bunch of manual stuff like grounding/ungrounding
> jack pins. If IRQ handler is not called then neither playback nor
> capture will not work.

The assumption would be that if the system doesn't have or need jack
detection then the jack pins won't be reconfigurable anyway.

> At this point this function just sets jack structure needed later in
> IRQ handler.

I can see what it's doing, I'd just also expect it to also be starting
and stopping the detection in the hardware.
anatol.pomozov@gmail.com Aug. 12, 2015, 11:08 p.m. UTC | #6
Hi

>> Moved it to ALSA controls. Though I still feel a bit uncomfortable
>> that the default control value is not usable.
>> I have to set "ADC Decimation Rate" and "DAC Oversampling Rate"
>> controls in userspace from its defaults to "128".

>Setting the default isn't too bad in this case (especially with a
>comment explaining why) - that doesn't preclude also providing user
>configuration.

What is the best way to set default control value? Just a regmap_write()?

>> After discussing it with my teammates who works on Intel+NAU8825
>> platform I found that they use MCLK only at playback/capture time. The
>> rest of the time VCO is suppose to be sysclk source. Thus using
>> get_clk() is not flexible for all cases.
>
> I'm sorry, I don't understand.  What makes you say that "using get_clk()
> is not flexible for all cases", I can't quite parse that bit?

My initial assumption was that you are talking about moving complete
clock handling (clock initialization, MCLK frequency calculation based
on rate, policy of switching clock sources i.e. everything that
platform drivers do) to the the driver code.

I've looked at other codec drivers and did not find many examples of
using clk api. The drivers seems expect clock signal is pre-configured
already. e.g. tegra has a utility function that configures audio
clocks tegra_asoc_utils_set_rate() and it actively used in tegra-based
platform drivers.

Locally I tried to move clock initialization out of platform code to
codec itself. Codec calls devm_clk_get() in init and sets frequency in
nau8825_set_sysclk(). See it here
https://github.com/anatol/linux/tree/nau8825  It works fine on a
Tegra-based device. But I found that devm_clk_get() is DTS specific
and does not work with ACPI. We need NAU8825 driver for Intel platform
as well.


>> I was following code from ts3a227e driver that does the same.
>
> That device is specifically tied to Chromebooks which have a defined
> mapping, as opposed to being a generic device which is usable on a range
> of devices.

I do not see anything Chromebook specific about this chip

http://www.ti.com/product/ts3a227e


>> > This function doesn't appear to affect the hardware.  I would expect
>> > that the jack detection hardware is turned off when not in use.
>
>> Disabling jack detection means disable HeadsetCompletion IRQ. But the
>> IRQ handler does a bunch of manual stuff like grounding/ungrounding
>> jack pins. If IRQ handler is not called then neither playback nor
>> capture will not work.
>
> The assumption would be that if the system doesn't have or need jack
> detection then the jack pins won't be reconfigurable anyway.
>
>> At this point this function just sets jack structure needed later in
>> IRQ handler.
>
> I can see what it's doing, I'd just also expect it to also be starting
> and stopping the detection in the hardware.

If we move IRQ enabling from init() function to enable_jack_detect()
then it means chip will be unusable after the init(). It won't be able
to playback nor capture audio. All developers will be forced to call
nau88225_enable_jack_detect() from their platform driver to enable
basic chip functionality. I think it is better if basic functionality
is enabled during driver init and does not require additional steps.

It is better if I rename nau8825_enable_jack_detect() to
nau8825_set_jack() to make cleaner what the function suppose to do. It
sets jack structure that driver uses to report events to upper level.
If the jack pointer is not set then driver still handles jack
insert/eject internally but does not report it to the userspace.
Mark Brown Aug. 13, 2015, 11:26 a.m. UTC | #7
On Wed, Aug 12, 2015 at 04:08:31PM -0700, Anatol Pomozov wrote:
> >> Moved it to ALSA controls. Though I still feel a bit uncomfortable
> >> that the default control value is not usable.
> >> I have to set "ADC Decimation Rate" and "DAC Oversampling Rate"
> >> controls in userspace from its defaults to "128".

> >Setting the default isn't too bad in this case (especially with a
> >comment explaining why) - that doesn't preclude also providing user
> >configuration.

> What is the best way to set default control value? Just a regmap_write()?

Yes.

> > I'm sorry, I don't understand.  What makes you say that "using get_clk()
> > is not flexible for all cases", I can't quite parse that bit?

> My initial assumption was that you are talking about moving complete
> clock handling (clock initialization, MCLK frequency calculation based
> on rate, policy of switching clock sources i.e. everything that
> platform drivers do) to the the driver code.

That is the ideal thing.

> I've looked at other codec drivers and did not find many examples of
> using clk api. The drivers seems expect clock signal is pre-configured
> already. e.g. tegra has a utility function that configures audio
> clocks tegra_asoc_utils_set_rate() and it actively used in tegra-based
> platform drivers.

Most of these drivers predate the clock API, while there's no pressing
need to update things (and it's pretty disruptive to existing systems
especially older ones) if you're running into problems with new code
that are best fixed with the clock API then you should do that.

> Locally I tried to move clock initialization out of platform code to
> codec itself. Codec calls devm_clk_get() in init and sets frequency in
> nau8825_set_sysclk(). See it here
> https://github.com/anatol/linux/tree/nau8825  It works fine on a
> Tegra-based device. But I found that devm_clk_get() is DTS specific
> and does not work with ACPI. We need NAU8825 driver for Intel platform
> as well.

No, that is not the case at all - what makes you claim that devm_clk_get()
is DT specific?

> >> I was following code from ts3a227e driver that does the same.

> > That device is specifically tied to Chromebooks which have a defined
> > mapping, as opposed to being a generic device which is usable on a range
> > of devices.

> I do not see anything Chromebook specific about this chip

> http://www.ti.com/product/ts3a227e

My understanding is that the accessories it supports are
Chrombook/Android ones (note that there's no configuration of ranges for
detection in the code).

> >> At this point this function just sets jack structure needed later in
> >> IRQ handler.

> > I can see what it's doing, I'd just also expect it to also be starting
> > and stopping the detection in the hardware.

> If we move IRQ enabling from init() function to enable_jack_detect()
> then it means chip will be unusable after the init(). It won't be able
> to playback nor capture audio. All developers will be forced to call
> nau88225_enable_jack_detect() from their platform driver to enable
> basic chip functionality. I think it is better if basic functionality
> is enabled during driver init and does not require additional steps.

I very much doubt that this will be a problem for all boards.  To repeat
what I said in my previous reply:

| The assumption would be that if the system doesn't have or need jack
| detection then the jack pins won't be reconfigurable anyway.

Your board may be wired up with jack detection configured in a way that
needs this, I would be surprised if all boards were.

> It is better if I rename nau8825_enable_jack_detect() to
> nau8825_set_jack() to make cleaner what the function suppose to do. It
> sets jack structure that driver uses to report events to upper level.
> If the jack pointer is not set then driver still handles jack
> insert/eject internally but does not report it to the userspace.

No, that doesn't help.
anatol.pomozov@gmail.com Aug. 13, 2015, 7:44 p.m. UTC | #8
Hi

On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown <broonie@kernel.org> wrote:
>> Locally I tried to move clock initialization out of platform code to
>> codec itself. Codec calls devm_clk_get() in init and sets frequency in
>> nau8825_set_sysclk(). See it here
>> https://github.com/anatol/linux/tree/nau8825  It works fine on a
>> Tegra-based device. But I found that devm_clk_get() is DTS specific
>> and does not work with ACPI. We need NAU8825 driver for Intel platform
>> as well.
>
> No, that is not the case at all - what makes you claim that devm_clk_get()
> is DT specific?

clk_dev uses of_clk_get_by_name() that is implemented for DTS only.
See include/linux/clk.h.

ACPI has a compatible API for getting simple properties
(strings/numbers), but no API for dealing with clocks.


> I very much doubt that this will be a problem for all boards.  To repeat
> what I said in my previous reply:
>
> | The assumption would be that if the system doesn't have or need jack
> | detection then the jack pins won't be reconfigurable anyway.
>
> Your board may be wired up with jack detection configured in a way that
> needs this, I would be surprised if all boards were.

I am still trying to understand the use-case for NAU8825 without jack
detection functionality. Are you talking about a board configuration
that does not wire jack to JKDET codec pin and (maybe) statically
connects output to speakers?

If platform driver does not enable jack detection, what NAU8825
functionality suppose to work? Does output (HPL/HPR) suppose to work?
Does mic suppose to work? Is OMTP/CTIA mic detection functionality
needed?
Mark Brown Aug. 13, 2015, 10:33 p.m. UTC | #9
On Thu, Aug 13, 2015 at 12:44:33PM -0700, Anatol Pomozov wrote:
> On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown <broonie@kernel.org> wrote:

> > No, that is not the case at all - what makes you claim that devm_clk_get()
> > is DT specific?

> clk_dev uses of_clk_get_by_name() that is implemented for DTS only.
> See include/linux/clk.h.

That means that the clock API can work with DT, not that DT is
mandatory - notice how there are stubs for the non-DT case.

> ACPI has a compatible API for getting simple properties
> (strings/numbers), but no API for dealing with clocks.

So ACPI based platforms have to deal with providing clocks in some other
way, that's something that is abstracted away by the clock API.  There
are a number of drivers for both board file based and ACPI based systems
already in tree which manage to make use of the clock API.

> > I very much doubt that this will be a problem for all boards.  To repeat
> > what I said in my previous reply:

> > | The assumption would be that if the system doesn't have or need jack
> > | detection then the jack pins won't be reconfigurable anyway.

> > Your board may be wired up with jack detection configured in a way that
> > needs this, I would be surprised if all boards were.

> I am still trying to understand the use-case for NAU8825 without jack
> detection functionality. Are you talking about a board configuration
> that does not wire jack to JKDET codec pin and (maybe) statically
> connects output to speakers?

I'm not specifically familiar with this CODEC, however this sort of
detection circuitry is all pretty standard.  I'm thinking of the sort of
straightforward configurations where the detection inputs and any
outputs required to flip polarity of the jack are just not wired up.

> If platform driver does not enable jack detection, what NAU8825
> functionality suppose to work? Does output (HPL/HPR) suppose to work?
> Does mic suppose to work? Is OMTP/CTIA mic detection functionality
> needed?

I'd expect the detection functionality wouldn't work without being wired
up as expected but I'd expect it to be perfectly possible to support the
headphones with the default ground confirguration.  I'd also expect mics
to work, either connected to the headset with a fixed polarity or just
elsewhere in the system.  If the device is capable of supprorting
headphones it must be capable of supporting them with a fixed ground,
and similarly for microphones.
anatol.pomozov@gmail.com Aug. 16, 2015, 7:38 a.m. UTC | #10
Hi

On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown <broonie@kernel.org> wrote:
> On Thu, Aug 13, 2015 at 12:44:33PM -0700, Anatol Pomozov wrote:
>> On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown <broonie@kernel.org> wrote:
>
>> > No, that is not the case at all - what makes you claim that devm_clk_get()
>> > is DT specific?
>
>> clk_dev uses of_clk_get_by_name() that is implemented for DTS only.
>> See include/linux/clk.h.
>
> That means that the clock API can work with DT, not that DT is
> mandatory - notice how there are stubs for the non-DT case.
>
>> ACPI has a compatible API for getting simple properties
>> (strings/numbers), but no API for dealing with clocks.
>
> So ACPI based platforms have to deal with providing clocks in some other
> way, that's something that is abstracted away by the clock API.  There
> are a number of drivers for both board file based and ACPI based systems
> already in tree which manage to make use of the clock API.

What is the best example of driver that uses clk API in both DT and
ACPI friendly way? I will use it as a template for my driver.

>> I am still trying to understand the use-case for NAU8825 without jack
>> detection functionality. Are you talking about a board configuration
>> that does not wire jack to JKDET codec pin and (maybe) statically
>> connects output to speakers?
>
> I'm not specifically familiar with this CODEC, however this sort of
> detection circuitry is all pretty standard.  I'm thinking of the sort of
> straightforward configurations where the detection inputs and any
> outputs required to flip polarity of the jack are just not wired up.

This corresponds to codec's JKDET/GPIO1 pin that is used as a default
source for jack detection interruption. And better place to describe a
board configuration like this is DTS.

Looking at Nuvoton's documentation I see it mentions that jack
detection IRQ source can be set to one of the GPIO pins or its
combination. In DTS it will look something like
  nuvoton,jkdet-source = <N>; // 0 to disable jkdet functionality, 1
for gpio1 (chip default), 2 for gpio2

The chip documentation on jack detection is sparse so I need to
clarify all aspects of configuration with Nuvoton engs.

And I believe this jack detection source configuration can be added
later and should not block the rest of the driver.
Mark Brown Aug. 17, 2015, 6:20 p.m. UTC | #11
On Sun, Aug 16, 2015 at 12:38:41AM -0700, Anatol Pomozov wrote:
> On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown <broonie@kernel.org> wrote:

> > So ACPI based platforms have to deal with providing clocks in some other
> > way, that's something that is abstracted away by the clock API.  There
> > are a number of drivers for both board file based and ACPI based systems
> > already in tree which manage to make use of the clock API.

> What is the best example of driver that uses clk API in both DT and
> ACPI friendly way? I will use it as a template for my driver.

Any driver using the clock API should provide a suitable reference.  The
mechanism used to resolve clocks is completely opaque to the driver
using the clock API.
anatol.pomozov@gmail.com Aug. 28, 2015, 11:23 p.m. UTC | #12
Hi

Sorry for the delay.

On Mon, Aug 17, 2015 at 11:20 AM, Mark Brown <broonie@kernel.org> wrote:
> On Sun, Aug 16, 2015 at 12:38:41AM -0700, Anatol Pomozov wrote:
>> On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown <broonie@kernel.org> wrote:
>
>> > So ACPI based platforms have to deal with providing clocks in some other
>> > way, that's something that is abstracted away by the clock API.  There
>> > are a number of drivers for both board file based and ACPI based systems
>> > already in tree which manage to make use of the clock API.
>
>> What is the best example of driver that uses clk API in both DT and
>> ACPI friendly way? I will use it as a template for my driver.
>
> Any driver using the clock API should provide a suitable reference.  The
> mechanism used to resolve clocks is completely opaque to the driver
> using the clock API.

Okay, I added clk support for MCLK. set_sysclk() callback sets rate
for this clock. Platform driver needs to call
snd_soc_codec_set_sysclk() to choose codec clock source and set its
rate.

Another change - added 10ms delay after enabling charge pump to eliminate pop.

Patch
diff mbox

diff --git a/Documentation/devicetree/bindings/sound/nau8825.txt b/Documentation/devicetree/bindings/sound/nau8825.txt
new file mode 100644
index 0000000..0334be3
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/nau8825.txt
@@ -0,0 +1,14 @@ 
+Nuvoton NAU8825 audio codec
+
+This device supports I2C only.
+
+Required properties:
+  - compatible : Must be "nuvoton,nau8825"
+  - reg : the I2C address of the device. This is either 0x1a (CSB=0) or 0x1b (CSB=1).
+
+Example:
+
+	codec: nau8825@1a {
+		compatible = "nuvoton,nau8825";
+		reg = <0x1a>;
+	};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 7fcbc22..1a3639c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -78,6 +78,7 @@  config SND_SOC_ALL_CODECS
 	select SND_SOC_MAX9877 if I2C
 	select SND_SOC_MC13783 if MFD_MC13XXX
 	select SND_SOC_ML26124 if I2C
+	select SND_SOC_NAU8825 if I2C
 	select SND_SOC_HDMI_CODEC
 	select SND_SOC_PCM1681 if I2C
 	select SND_SOC_PCM1792A if SPI_MASTER
@@ -892,6 +893,9 @@  config SND_SOC_MC13783
 config SND_SOC_ML26124
 	tristate
 
+config SND_SOC_NAU8825
+	tristate
+
 config SND_SOC_TPA6130A2
 	tristate "Texas Instruments TPA6130A2 headphone amplifier"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5b46c8f..4874881 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -71,6 +71,7 @@  snd-soc-max98925-objs := max98925.o
 snd-soc-max9850-objs := max9850.o
 snd-soc-mc13783-objs := mc13783.o
 snd-soc-ml26124-objs := ml26124.o
+snd-soc-nau8825-objs := nau8825.o
 snd-soc-hdmi-codec-objs := hdmi.o
 snd-soc-pcm1681-objs := pcm1681.o
 snd-soc-pcm1792a-codec-objs := pcm1792a.o
@@ -262,6 +263,7 @@  obj-$(CONFIG_SND_SOC_MAX98925)	+= snd-soc-max98925.o
 obj-$(CONFIG_SND_SOC_MAX9850)	+= snd-soc-max9850.o
 obj-$(CONFIG_SND_SOC_MC13783)	+= snd-soc-mc13783.o
 obj-$(CONFIG_SND_SOC_ML26124)	+= snd-soc-ml26124.o
+obj-$(CONFIG_SND_SOC_NAU8825)   += snd-soc-nau8825.o
 obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
 obj-$(CONFIG_SND_SOC_PCM1681)	+= snd-soc-pcm1681.o
 obj-$(CONFIG_SND_SOC_PCM1792A)	+= snd-soc-pcm1792a-codec.o
diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c
new file mode 100644
index 0000000..1c99308b
--- /dev/null
+++ b/sound/soc/codecs/nau8825.c
@@ -0,0 +1,860 @@ 
+/*
+ * NAU8825 audio codec driver
+ *
+ * Copyright 2015 Nuvoton Technology Corp.
+ * Copyright 2015 Google Chromium project.
+ *  Author: Anatol Pomozov <anatol@chromium.org>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/input.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "nau8825.h"
+
+struct nau8825 {
+	struct device *dev;
+	struct regmap *regmap;
+	struct snd_soc_dapm_context *dapm;
+	int irq;
+	struct snd_soc_jack *jack;
+	int button_pressed;
+};
+
+static const struct reg_default nau8825_reg_defaults[] = {
+	{ NAU8825_REG_ENA_CTRL, 0x00ff },
+	{ NAU8825_REG_CLK_DIVIDER, 0x0050 },
+	{ NAU8825_REG_FLL1, 0x0 },
+	{ NAU8825_REG_FLL2, 0x3126 },
+	{ NAU8825_REG_FLL3, 0x0008 },
+	{ NAU8825_REG_FLL4, 0x0010 },
+	{ NAU8825_REG_FLL5, 0x0 },
+	{ NAU8825_REG_FLL6, 0x6000 },
+	{ NAU8825_REG_FLL_VCO_RSV, 0xf13c },
+	{ NAU8825_REG_HSD_CTRL, 0x000c },
+	{ NAU8825_REG_JACK_DET_CTRL, 0x0 },
+	{ NAU8825_REG_INTERRUPT_MASK, 0x0 },
+	{ NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff },
+	{ NAU8825_REG_SAR_CTRL, 0x0015 },
+	{ NAU8825_REG_KEYDET_CTRL, 0x0110 },
+	{ NAU8825_REG_VDET_THRESHOLD_1, 0x0 },
+	{ NAU8825_REG_VDET_THRESHOLD_2, 0x0 },
+	{ NAU8825_REG_VDET_THRESHOLD_3, 0x0 },
+	{ NAU8825_REG_VDET_THRESHOLD_4, 0x0 },
+	{ NAU8825_REG_GPIO34_CTRL, 0x0 },
+	{ NAU8825_REG_GPIO12_CTRL, 0x0 },
+	{ NAU8825_REG_TDM_CTRL, 0x0 },
+	{ NAU8825_REG_I2S_PCM_CTRL1, 0x000b },
+	{ NAU8825_REG_I2S_PCM_CTRL2, 0x8010 },
+	{ NAU8825_REG_LEFT_TIME_SLOT, 0x0 },
+	{ NAU8825_REG_RIGHT_TIME_SLOT, 0x0 },
+	{ NAU8825_REG_BIQ_CTRL, 0x0 },
+	{ NAU8825_REG_BIQ_COF1, 0x0 },
+	{ NAU8825_REG_BIQ_COF2, 0x0 },
+	{ NAU8825_REG_BIQ_COF3, 0x0 },
+	{ NAU8825_REG_BIQ_COF4, 0x0 },
+	{ NAU8825_REG_BIQ_COF5, 0x0 },
+	{ NAU8825_REG_BIQ_COF6, 0x0 },
+	{ NAU8825_REG_BIQ_COF7, 0x0 },
+	{ NAU8825_REG_BIQ_COF8, 0x0 },
+	{ NAU8825_REG_BIQ_COF9, 0x0 },
+	{ NAU8825_REG_BIQ_COF10, 0x0 },
+	{ NAU8825_REG_ADC_RATE, 0x0010 },
+	{ NAU8825_REG_DAC_CTRL1, 0x0001 },
+	{ NAU8825_REG_DAC_CTRL2, 0x0 },
+	{ NAU8825_REG_DAC_DGAIN_CTRL, 0x0 },
+	{ NAU8825_REG_ADC_DGAIN_CTRL, 0x00cf },
+	{ NAU8825_REG_MUTE_CTRL, 0x0 },
+	{ NAU8825_REG_HSVOL_CTRL, 0x0 },
+	{ NAU8825_REG_DACL_CTRL, 0x02cf },
+	{ NAU8825_REG_DACR_CTRL, 0x00cf },
+	{ NAU8825_REG_ADC_DRC_KNEE_IP12, 0x1486 },
+	{ NAU8825_REG_ADC_DRC_KNEE_IP34, 0x0f12 },
+	{ NAU8825_REG_ADC_DRC_SLOPES, 0x25ff },
+	{ NAU8825_REG_ADC_DRC_ATKDCY, 0x3457 },
+	{ NAU8825_REG_DAC_DRC_KNEE_IP12, 0x1486 },
+	{ NAU8825_REG_DAC_DRC_KNEE_IP34, 0x0f12 },
+	{ NAU8825_REG_DAC_DRC_SLOPES, 0x25f9 },
+	{ NAU8825_REG_DAC_DRC_ATKDCY, 0x3457 },
+	{ NAU8825_REG_IMM_MODE_CTRL, 0x0 },
+	{ NAU8825_REG_CLASSG_CTRL, 0x0 },
+	{ NAU8825_REG_OPT_EFUSE_CTRL, 0x0 },
+	{ NAU8825_REG_MISC_CTRL, 0x0 },
+	{ NAU8825_REG_BIAS_ADJ, 0x0 },
+	{ NAU8825_REG_TRIM_SETTINGS, 0x0 },
+	{ NAU8825_REG_ANALOG_CONTROL_1, 0x0 },
+	{ NAU8825_REG_ANALOG_CONTROL_2, 0x0 },
+	{ NAU8825_REG_ANALOG_ADC_1, 0x0011 },
+	{ NAU8825_REG_ANALOG_ADC_2, 0x0020 },
+	{ NAU8825_REG_RDAC, 0x0008 },
+	{ NAU8825_REG_MIC_BIAS, 0x0006 },
+	{ NAU8825_REG_BOOST, 0x0 },
+	{ NAU8825_REG_FEPGA, 0x0 },
+	{ NAU8825_REG_POWER_UP_CONTROL, 0x0 },
+	{ NAU8825_REG_CHARGE_PUMP, 0x0 },
+};
+
+static bool nau8825_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case NAU8825_REG_ENA_CTRL:
+	case NAU8825_REG_CLK_DIVIDER ... NAU8825_REG_FLL_VCO_RSV:
+	case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL:
+	case NAU8825_REG_INTERRUPT_MASK ... NAU8825_REG_KEYDET_CTRL:
+	case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL:
+	case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY:
+	case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY:
+	case NAU8825_REG_IMM_MODE_CTRL ... NAU8825_REG_IMM_RMS_R:
+	case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL:
+	case NAU8825_REG_MISC_CTRL:
+	case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_SARDOUT_RAM_STATUS:
+	case NAU8825_REG_BIAS_ADJ:
+	case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2:
+	case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS:
+	case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA:
+	case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_GENERAL_STATUS:
+		return true;
+	default:
+		return false;
+	}
+
+}
+
+static bool nau8825_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case NAU8825_REG_RESET ... NAU8825_REG_ENA_CTRL:
+	case NAU8825_REG_CLK_DIVIDER ... NAU8825_REG_FLL_VCO_RSV:
+	case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL:
+	case NAU8825_REG_INTERRUPT_MASK:
+	case NAU8825_REG_INT_CLR_KEY_STATUS ... NAU8825_REG_KEYDET_CTRL:
+	case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL:
+	case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY:
+	case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY:
+	case NAU8825_REG_IMM_MODE_CTRL:
+	case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL:
+	case NAU8825_REG_MISC_CTRL:
+	case NAU8825_REG_BIAS_ADJ:
+	case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2:
+	case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS:
+	case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA:
+	case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_CHARGE_PUMP:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool nau8825_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case NAU8825_REG_RESET:
+	case NAU8825_REG_IRQ_STATUS:
+	case NAU8825_REG_INT_CLR_KEY_STATUS:
+	case NAU8825_REG_IMM_RMS_L:
+	case NAU8825_REG_IMM_RMS_R:
+	case NAU8825_REG_I2C_DEVICE_ID:
+	case NAU8825_REG_SARDOUT_RAM_STATUS:
+	case NAU8825_REG_CHARGE_PUMP_INPUT_READ:
+	case NAU8825_REG_GENERAL_STATUS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -10300, 2400);
+static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0);
+static const DECLARE_TLV_DB_MINMAX(dac_vol_tlv, -5400, 0);
+static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600);
+
+static const struct snd_kcontrol_new nau8825_snd_controls[] = {
+	SOC_SINGLE_TLV("MIC Volume", NAU8825_REG_ADC_DGAIN_CTRL,
+		0, 0xff, 0, adc_vol_tlv),
+	SOC_DOUBLE_TLV("HP Sidetone Volume", NAU8825_REG_ADC_DGAIN_CTRL,
+		12, 8, 0x0f, 0, sidetone_vol_tlv),
+	SOC_DOUBLE_TLV("HP Volume", NAU8825_REG_HSVOL_CTRL,
+		6, 0, 0x3f, 1, dac_vol_tlv),
+	SOC_SINGLE_TLV("Frontend PGA Gain", NAU8825_REG_POWER_UP_CONTROL,
+		8, 37, 0, fepga_gain_tlv),
+};
+
+static int nau8825_charge_pump_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	struct regmap *regmap = nau8825->regmap;
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP,
+			NAU8825_JAMNODCLOW, NAU8825_JAMNODCLOW);
+	} else {
+		regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP,
+			NAU8825_JAMNODCLOW, 0);
+	}
+
+	return 0;
+}
+
+static int nau8825_output_driver_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	struct regmap *regmap = nau8825->regmap;
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		/* Power Up L and R Output Drivers */
+		regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3c,
+			0x3c);
+		/* Power up Main Output Drivers (The main driver must be turned
+		   on after the predriver to avoid pops) */
+		regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3,
+			0x3);
+	} else {
+		/* Power Down L and R Output Drivers */
+		regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3f,
+			0x0);
+	}
+
+	return 0;
+}
+
+/* DAC Mux 0x33[9] and 0x34[9] */
+static const char * const nau8825_dac_src[] = {
+	"DACL", "DACR",
+};
+
+static SOC_ENUM_SINGLE_DECL(
+	nau8825_dacl_enum, NAU8825_REG_DACL_CTRL,
+	NAU8825_DACL_CH_SEL_SFT, nau8825_dac_src);
+
+static SOC_ENUM_SINGLE_DECL(
+	nau8825_dacr_enum, NAU8825_REG_DACR_CTRL,
+	NAU8825_DACR_CH_SEL_SFT, nau8825_dac_src);
+
+static const struct snd_kcontrol_new nau8825_dacl_mux =
+	SOC_DAPM_ENUM("DACL Source", nau8825_dacl_enum);
+
+static const struct snd_kcontrol_new nau8825_dacr_mux =
+	SOC_DAPM_ENUM("DACR Source", nau8825_dacr_enum);
+
+static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = {
+	SND_SOC_DAPM_INPUT("MIC"),
+	SND_SOC_DAPM_MICBIAS("MICBIAS", NAU8825_REG_MIC_BIAS, 8, 0),
+
+	SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0,
+		NULL, 0),
+
+	SND_SOC_DAPM_ADC("ADC", NULL, NAU8825_REG_ENA_CTRL, 8, 0),
+	SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0),
+
+	/* ADC for button press detection */
+	SND_SOC_DAPM_ADC("SAR", NULL, NAU8825_REG_SAR_CTRL,
+		NAU8825_SAR_ADC_EN_SFT, 0),
+
+	SND_SOC_DAPM_DAC("ADACL", NULL, NAU8825_REG_RDAC, 12, 0),
+	SND_SOC_DAPM_DAC("ADACR", NULL, NAU8825_REG_RDAC, 13, 0),
+	SND_SOC_DAPM_SUPPLY("ADACL Clock", NAU8825_REG_RDAC, 8, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("ADACR Clock", NAU8825_REG_RDAC, 9, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("DDACR", NULL, NAU8825_REG_ENA_CTRL,
+		NAU8825_ENABLE_DACR_SFT, 0),
+	SND_SOC_DAPM_DAC("DDACL", NULL, NAU8825_REG_ENA_CTRL,
+		NAU8825_ENABLE_DACL_SFT, 0),
+	SND_SOC_DAPM_SUPPLY("DDAC Clock", NAU8825_REG_ENA_CTRL, 6, 0, NULL, 0),
+
+	SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0,
+			&nau8825_dacl_mux),
+	SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0,
+			&nau8825_dacr_mux),
+
+	SND_SOC_DAPM_PGA("HP amp L", NAU8825_REG_CLASSG_CTRL, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("HP amp R", NAU8825_REG_CLASSG_CTRL, 2, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("HP amp power", NAU8825_REG_CLASSG_CTRL, 0, 0, NULL,
+		0),
+
+	SND_SOC_DAPM_SUPPLY("Change Pump", NAU8825_REG_CHARGE_PUMP, 5, 0,
+		nau8825_charge_pump_event,
+		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_SUPPLY("DACL Power", NAU8825_REG_CHARGE_PUMP, 8, 1, NULL,
+		0),
+	SND_SOC_DAPM_SUPPLY("DACR Power", NAU8825_REG_CHARGE_PUMP, 9, 1, NULL,
+		0),
+
+	SND_SOC_DAPM_OUT_DRV_E("Output Driver", SND_SOC_NOPM, 0, 0, NULL, 0,
+		nau8825_output_driver_event,
+		SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+	SND_SOC_DAPM_OUTPUT("HPOL"),
+	SND_SOC_DAPM_OUTPUT("HPOR"),
+};
+
+static const struct snd_soc_dapm_route nau8825_dapm_routes[] = {
+	{"Frontend PGA", NULL, "MIC"},
+	{"ADC", NULL, "Frontend PGA"},
+	{"ADC", NULL, "ADC Clock"},
+	{"Capture", NULL, "ADC"},
+
+	{"DDACL", NULL, "Playback"},
+	{"DDACR", NULL, "Playback"},
+	{"DDACL", NULL, "DDAC Clock"},
+	{"DDACR", NULL, "DDAC Clock"},
+	{"DDACL", NULL, "DACL Power"},
+	{"DDACR", NULL, "DACR Power"},
+	{"DACL Mux", "DACL", "DDACL"},
+	{"DACL Mux", "DACR", "DDACR"},
+	{"DACR Mux", "DACL", "DDACL"},
+	{"DACR Mux", "DACR", "DDACR"},
+	{"HP amp L", NULL, "Change Pump"},
+	{"HP amp R", NULL, "Change Pump"},
+	{"HP amp L", NULL, "DACL Mux"},
+	{"HP amp R", NULL, "DACR Mux"},
+	{"HP amp L", NULL, "HP amp power"},
+	{"HP amp R", NULL, "HP amp power"},
+	{"ADACL", NULL, "HP amp L"},
+	{"ADACR", NULL, "HP amp R"},
+	{"ADACL", NULL, "ADACL Clock"},
+	{"ADACR", NULL, "ADACR Clock"},
+	{"Output Driver", NULL, "ADACL"},
+	{"Output Driver", NULL, "ADACR"},
+	{"HPOL", NULL, "Output Driver"},
+	{"HPOR", NULL, "Output Driver"},
+};
+
+static int nau8825_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val_len = 0;
+
+	switch (params_width(params)) {
+	case 16:
+		val_len |= NAU8825_I2S_DL_16;
+		break;
+	case 20:
+		val_len |= NAU8825_I2S_DL_20;
+		break;
+	case 24:
+		val_len |= NAU8825_I2S_DL_24;
+		break;
+	case 32:
+		val_len |= NAU8825_I2S_DL_32;
+		break;
+	default:
+		return -EINVAL;
+	}
+	regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1,
+		NAU8825_I2S_DL_MASK, val_len);
+
+	return 0;
+}
+
+static int nau8825_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	int reg_val;
+
+	reg_val = mute ? NAU8825_HP_MUTE : 0;
+	regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
+		NAU8825_HP_MUTE, reg_val);
+
+	return 0;
+}
+
+static int nau8825_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	unsigned int ctrl1_val = 0, ctrl2_val = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ctrl2_val |= NAU8825_I2S_MS_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		ctrl1_val |= NAU8825_I2S_BP_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		ctrl1_val |= NAU8825_I2S_DF_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ctrl1_val |= NAU8825_I2S_DF_LEFT;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		ctrl1_val |= NAU8825_I2S_DF_RIGTH;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		ctrl1_val |= NAU8825_I2S_DF_PCM_AB;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		ctrl1_val |= NAU8825_I2S_DF_PCM_AB;
+		ctrl1_val |= NAU8825_I2S_PCMB_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1,
+		NAU8825_I2S_DL_MASK | NAU8825_I2S_DF_MASK |
+		NAU8825_I2S_BP_MASK | NAU8825_I2S_PCMB_MASK,
+		ctrl1_val);
+	regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
+		NAU8825_I2S_MS_MASK, ctrl2_val);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops nau8825_dai_ops = {
+	.hw_params	= nau8825_hw_params,
+	.digital_mute	= nau8825_mute,
+	.set_fmt	= nau8825_set_dai_fmt,
+};
+
+#define NAU8825_RATES	SNDRV_PCM_RATE_8000_192000
+#define NAU8825_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
+			 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver nau8825_dai = {
+	.name = "nau8825-hifi",
+	.playback = {
+		.stream_name	 = "Playback",
+		.channels_min	 = 1,
+		.channels_max	 = 2,
+		.rates		 = NAU8825_RATES,
+		.formats	 = NAU8825_FORMATS,
+	},
+	.capture = {
+		.stream_name	 = "Capture",
+		.channels_min	 = 1,
+		.channels_max	 = 1,
+		.rates		 = NAU8825_RATES,
+		.formats	 = NAU8825_FORMATS,
+	},
+	.ops = &nau8825_dai_ops,
+};
+
+/**
+ * nau8825_enable_jack_detect - Specify a jack for event reporting
+ *
+ * @component:  component to register the jack with
+ * @jack: jack to use to report headset and button events on
+ *
+ * After this function has been called the headset insert/remove and button
+ * events will be routed to the given jack.  Jack can be null to stop
+ * reporting.
+ */
+int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
+				struct snd_soc_jack *jack)
+{
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+
+	snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
+	snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+	snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+	snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+	nau8825->jack = jack;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
+
+
+static bool nau8825_is_jack_inserted(struct regmap *regmap)
+{
+	int status;
+
+	regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status);
+	return !(status & NAU8825_GPIO2JD1);
+}
+
+static void nau8825_restart_jack_detection(struct regmap *regmap)
+{
+	/* this will restart the entire jack detection process including MIC/GND
+	 * switching and create interrupts. We have to go from 0 to 1 and back
+	 * to 0 to restart.
+	 */
+	regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
+		NAU8825_JACK_DET_RESTART, NAU8825_JACK_DET_RESTART);
+	regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
+		NAU8825_JACK_DET_RESTART, 0);
+}
+
+static void nau8825_eject_jack(struct nau8825 *nau8825)
+{
+	struct snd_soc_dapm_context *dapm = nau8825->dapm;
+	struct regmap *regmap = nau8825->regmap;
+
+	snd_soc_dapm_disable_pin(dapm, "SAR");
+	snd_soc_dapm_disable_pin(dapm, "MICBIAS");
+	/* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */
+	regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS,
+		NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, 0);
+	/* ground HPL/HPR, MICGRND1/2 */
+	regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf);
+
+	snd_soc_dapm_sync(dapm);
+}
+
+static int nau8825_button_decode(int value)
+{
+	int buttons;
+
+	if (value & BIT(0))
+		buttons |= SND_JACK_BTN_0;
+	if (value & BIT(1))
+		buttons |= SND_JACK_BTN_1;
+	if (value & BIT(2))
+		buttons |= SND_JACK_BTN_2;
+	if (value & BIT(3))
+		buttons |= SND_JACK_BTN_3;
+
+	return buttons;
+}
+
+static int nau8825_jack_insert(struct nau8825 *nau8825)
+{
+	struct regmap *regmap = nau8825->regmap;
+	struct snd_soc_dapm_context *dapm = nau8825->dapm;
+	int jack_status_reg, mic_detected;
+	int type;
+
+	regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg);
+	mic_detected = (jack_status_reg >> 10) & 3;
+
+	switch (mic_detected) {
+	case 0:
+		/* no mic */
+		type = SND_JACK_HEADPHONE;
+		break;
+	case 1:
+		dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n");
+		type = SND_JACK_HEADSET;
+
+		/* Unground MICGND1 */
+		regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2,
+			1 << 2);
+		/* Unground HPL/R */
+		regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0x3, 0);
+		/* Attach 2kOhm Resistor from MICBIAS to MICGND1 */
+		regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS,
+			NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2,
+			NAU8825_MICBIAS_JKR2);
+		/* Attach SARADC to MICGND1 */
+		regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL,
+			NAU8825_SAR_INPUT_MASK,
+			NAU8825_SAR_INPUT_JKR2);
+
+		snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
+		snd_soc_dapm_force_enable_pin(dapm, "SAR");
+		snd_soc_dapm_sync(dapm);
+		break;
+	case 2:
+	case 3:
+		dev_info(nau8825->dev, "CTIA (micgnd2) mic connected\n");
+		type = SND_JACK_HEADSET;
+
+		/* Unground MICGND2 */
+		regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2,
+			2 << 2);
+		/* Unground HPL/R */
+		regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0x3, 0);
+		/* Attach 2kOhm Resistor from MICBIAS to MICGND2 */
+		regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS,
+			NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2,
+			NAU8825_MICBIAS_JKSLV);
+		/* Attach SARADC to MICGND2 */
+		regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL,
+			NAU8825_SAR_INPUT_MASK,
+			NAU8825_SAR_INPUT_JKSLV);
+
+		snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
+		snd_soc_dapm_force_enable_pin(dapm, "SAR");
+		snd_soc_dapm_sync(dapm);
+		break;
+	}
+
+	return type;
+}
+
+#define NAU8825_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \
+		SND_JACK_BTN_2 | SND_JACK_BTN_3)
+
+static irqreturn_t nau8825_interrupt(int irq, void *data)
+{
+	struct nau8825 *nau8825 = (struct nau8825 *)data;
+	struct regmap *regmap = nau8825->regmap;
+	int active_irq, clear_irq = 0, event = 0, event_mask = 0;
+
+	regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq);
+
+	if ((active_irq & NAU8825_JACK_EJECTION_IRQ_MASK) ==
+		NAU8825_JACK_EJECTION_DETECTED) {
+
+		nau8825_eject_jack(nau8825);
+		event_mask |= SND_JACK_HEADSET;
+		clear_irq = NAU8825_JACK_EJECTION_IRQ_MASK;
+	} else if (active_irq & NAU8825_KEY_SHORT_PRESS_IRQ) {
+		int key_status;
+
+		regmap_read(regmap, NAU8825_REG_INT_CLR_KEY_STATUS,
+			&key_status);
+
+		/* upper 8 bits of the register are for short pressed keys,
+		   lower 8 bits - for long pressed buttons */
+		nau8825->button_pressed = nau8825_button_decode(
+			key_status >> 8);
+
+		event |= nau8825->button_pressed;
+		event_mask |= NAU8825_BUTTONS;
+		clear_irq = NAU8825_KEY_SHORT_PRESS_IRQ;
+	} else if (active_irq & NAU8825_KEY_RELEASE_IRQ) {
+		event_mask = NAU8825_BUTTONS;
+		clear_irq = NAU8825_KEY_RELEASE_IRQ;
+	} else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) {
+		if (nau8825_is_jack_inserted(regmap)) {
+			event |= nau8825_jack_insert(nau8825);
+		} else {
+			dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
+			nau8825_eject_jack(nau8825);
+		}
+
+		event_mask |= SND_JACK_HEADSET;
+		clear_irq = NAU8825_HEADSET_COMPLETION_IRQ;
+	}
+
+	if (!clear_irq)
+		clear_irq = active_irq;
+	/* clears the rightmost interruption */
+	regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
+
+	if (event_mask)
+		snd_soc_jack_report(nau8825->jack, event, event_mask);
+
+	return IRQ_HANDLED;
+}
+
+static void nau8825_setup_buttons(struct regmap *regmap)
+{
+	/* Setup Button Detect (Debounce, number of buttons, and Hysteresis) */
+	regmap_write(regmap, NAU8825_REG_KEYDET_CTRL, 0x7311);
+
+	/* Setup 4 buttons impedane according to Android specification
+	 * https://source.android.com/accessories/headset-spec.html
+	 * Button 0 - 0-70 Ohm
+	 * Button 1 - 110-180 Ohm
+	 * Button 2 - 210-290 Ohm
+	 * Button 3 - 360-680 Ohm
+	 */
+	regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_1, 0x0f1f);
+	regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_2, 0x325f);
+}
+
+static void nau8825_init_regs(struct regmap *regmap)
+{
+	/* Power down DACs for power savings */
+	regmap_write(regmap, NAU8825_REG_CHARGE_PUMP, 0x0300);
+	/* DAC Zero Crossing Enable */
+	regmap_write(regmap, NAU8825_REG_MUTE_CTRL, 0x1000);
+	/* VMID Enable and Tieoff */
+	regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060);
+	/* Analog Bias Enable, Disable Boost Driver,
+	   Automatic Short circuit protection enable */
+	regmap_write(regmap, NAU8825_REG_BOOST, 0x3140);
+	/* Ground HP Outputs[1:0],
+	   Enable Automatic Mic/Gnd switching reading on insert interrupt[6] */
+	regmap_write(regmap, NAU8825_REG_HSD_CTRL, 0x004f);
+
+	regmap_write(regmap, NAU8825_REG_JACK_DET_CTRL, 0x01e0);
+	/* IRQ Output Enable, Mask Insert Interrupt */
+	regmap_write(regmap, NAU8825_REG_INTERRUPT_MASK, 0x0801);
+	/* Jack Detect pull up (High=eject, Low=insert) */
+	regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800);
+	/* Setup SAR ADC */
+	regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
+	nau8825_setup_buttons(regmap);
+
+	/* Setup ADC x128 OSR */
+	regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
+	/* Setup DAC x128 OSR */
+	regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
+	/* DAC bias settings */
+	regmap_write(regmap, NAU8825_REG_ANALOG_CONTROL_2, 0x1003);
+	/* ADC Bias Settings */
+	regmap_write(regmap, NAU8825_REG_ANALOG_ADC_2, 0x0260);
+	/* Maximize Jack Insert Debounce */
+	regmap_write(regmap, NAU8825_REG_JACK_DET_CTRL, 0x00e0);
+
+	/* Setup clocks */
+	regmap_write(regmap, NAU8825_REG_ENA_CTRL, 0x0416);
+
+	/* Chip needs one FSCLK cycle in order to generate interrupts,
+	   as we cannot guarantee one will be provided by the system. Turning
+	   master mode on then off enables us to generate that FSCLK cycle
+	   with a minimum of contention on the clock bus.
+	   Enables master mode and correctly divides BCLK and FSCLK from MCLK.
+	*/
+	regmap_write(regmap, NAU8825_REG_I2S_PCM_CTRL2, 0x2019);
+	regmap_write(regmap, NAU8825_REG_I2S_PCM_CTRL2, 0x0010);
+
+	/* Mask jack insert interruption */
+	regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0x0011);
+}
+
+static const struct regmap_config nau8825_regmap_config = {
+	.val_bits = 16,
+	.reg_bits = 16,
+
+	.max_register = NAU8825_REG_MAX,
+	.readable_reg = nau8825_readable_reg,
+	.writeable_reg = nau8825_writeable_reg,
+	.volatile_reg = nau8825_volatile_reg,
+
+	.cache_type = REGCACHE_RBTREE,
+	.reg_defaults = nau8825_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(nau8825_reg_defaults),
+};
+
+static int nau8825_codec_probe(struct snd_soc_codec *codec)
+{
+	struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = codec->dev;
+
+	nau8825->dapm = &codec->dapm;
+
+	nau8825_init_regs(nau8825->regmap);
+	/* The interrupt clock is gated by x1[10:8],
+	 * one of them needs to be enabled all the time for
+	 * interrupts to happen. */
+	snd_soc_dapm_force_enable_pin(&codec->dapm, "DDACR");
+	snd_soc_dapm_sync(&codec->dapm);
+
+	if (nau8825->irq) {
+		int ret = devm_request_threaded_irq(dev, nau8825->irq, NULL,
+			nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+			"nau8825", nau8825);
+
+		if (ret) {
+			dev_err(dev, "Cannot request irq %d (%d)\n",
+				nau8825->irq, ret);
+			return ret;
+		}
+	}
+	nau8825_restart_jack_detection(nau8825->regmap);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver nau8825_codec_driver = {
+	.probe = nau8825_codec_probe,
+
+	.controls = nau8825_snd_controls,
+	.num_controls = ARRAY_SIZE(nau8825_snd_controls),
+	.dapm_widgets = nau8825_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(nau8825_dapm_widgets),
+	.dapm_routes = nau8825_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(nau8825_dapm_routes),
+};
+
+static void nau8825_reset_chip(struct regmap *regmap)
+{
+	regmap_write(regmap, NAU8825_REG_RESET, 0x00);
+	regmap_write(regmap, NAU8825_REG_RESET, 0x00);
+}
+
+static int nau8825_i2c_probe(struct i2c_client *i2c,
+	const struct i2c_device_id *id)
+{
+	struct device *dev = &i2c->dev;
+	struct nau8825 *nau8825;
+	int ret, value;
+
+	nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL);
+	if (!nau8825)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, nau8825);
+
+	nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config);
+	if (IS_ERR(nau8825->regmap))
+		return PTR_ERR(nau8825->regmap);
+	nau8825->irq = i2c->irq;
+	nau8825->dev = dev;
+
+	nau8825_reset_chip(nau8825->regmap);
+	ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
+			ret);
+		return ret;
+	}
+	if ((value & NAU8825_SOFTWARE_ID_MASK) !=
+			NAU8825_SOFTWARE_ID_NAU8825) {
+		dev_err(dev, "Not a NAU8825 chip\n");
+		return -ENODEV;
+	}
+
+	return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver,
+			&nau8825_dai, 1);
+}
+
+static int nau8825_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	return 0;
+}
+
+static const struct i2c_device_id nau8825_i2c_ids[] = {
+	{ "nau8825", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids);
+
+static struct i2c_driver nau8825_driver = {
+	.driver = {
+		.name = "nau8825",
+		.owner = THIS_MODULE,
+	},
+	.probe = nau8825_i2c_probe,
+	.remove = nau8825_i2c_remove,
+	.id_table = nau8825_i2c_ids,
+};
+module_i2c_driver(nau8825_driver);
+
+MODULE_DESCRIPTION("ASoC nau8825 driver");
+MODULE_AUTHOR("Anatol Pomozov <anatol@chromium.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h
new file mode 100644
index 0000000..e1eb769
--- /dev/null
+++ b/sound/soc/codecs/nau8825.h
@@ -0,0 +1,258 @@ 
+/*
+ * NAU8825 ALSA SoC audio driver
+ *
+ * Copyright 2015 Google Inc.
+ * Author: Anatol Pomozov <anatol.pomozov@chrominium.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __NAU8825_H__
+#define __NAU8825_H__
+
+#define NAU8825_REG_RESET		0x00
+#define NAU8825_REG_ENA_CTRL		0x01
+#define NAU8825_REG_CLK_DIVIDER		0x03
+#define NAU8825_REG_FLL1		0x04
+#define NAU8825_REG_FLL2		0x05
+#define NAU8825_REG_FLL3		0x06
+#define NAU8825_REG_FLL4		0x07
+#define NAU8825_REG_FLL5		0x08
+#define NAU8825_REG_FLL6		0x09
+#define NAU8825_REG_FLL_VCO_RSV		0x0a
+#define NAU8825_REG_HSD_CTRL		0x0c
+#define NAU8825_REG_JACK_DET_CTRL		0x0d
+#define NAU8825_REG_INTERRUPT_MASK		0x0f
+#define NAU8825_REG_IRQ_STATUS		0x10
+#define NAU8825_REG_INT_CLR_KEY_STATUS		0x11
+#define NAU8825_REG_INTERRUPT_DIS_CTRL		0x12
+#define NAU8825_REG_SAR_CTRL		0x13
+#define NAU8825_REG_KEYDET_CTRL		0x14
+#define NAU8825_REG_VDET_THRESHOLD_1		0x15
+#define NAU8825_REG_VDET_THRESHOLD_2		0x16
+#define NAU8825_REG_VDET_THRESHOLD_3		0x17
+#define NAU8825_REG_VDET_THRESHOLD_4		0x18
+#define NAU8825_REG_GPIO34_CTRL		0x19
+#define NAU8825_REG_GPIO12_CTRL		0x1a
+#define NAU8825_REG_TDM_CTRL		0x1b
+#define NAU8825_REG_I2S_PCM_CTRL1		0x1c
+#define NAU8825_REG_I2S_PCM_CTRL2		0x1d
+#define NAU8825_REG_LEFT_TIME_SLOT		0x1e
+#define NAU8825_REG_RIGHT_TIME_SLOT		0x1f
+#define NAU8825_REG_BIQ_CTRL		0x20
+#define NAU8825_REG_BIQ_COF1		0x21
+#define NAU8825_REG_BIQ_COF2		0x22
+#define NAU8825_REG_BIQ_COF3		0x23
+#define NAU8825_REG_BIQ_COF4		0x24
+#define NAU8825_REG_BIQ_COF5		0x25
+#define NAU8825_REG_BIQ_COF6		0x26
+#define NAU8825_REG_BIQ_COF7		0x27
+#define NAU8825_REG_BIQ_COF8		0x28
+#define NAU8825_REG_BIQ_COF9		0x29
+#define NAU8825_REG_BIQ_COF10		0x2a
+#define NAU8825_REG_ADC_RATE		0x2b
+#define NAU8825_REG_DAC_CTRL1		0x2c
+#define NAU8825_REG_DAC_CTRL2		0x2d
+#define NAU8825_REG_DAC_DGAIN_CTRL		0x2f
+#define NAU8825_REG_ADC_DGAIN_CTRL		0x30
+#define NAU8825_REG_MUTE_CTRL		0x31
+#define NAU8825_REG_HSVOL_CTRL		0x32
+#define NAU8825_REG_DACL_CTRL		0x33
+#define NAU8825_REG_DACR_CTRL		0x34
+#define NAU8825_REG_ADC_DRC_KNEE_IP12		0x38
+#define NAU8825_REG_ADC_DRC_KNEE_IP34		0x39
+#define NAU8825_REG_ADC_DRC_SLOPES		0x3a
+#define NAU8825_REG_ADC_DRC_ATKDCY		0x3b
+#define NAU8825_REG_DAC_DRC_KNEE_IP12		0x45
+#define NAU8825_REG_DAC_DRC_KNEE_IP34		0x46
+#define NAU8825_REG_DAC_DRC_SLOPES		0x47
+#define NAU8825_REG_DAC_DRC_ATKDCY		0x48
+#define NAU8825_REG_IMM_MODE_CTRL		0x4c
+#define NAU8825_REG_IMM_RMS_L		0x4d
+#define NAU8825_REG_IMM_RMS_R		0x4e
+#define NAU8825_REG_CLASSG_CTRL		0x50
+#define NAU8825_REG_OPT_EFUSE_CTRL		0x51
+#define NAU8825_REG_MISC_CTRL		0x55
+#define NAU8825_REG_I2C_DEVICE_ID		0x58
+#define NAU8825_REG_SARDOUT_RAM_STATUS		0x59
+#define NAU8825_REG_BIAS_ADJ		0x66
+#define NAU8825_REG_TRIM_SETTINGS		0x68
+#define NAU8825_REG_ANALOG_CONTROL_1		0x69
+#define NAU8825_REG_ANALOG_CONTROL_2		0x6a
+#define NAU8825_REG_ANALOG_ADC_1		0x71
+#define NAU8825_REG_ANALOG_ADC_2		0x72
+#define NAU8825_REG_RDAC		0x73
+#define NAU8825_REG_MIC_BIAS		0x74
+#define NAU8825_REG_BOOST		0x76
+#define NAU8825_REG_FEPGA		0x77
+#define NAU8825_REG_POWER_UP_CONTROL		0x7f
+#define NAU8825_REG_CHARGE_PUMP		0x80
+#define NAU8825_REG_CHARGE_PUMP_INPUT_READ		0x81
+#define NAU8825_REG_GENERAL_STATUS		0x82
+#define NAU8825_REG_MAX		NAU8825_REG_GENERAL_STATUS
+
+/* ENA_CTRL (0x1) */
+#define NAU8825_ENABLE_DACR_SFT	10
+#define NAU8825_ENABLE_DACL_SFT	9
+#define NAU8825_ENABLE_ADC_SFT	8
+#define NAU8825_ENABLE_SAR_SFT	1
+
+/* CLK_DIVIDER (0x3) */
+#define NAU8825_CLK_SRC_SFT	15
+#define NAU8825_CLK_SRC_MASK	(1 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_SRC_VCO	(1 << NAU8825_CLK_SRC_SFT)
+#define NAU8825_CLK_SRC_MCLK	(0 << NAU8825_CLK_SRC_SFT)
+
+/* HSD_CTRL (0xc) */
+#define NAU8825_HSD_AUTO_MODE	(1 << 6)
+#define NAU8825_SPKR_DWN1R_SHORT	(1 << 1)
+#define NAU8825_SPKR_DWN1L_SHORT	(1 << 0)
+
+/* JACK_DET_CTRL (0xd) */
+#define NAU8825_JACK_DET_RESTART	(1 << 9)
+
+/* IRQ_STATUS (0x10) */
+#define NAU8825_HEADSET_COMPLETION_IRQ	(1 << 10)
+#define NAU8825_SHORT_CIRCUIT_IRQ	(1 << 9)
+#define NAU8825_IMPEDANCE_MEAS_IRQ	(1 << 8)
+#define NAU8825_KEY_IRQ_MASK	(0x7 << 5)
+#define NAU8825_KEY_RELEASE_IRQ	(1 << 7)
+#define NAU8825_KEY_LONG_PRESS_IRQ	(1 << 6)
+#define NAU8825_KEY_SHORT_PRESS_IRQ	(1 << 5)
+#define NAU8825_MIC_DETECTION_IRQ	(1 << 4)
+#define NAU8825_JACK_EJECTION_IRQ_MASK	(3 << 2)
+#define NAU8825_JACK_EJECTION_DETECTED	(1 << 2)
+#define NAU8825_JACK_INSERTION_IRQ_MASK	(3 << 0)
+#define NAU8825_JACK_INSERTION_DETECTED	(1 << 0)
+
+/* SAR_CTRL (0x13) */
+#define NAU8825_SAR_ADC_EN_SFT	12
+#define NAU8825_SAR_ADC_EN	(1 << NAU8825_SAR_ADC_EN_SFT)
+#define NAU8825_SAR_INPUT_MASK	(1 << 11)
+#define NAU8825_SAR_INPUT_JKSLV	(1 << 11)
+#define NAU8825_SAR_INPUT_JKR2	(0 << 11)
+
+/* KEYDET_CTRL (0x14) */
+#define NAU8825_KEYDET_LEVELS_NR_SFT	8
+#define NAU8825_KEYDET_LEVELS_NR_MASK	(0x7 << 8)
+
+/* I2S_PCM_CTRL1 (0x1c) */
+#define NAU8825_I2S_BP_SFT	7
+#define NAU8825_I2S_BP_MASK	(1 << NAU8825_I2S_BP_SFT)
+#define NAU8825_I2S_BP_INV	(1 << NAU8825_I2S_BP_SFT)
+#define NAU8825_I2S_PCMB_SFT	6
+#define NAU8825_I2S_PCMB_MASK	(1 << NAU8825_I2S_PCMB_SFT)
+#define NAU8825_I2S_PCMB_EN	(1 << NAU8825_I2S_PCMB_SFT)
+#define NAU8825_I2S_DL_SFT	2
+#define NAU8825_I2S_DL_MASK	(0x3 << NAU8825_I2S_DL_SFT)
+#define NAU8825_I2S_DL_16	(0 << NAU8825_I2S_DL_SFT)
+#define NAU8825_I2S_DL_20	(1 << NAU8825_I2S_DL_SFT)
+#define NAU8825_I2S_DL_24	(2 << NAU8825_I2S_DL_SFT)
+#define NAU8825_I2S_DL_32	(3 << NAU8825_I2S_DL_SFT)
+#define NAU8825_I2S_DF_SFT	0
+#define NAU8825_I2S_DF_MASK	(0x3 << NAU8825_I2S_DF_SFT)
+#define NAU8825_I2S_DF_RIGTH	(0 << NAU8825_I2S_DF_SFT)
+#define NAU8825_I2S_DF_LEFT	(1 << NAU8825_I2S_DF_SFT)
+#define NAU8825_I2S_DF_I2S	(2 << NAU8825_I2S_DF_SFT)
+#define NAU8825_I2S_DF_PCM_AB	(3 << NAU8825_I2S_DF_SFT)
+
+/* I2S_PCM_CTRL2 (0x1d) */
+#define NAU8825_I2S_MS_SFT	3
+#define NAU8825_I2S_MS_MASK	(1 << NAU8825_I2S_MS_SFT)
+#define NAU8825_I2S_MS_MASTER	(1 << NAU8825_I2S_MS_SFT)
+#define NAU8825_I2S_MS_SLAVE	(0 << NAU8825_I2S_MS_SFT)
+
+/* ADC_RATE (0x2b) */
+#define NAU8825_ADC_SINC4	(1 << 4)
+#define NAU8825_ADC_SINC_DOWN_MASK	3
+#define NAU8825_ADC_SINC_DOWN_32	0
+#define NAU8825_ADC_SINC_DOWN_64	1
+#define NAU8825_ADC_SINC_DOWN_128	2
+#define NAU8825_ADC_SINC_DOWN_256	3
+
+/* DAC_CTRL1 (0x2c) */
+#define NAU8825_DAC_CLIP_OFF	(1 << 7)
+#define NAU8825_DAC_OVERSAMPLE_MASK	7
+#define NAU8825_DAC_OVERSAMPLE_64	0
+#define NAU8825_DAC_OVERSAMPLE_256	1
+#define NAU8825_DAC_OVERSAMPLE_128	2
+#define NAU8825_DAC_OVERSAMPLE_32	4
+
+/* MUTE_CTRL (0x31) */
+#define NAU8825_DAC_ZERO_CROSSING_EN	(1 << 9)
+#define NAU8825_DAC_SOFT_MUTE	(1 << 9)
+
+/* HSVOL_CTRL (0x32) */
+#define NAU8825_HP_MUTE	(1 << 15)
+
+/* DACL_CTRL (0x33) */
+#define NAU8825_DACL_CH_SEL_SFT	9
+
+/* DACR_CTRL (0x34) */
+#define NAU8825_DACR_CH_SEL_SFT	9
+
+/* I2C_DEVICE_ID (0x58) */
+#define NAU8825_GPIO2JD1	(1 << 7)
+#define NAU8825_SOFTWARE_ID_MASK	0x3
+#define NAU8825_SOFTWARE_ID_NAU8825	0x0
+
+/* BIAS_ADJ (0x66) */
+#define NAU8825_BIAS_VMID	(1 << 6)
+#define NAU8825_BIAS_VMID_SEL_MASK	(3 << 4)
+#define NAU8825_BIAS_VMID_SEL_OPEN	(0 << 4)
+#define NAU8825_BIAS_VMID_SEL_25KOHM	(1 << 4)
+#define NAU8825_BIAS_VMID_SEL_125KOHM	(2 << 4)
+#define NAU8825_BIAS_VMID_SEL_2_5KOHM	(3 << 4)
+
+/* ANALOG_CONTROL_2 (0x6a) */
+#define NAU8825_HP_NON_CLASSG_CURRENT_2xADJ (1 << 12)
+#define NAU8825_DAC_CAPACITOR_MSB (1 << 1)
+#define NAU8825_DAC_CAPACITOR_LSB (1 << 0)
+
+/* ANALOG_ADC_2 (0x72) */
+#define NAU8825_ADC_VREFSEL_MASK	(0x3 << 8)
+#define NAU8825_ADC_VREFSEL_ANALOG	(0 << 8)
+#define NAU8825_ADC_VREFSEL_VMID	(1 << 8)
+#define NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB	(2 << 8)
+#define NAU8825_ADC_VREFSEL_VMID_PLUS_1DB	(3 << 8)
+#define NAU8825_POWERUP_ADCL	(1 << 6)
+
+/* MIC_BIAS (0x74) */
+#define NAU8825_MICBIAS_JKSLV	(1 << 14)
+#define NAU8825_MICBIAS_JKR2	(1 << 12)
+#define NAU8825_MICBIAS_POWERUP_SFT	8
+
+/* BOOST (0x76) */
+#define NAU8825_PRECHARGE_DIS	(1 << 13)
+#define NAU8825_GLOBAL_BIAS_EN	(1 << 12)
+#define NAU8825_HP_BOOST_DIS	(1 << 12)
+#define NAU8825_SHORT_SHUTDOWN_EN	(1 << 6)
+
+/* POWER_UP_CONTROL (0x7f) */
+#define NAU8825_POWERUP_HP_DRV_L	(1 << 0)
+#define NAU8825_POWERUP_HP_DRV_R	(1 << 1)
+#define NAU8825_POWERUP_DRV_IN_L	(1 << 2)
+#define NAU8825_POWERUP_DRV_IN_R	(1 << 3)
+#define NAU8825_POWERUP_INTEGR_L	(1 << 4)
+#define NAU8825_POWERUP_INTEGR_R	(1 << 5)
+
+/* CHARGE_PUMP_AND_POWER_DOWN_CONTROL (0x80) */
+#define NAU8825_JAMNODCLOW	(1 << 10)
+#define NAU8825_POWER_DOWN_DACR	(1 << 9)
+#define NAU8825_POWER_DOWN_DACL	(1 << 8)
+#define NAU8825_CHANRGE_PUMP_EN	(1 << 5)
+
+
+/* System Clock Source */
+enum {
+	NAU8825_CLK_MCLK = 0,
+	NAU8825_CLK_INTERNAL,
+};
+
+int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
+				struct snd_soc_jack *jack);
+
+
+#endif  /* __NAU8825_H__ */