diff mbox

[v6] ASoC: add RT286 CODEC driver

Message ID 1397455170-2701-1-git-send-email-bardliao@realtek.com (mailing list archive)
State New, archived
Headers show

Commit Message

Bard Liao April 14, 2014, 5:59 a.m. UTC
From: Bard Liao <bardliao@realtek.com>

ALC286 is a dual mode codec, which can run as HD-A or I2S mode.
It is controlled by HD-A verb commands via I2C protocol.
The following is the I/O difference between ALC286 and general I2S codecs.
1. A HD-A verb command contains three parts, NID, VID, and PID.
   And an I2S command contains only two parts: address and data.
2. Not only the register address is written, but the read command also
   includes the entire write command.
3. rt286 uses different registers for read and write the same bits.

As a result, regmap is difficult to be used on ALC286.
We don't request a standard I/O by snd_soc_codec_set_cache_io anymore.
Now we have specific write and read functions for ALC286's I/O.
A verb command contains three part: NID, VID, and PID.
We combine NID and VID as reg address, and PID as reg value.
And do corresponding change in rt286_hw_read to get the data properly.

Signed-off-by: Bard Liao <bardliao@realtek.com>
Signed-off-by: Gustaw Lewandowski <gustaw.lewandowski@intel.com>

---
The jack detection function is done by Lewandowski Gustaw.
So I add Gustaw's Signed-off-by line in this patch

The difference between this version and previous version is listed below.
* Implement specific read/write functions rather than use regmap.
* Use dapm widget to control power bit instead of setting it in _event
* Add device ID check

Verb command use two bit to set power state(D0, D1, D2, D3). But dapm widget
use only one bit to set power up/down. So we use dapm widget to switch each
node between D0 and D1 power state. And will set audio group to D3 when bias
level = STANDBY.

---
 include/sound/rt286.h     |   19 +
 sound/soc/codecs/Kconfig  |    4 +
 sound/soc/codecs/Makefile |    2 +
 sound/soc/codecs/rt286.c  | 1054 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/rt286.h  |  186 ++++++++
 5 files changed, 1265 insertions(+)
 create mode 100644 include/sound/rt286.h
 create mode 100644 sound/soc/codecs/rt286.c
 create mode 100644 sound/soc/codecs/rt286.h

Comments

Mark Brown April 15, 2014, 12:04 p.m. UTC | #1
On Mon, Apr 14, 2014 at 01:59:30PM +0800, bardliao@realtek.com wrote:

> +static unsigned int rt286_hw_read(struct snd_soc_codec *codec,
> +						unsigned int reg)
> +{

> +static int rt286_hw_write(struct snd_soc_codec *codec,
> +				unsigned int reg, unsigned int value)
> +{

To repeat what's been said before if you are implementing register I/O
you *MUST* use regmap to do this.  What's not been clear is if this
device has a control interface that looks enough like a register map to
be implemented like one but since we're just guessing based on a
combination of the code and the HDA spec it's not entirely clear.
Bard Liao April 16, 2014, 1:26 p.m. UTC | #2
> -----Original Message-----
> From: Mark Brown [mailto:broonie@kernel.org]
> Sent: Tuesday, April 15, 2014 8:04 PM
> To: Bard Liao
> Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove;
> Oder Chiou; Gustaw Lewandowski
> Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
> 
> On Mon, Apr 14, 2014 at 01:59:30PM +0800, bardliao@realtek.com wrote:
> 
> > +static unsigned int rt286_hw_read(struct snd_soc_codec *codec,
> > +						unsigned int reg)
> > +{
> 
> > +static int rt286_hw_write(struct snd_soc_codec *codec,
> > +				unsigned int reg, unsigned int value) {
> 
> To repeat what's been said before if you are implementing register I/O you
> *MUST* use regmap to do this.  What's not been clear is if this device has a
> control interface that looks enough like a register map to be implemented like one
> but since we're just guessing based on a combination of the code and the HDA spec
> it's not entirely clear.

I am a little bit confused about it. I thought you told me not using regmap.
Actually, I don't know how to use dapm without implementing register I/O.
Can I just convert rt286_hw_read/write to regmap?


> 
> ------Please consider the environment before printing this e-mail.
Mark Brown April 16, 2014, 9:11 p.m. UTC | #3
On Wed, Apr 16, 2014 at 01:26:41PM +0000, Bard Liao wrote:

> > *MUST* use regmap to do this.  What's not been clear is if this device has a
> > control interface that looks enough like a register map to be implemented like one
> > but since we're just guessing based on a combination of the code and the HDA spec
> > it's not entirely clear.

> I am a little bit confused about it. I thought you told me not using regmap.
> Actually, I don't know how to use dapm without implementing register I/O.
> Can I just convert rt286_hw_read/write to regmap?

As far as we've been able to tell thus far (based on inferring stuff
from the HDA spec) you shouldn't be doing register I/O at all, the
device doesn't really have things that look enough like registers.

You can implement non-register DAPM widgets - look at the virtual
controls in the core, or things using SND_SOC_NOPM.
Bard Liao April 17, 2014, 5:39 a.m. UTC | #4
> -----Original Message-----
> From: Mark Brown [mailto:broonie@kernel.org]
> Sent: Thursday, April 17, 2014 5:12 AM
> To: Bard Liao
> Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove;
> Oder Chiou; Gustaw Lewandowski
> Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
> 
> On Wed, Apr 16, 2014 at 01:26:41PM +0000, Bard Liao wrote:
> 
> > > *MUST* use regmap to do this.  What's not been clear is if this
> > > device has a control interface that looks enough like a register map
> > > to be implemented like one but since we're just guessing based on a
> > > combination of the code and the HDA spec it's not entirely clear.
> 
> > I am a little bit confused about it. I thought you told me not using regmap.
> > Actually, I don't know how to use dapm without implementing register I/O.
> > Can I just convert rt286_hw_read/write to regmap?
> 
> As far as we've been able to tell thus far (based on inferring stuff from the HDA
> spec) you shouldn't be doing register I/O at all, the device doesn't really have
> things that look enough like registers.
> 
> You can implement non-register DAPM widgets - look at the virtual controls in the
> core, or things using SND_SOC_NOPM.

It means dapm widget will not control the codec directly, right? 
I.e. Everything will be done by _event?
For simplest the driver, can I not define .dapm_widgets and .dapm_routes?
All controls will be done in .controls.

> 
> ------Please consider the environment before printing this e-mail.
Mark Brown April 18, 2014, 10:37 a.m. UTC | #5
On Thu, Apr 17, 2014 at 05:39:08AM +0000, Bard Liao wrote:

> > You can implement non-register DAPM widgets - look at the virtual controls in the
> > core, or things using SND_SOC_NOPM.

> It means dapm widget will not control the codec directly, right? 
> I.e. Everything will be done by _event?

The framework won't do anything directly, yes.

> For simplest the driver, can I not define .dapm_widgets and .dapm_routes?
> All controls will be done in .controls.

It is mandatory to have at least some DAPM support, not having DAPM was
such a constant source of bugs that we stopped supporting it.
Bard Liao May 6, 2014, 12:04 p.m. UTC | #6
> -----Original Message-----
> From: Mark Brown [mailto:broonie@kernel.org]
> Sent: Friday, April 18, 2014 6:37 PM
> To: Bard Liao
> Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove;
> Oder Chiou; Gustaw Lewandowski
> Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
> 
> On Thu, Apr 17, 2014 at 05:39:08AM +0000, Bard Liao wrote:
> 
> > > You can implement non-register DAPM widgets - look at the virtual
> > > controls in the core, or things using SND_SOC_NOPM.
> 
> > It means dapm widget will not control the codec directly, right?
> > I.e. Everything will be done by _event?
> 
> The framework won't do anything directly, yes.
> 

I have a question about the virtual controls.

Virtual controls assume there is no related register related to the controls, so codec
doesn't need to set anything when the control's value is changed in general cases.
But rt286 need to do something when the control's value is changed especially mux
settings.

I have no idea when should codec driver set the corresponding registers of the controls.
The SND_SOC_DAPM_POST_REG event seems not works in virtual controls. Also, I didn't
find a suitable API to get a virtual control's value. Look like dapm_kcontrol_get_value
can do that, but dapm_kcontrol_get_value is not exported.

I am trying to define a .put function in snd_kcontrol_new, so that I can set corresponding 
registers to the control. But I don't know how to update dapm path's connect status. I don't
know if snd_soc_dapm_mux_update_power can do that.

Do you have any suggestion for setting corresponding registers of a virtual control?

Thanks.

> 
> ------Please consider the environment before printing this e-mail.
Mark Brown May 7, 2014, 5:21 p.m. UTC | #7
On Tue, May 06, 2014 at 12:04:27PM +0000, Bard Liao wrote:
> > -----Original Message-----
> > From: Mark Brown [mailto:broonie@kernel.org]

As I'm fairly sure has been requested before please fix your mailer to
word wrap within paragraphs, it is very hard to read your mails since
the lines are longer than 80 columns.

> Virtual controls assume there is no related register related to the controls, so codec
> doesn't need to set anything when the control's value is changed in general cases.
> But rt286 need to do something when the control's value is changed especially mux
> settings.

...

> Do you have any suggestion for setting corresponding registers of a
> virtual control?

Please extend the APIs for virtual controls if you find they are missing
things you need, they've not been widely used in this sort of context -
I think this is the first substantial device anyone has tried to merge
without a register map so it's likely there are gaps.
Lars-Peter Clausen May 7, 2014, 5:46 p.m. UTC | #8
On 05/07/2014 07:21 PM, Mark Brown wrote:
> On Tue, May 06, 2014 at 12:04:27PM +0000, Bard Liao wrote:
>>> -----Original Message-----
>>> From: Mark Brown [mailto:broonie@kernel.org]
>
> As I'm fairly sure has been requested before please fix your mailer to
> word wrap within paragraphs, it is very hard to read your mails since
> the lines are longer than 80 columns.
>
>> Virtual controls assume there is no related register related to the controls, so codec
>> doesn't need to set anything when the control's value is changed in general cases.
>> But rt286 need to do something when the control's value is changed especially mux
>> settings.
>
> ...
>
>> Do you have any suggestion for setting corresponding registers of a
>> virtual control?
>
> Please extend the APIs for virtual controls if you find they are missing
> things you need, they've not been widely used in this sort of context -
> I think this is the first substantial device anyone has tried to merge
> without a register map so it's likely there are gaps.

I don't think virtual controls are the right approach here. Virtual controls 
are for controls where the state is completely in software, but here the 
state is still in hardware. It's just that there is no uniform hardware map. 
But there is still some structure. So you'd have controls with custom put 
and get handlers and for DAPM widgets use the event handler.

But I still think that the best and easiest solution is to have custom 
regmap read/write function that do the translation of logical registers to 
physical read/write operations.

  - Lars
Mark Brown May 7, 2014, 6:07 p.m. UTC | #9
On Wed, May 07, 2014 at 07:46:09PM +0200, Lars-Peter Clausen wrote:

> I don't think virtual controls are the right approach here. Virtual controls
> are for controls where the state is completely in software, but here the
> state is still in hardware. It's just that there is no uniform hardware map.
> But there is still some structure. So you'd have controls with custom put
> and get handlers and for DAPM widgets use the event handler.

What you just described is what I'd consider a virtual control - I don't
consider the fact that the put callbacks end up writing to the hardware
particularly substantial, as far as the framework is concerned some
driver specific thing goes off and does something but it could be
setting a variable just as well as writing to hardware.

> But I still think that the best and easiest solution is to have custom
> regmap read/write function that do the translation of logical registers to
> physical read/write operations.

That's the first time this idea has been mentioned that I recall.  I
have to say I'm really not keen on pushing non-physical registers
through regmap, all the drivers that have done that previously have
ended up causing problems though if the registers were *all* fake then
there would be less issue (most of the issues have been due to having
extra registers that don't really exist).  It does still feel like it's
a layering problem though.
Lars-Peter Clausen May 7, 2014, 6:21 p.m. UTC | #10
On 05/07/2014 08:07 PM, Mark Brown wrote:
> On Wed, May 07, 2014 at 07:46:09PM +0200, Lars-Peter Clausen wrote:
>
>> I don't think virtual controls are the right approach here. Virtual controls
>> are for controls where the state is completely in software, but here the
>> state is still in hardware. It's just that there is no uniform hardware map.
>> But there is still some structure. So you'd have controls with custom put
>> and get handlers and for DAPM widgets use the event handler.
>
> What you just described is what I'd consider a virtual control - I don't
> consider the fact that the put callbacks end up writing to the hardware
> particularly substantial, as far as the framework is concerned some
> driver specific thing goes off and does something but it could be
> setting a variable just as well as writing to hardware.

Ok, but we have controls that have virtual in their name, so I think it 
should be made clear that you are not talking about those.

>
>> But I still think that the best and easiest solution is to have custom
>> regmap read/write function that do the translation of logical registers to
>> physical read/write operations.
>
> That's the first time this idea has been mentioned that I recall.  I
> have to say I'm really not keen on pushing non-physical registers
> through regmap, all the drivers that have done that previously have
> ended up causing problems though if the registers were *all* fake then
> there would be less issue (most of the issues have been due to having
> extra registers that don't really exist).  It does still feel like it's
> a layering problem though.

I think the drivers you are thinking of are those which had physical 
register which were backed by hardware and virtual register which were 
backed by software. For the later we introduced the virtual controls, so 
that such hacks are no longer necessary in driver. But in the case of the 
RT286 all the register all still backed by hardware except that the way they 
are accessed is not very uniform (different types of registers use a 
different message format, there is a asymmetry between reading and writing 
the same data). In a sense this is similar to devices which have registers 
with different sizes, we also support these with custom regmap callbacks.

- Lars
Mark Brown May 7, 2014, 7:49 p.m. UTC | #11
On Wed, May 07, 2014 at 08:21:02PM +0200, Lars-Peter Clausen wrote:
> On 05/07/2014 08:07 PM, Mark Brown wrote:

> >What you just described is what I'd consider a virtual control - I don't
> >consider the fact that the put callbacks end up writing to the hardware
> >particularly substantial, as far as the framework is concerned some
> >driver specific thing goes off and does something but it could be
> >setting a variable just as well as writing to hardware.

> Ok, but we have controls that have virtual in their name, so I think it
> should be made clear that you are not talking about those.

My first thought would be to extend those so we have callbacks when
required, to be honest I'd forgotten they weren't there already.

> I think the drivers you are thinking of are those which had physical
> register which were backed by hardware and virtual register which were
> backed by software. For the later we introduced the virtual controls, so
> that such hacks are no longer necessary in driver. But in the case of the
> RT286 all the register all still backed by hardware except that the way they
> are accessed is not very uniform (different types of registers use a
> different message format, there is a asymmetry between reading and writing

Right, to me that's not actually a register map at all since it's
missing so many of the assumptions that back up register maps.

> the same data). In a sense this is similar to devices which have registers
> with different sizes, we also support these with custom regmap callbacks.

But those are only breaking that one assumption so they fit into the
idea of a register map much more readily - it's really only the physical
I/O code that actually notices anything, all the cache and other
operations can carry on uninterrupted.
Lars-Peter Clausen May 8, 2014, 7:05 a.m. UTC | #12
On 05/07/2014 09:49 PM, Mark Brown wrote:
> On Wed, May 07, 2014 at 08:21:02PM +0200, Lars-Peter Clausen wrote:
>> On 05/07/2014 08:07 PM, Mark Brown wrote:
>
>>> What you just described is what I'd consider a virtual control - I don't
>>> consider the fact that the put callbacks end up writing to the hardware
>>> particularly substantial, as far as the framework is concerned some
>>> driver specific thing goes off and does something but it could be
>>> setting a variable just as well as writing to hardware.
>
>> Ok, but we have controls that have virtual in their name, so I think it
>> should be made clear that you are not talking about those.
>
> My first thought would be to extend those so we have callbacks when
> required, to be honest I'd forgotten they weren't there already.

But that's just the normal ..._EXT() controls with custom put/get handlers.

Having virtual controls with custom put/get handlers makes no sense, since 
the virtual controls already have put/get function in which virtual-ness of 
the control is implemented.

>
>> I think the drivers you are thinking of are those which had physical
>> register which were backed by hardware and virtual register which were
>> backed by software. For the later we introduced the virtual controls, so
>> that such hacks are no longer necessary in driver. But in the case of the
>> RT286 all the register all still backed by hardware except that the way they
>> are accessed is not very uniform (different types of registers use a
>> different message format, there is a asymmetry between reading and writing
>
> Right, to me that's not actually a register map at all since it's
> missing so many of the assumptions that back up register maps.
>

Well there is a register map, there is just no linear address space. It's 
more hierarchical, but you can still map that hierarchical addressing scheme 
to a linear one. I think what best describes HDA is object oriented RPC. You 
have your objects, called nodes, each node has a unique identifier, called 
the node ID (NID). Then you have functions, called verbs, each verb has a 
specific ID. A node can support a certain set of verbs. And each verb has a 
verb specific payload, which is the functions signature. Most of the verbs 
are some kind of read/write register functions.

E.g. you have the set amplifier gain verb, whose payload looks like:

bool output, bool input, bool left, bool right, int index, bool mute, int gain

output, input, left, right, index select for which amplifier to set the 
parameters and mute and gain are the parameters that should be set.

So the address of the register in which the amplifier gain is stored is the 
NID + It's a amplifier you want to access + whether it's a input or output 
amplifier + whether it's left or right amplifier + amplifier index. You can 
map that onto a linear address space and in the regmap read/write callback 
do the mapping to the appropriate verb payload layout.

>> the same data). In a sense this is similar to devices which have registers
>> with different sizes, we also support these with custom regmap callbacks.
>
> But those are only breaking that one assumption so they fit into the
> idea of a register map much more readily - it's really only the physical
> I/O code that actually notices anything, all the cache and other
> operations can carry on uninterrupted.
>

This is one of the reasons why I suggest using regmap. You probably still 
want caching, you probably still be able to sync the register cache, etc. If 
you don't use regmap you'd have to implement this on your own.
Mark Brown May 8, 2014, 8 a.m. UTC | #13
On Thu, May 08, 2014 at 09:05:01AM +0200, Lars-Peter Clausen wrote:
> On 05/07/2014 09:49 PM, Mark Brown wrote:

> >My first thought would be to extend those so we have callbacks when
> >required, to be honest I'd forgotten they weren't there already.

> But that's just the normal ..._EXT() controls with custom put/get handlers.

Hrm, yes.  That's the issue - I have never really differentiated between
virtual and external enums here, the external controls are (as far as
DAPM is concerned) also virtual.  To be honest I'd forgotten that there
were external controls in DAPM, there's only one user.

> >Right, to me that's not actually a register map at all since it's
> >missing so many of the assumptions that back up register maps.

> Well there is a register map, there is just no linear address space. It's
> more hierarchical, but you can still map that hierarchical addressing scheme
> to a linear one. I think what best describes HDA is object oriented RPC. You

The non-linear addressing isn't the worst bit, I'd say the worst bit is
that the read and write formats for the data aren't consistent with each
other.  That's really the fundamental assumption here.

> have your objects, called nodes, each node has a unique identifier, called
> the node ID (NID). Then you have functions, called verbs, each verb has a
> specific ID. A node can support a certain set of verbs. And each verb has a
> verb specific payload, which is the functions signature. Most of the verbs
> are some kind of read/write register functions.

Yes, it makes sense as RPC (and hence as external controls as I have
been suggesting).

> So the address of the register in which the amplifier gain is stored is the
> NID + It's a amplifier you want to access + whether it's a input or output
> amplifier + whether it's left or right amplifier + amplifier index. You can
> map that onto a linear address space and in the regmap read/write callback
> do the mapping to the appropriate verb payload layout.

It is of course always going to be possible to map any set of settings
onto a register map, the question is if it accomplishes anything to do
so.  Once you're talking about having a mapping function like that
which understands the contents of all the registers it sounds like the
contortions to fit into a register map are more effort than is being
saved and doing nothing for comprehensibility.

> >>the same data). In a sense this is similar to devices which have registers
> >>with different sizes, we also support these with custom regmap callbacks.

> >But those are only breaking that one assumption so they fit into the
> >idea of a register map much more readily - it's really only the physical
> >I/O code that actually notices anything, all the cache and other
> >operations can carry on uninterrupted.

> This is one of the reasons why I suggest using regmap. You probably still
> want caching, you probably still be able to sync the register cache, etc. If
> you don't use regmap you'd have to implement this on your own.

So how does HDA handle this?  We can obviously keep recording settings
in the same way as we do for virtual enums, writing them out shouldn't
be so hard.   The cache code isn't going to buy us much if we have to
write things out control by control anyway, it essentially just boils
down to a fancy list walk.

If you really want to reuse regmap having a write only regmap internal
to the driver (not presented to ASoC) which just remembers the last
value written to every NID/VID combination might work and at least
avoids the ugly bits with trying to convince ASoC there are registers
since you don't need to worry about reading the data back and can just
pretend that read values match written values since we never look at
them except to write them back out.
Lars-Peter Clausen May 8, 2014, 8:57 a.m. UTC | #14
On 05/08/2014 10:00 AM, Mark Brown wrote:
[...]
>> So the address of the register in which the amplifier gain is stored is the
>> NID + It's a amplifier you want to access + whether it's a input or output
>> amplifier + whether it's left or right amplifier + amplifier index. You can
>> map that onto a linear address space and in the regmap read/write callback
>> do the mapping to the appropriate verb payload layout.
>
> It is of course always going to be possible to map any set of settings
> onto a register map, the question is if it accomplishes anything to do
> so.  Once you're talking about having a mapping function like that
> which understands the contents of all the registers it sounds like the
> contortions to fit into a register map are more effort than is being
> saved and doing nothing for comprehensibility.

There is still structure, the mapping function does not have to understand 
each register it only has to understand each verb and the driver uses maybe 
6-7 different verbs, so it is not that bad. The code would basically look 
like this:

write_reg(addr, val)
{
	verb = EXTRACT_VERB(addr);
	nid = EXTRACT_NID(addr);
	pid = EXTRACT_PID(addr);

	switch (verb) {
	case VERB1:
		return write_verb1(nid, pid, val):
	case VERB2:
		return write_verb2(nid, pid, val):
	....
	}
}

Where pid is verb specific additional addressing information that is used in 
the verbs payload.

>
>>>> the same data). In a sense this is similar to devices which have registers
>>>> with different sizes, we also support these with custom regmap callbacks.
>
>>> But those are only breaking that one assumption so they fit into the
>>> idea of a register map much more readily - it's really only the physical
>>> I/O code that actually notices anything, all the cache and other
>>> operations can carry on uninterrupted.
>
>> This is one of the reasons why I suggest using regmap. You probably still
>> want caching, you probably still be able to sync the register cache, etc. If
>> you don't use regmap you'd have to implement this on your own.
>
> So how does HDA handle this?  We can obviously keep recording settings
> in the same way as we do for virtual enums, writing them out shouldn't
> be so hard.   The cache code isn't going to buy us much if we have to
> write things out control by control anyway, it essentially just boils
> down to a fancy list walk.

It's not just the DAPM stuff, but also the normal controls, for which we do 
not have per control caching.

>
> If you really want to reuse regmap having a write only regmap internal
> to the driver (not presented to ASoC) which just remembers the last
> value written to every NID/VID combination might work and at least
> avoids the ugly bits with trying to convince ASoC there are registers
> since you don't need to worry about reading the data back and can just
> pretend that read values match written values since we never look at
> them except to write them back out.

Yes kind of.
Bard Liao May 12, 2014, 11:08 a.m. UTC | #15
2014-05-08 16:57 GMT+08:00 Lars-Peter Clausen <lars@metafoo.de>:
> On 05/08/2014 10:00 AM, Mark Brown wrote:

[...]

Just for confirm.
From the mail thread, what I need to to are:
1. Don't present register map to alsa, use virtrul controls instead.
   I may need to extend the APIs to accomplish the goal.
2. I can (or should?) use regmap internal to codec driver to set the
codec physically.
Did I miss anything?

Thanks.

>
> ------Please consider the environment before printing this e-mail.
Lars-Peter Clausen May 12, 2014, 2:51 p.m. UTC | #16
On 05/12/2014 01:08 PM, Bard Liao wrote:
> 2014-05-08 16:57 GMT+08:00 Lars-Peter Clausen <lars@metafoo.de>:
>> On 05/08/2014 10:00 AM, Mark Brown wrote:
>
> [...]
>
> Just for confirm.
>  From the mail thread, what I need to to are:
> 1. Don't present register map to alsa, use virtrul controls instead.
>     I may need to extend the APIs to accomplish the goal.

To avoid confusion we should not call them virtual controls, but controls 
with custom put/get handlers.

> 2. I can (or should?) use regmap internal to codec driver to set the
> codec physically.
> Did I miss anything?

There are essentially two options.

a) Have custom put/get handlers for different types of verbs. This means 
you'll re-implement things like caching.

b) Have a mapping that maps the hierarchical HDA register addressing 
structure onto a linear address map and use regmap.

- Lars
Mark Brown May 12, 2014, 8:29 p.m. UTC | #17
On Thu, May 08, 2014 at 10:57:44AM +0200, Lars-Peter Clausen wrote:

> There is still structure, the mapping function does not have to understand
> each register it only has to understand each verb and the driver uses maybe
> 6-7 different verbs, so it is not that bad. The code would basically look
> like this:

> write_reg(addr, val)
> {
> 	verb = EXTRACT_VERB(addr);
> 	nid = EXTRACT_NID(addr);
> 	pid = EXTRACT_PID(addr);
> 
> 	switch (verb) {
> 	case VERB1:
> 		return write_verb1(nid, pid, val):
> 	case VERB2:
> 		return write_verb2(nid, pid, val):
> 	....
> 	}
> }

> Where pid is verb specific additional addressing information that is used in
> the verbs payload.

Yeah, it's not exactly a model of niceness though.  It's looking an
awful lot like we didn't manage to abstract things properly and setting
off alarm bells as a result.

> >So how does HDA handle this?  We can obviously keep recording settings
> >in the same way as we do for virtual enums, writing them out shouldn't
> >be so hard.   The cache code isn't going to buy us much if we have to
> >write things out control by control anyway, it essentially just boils
> >down to a fancy list walk.

> It's not just the DAPM stuff, but also the normal controls, for which we do
> not have per control caching.

That seems like a simple matter of programming to resolve.

> >If you really want to reuse regmap having a write only regmap internal
> >to the driver (not presented to ASoC) which just remembers the last
> >value written to every NID/VID combination might work and at least
> >avoids the ugly bits with trying to convince ASoC there are registers
> >since you don't need to worry about reading the data back and can just
> >pretend that read values match written values since we never look at
> >them except to write them back out.

> Yes kind of.

Kind of?  It solves the cache sync case and if it's write only then we
won't need to go to the register map for reads anyway (outside of
interrupt handling and so on).
Bard Liao June 4, 2014, 12:08 p.m. UTC | #18
> -----Original Message-----
> From: Mark Brown [mailto:broonie@kernel.org]
> Sent: Tuesday, May 13, 2014 4:29 AM
> To: Lars-Peter Clausen
> Cc: Bard Liao; lgirdwood@gmail.com; alsa-devel@alsa-project.org; Flove; Oder
> Chiou; Gustaw Lewandowski
> Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
> 
> 
> > >If you really want to reuse regmap having a write only regmap
> > >internal to the driver (not presented to ASoC) which just remembers
> > >the last value written to every NID/VID combination might work and at
> > >least avoids the ugly bits with trying to convince ASoC there are
> > >registers since you don't need to worry about reading the data back
> > >and can just pretend that read values match written values since we
> > >never look at them except to write them back out.
> 
> > Yes kind of.
> 
> Kind of?  It solves the cache sync case and if it's write only then we won't need to
> go to the register map for reads anyway (outside of interrupt handling and so on).

Does "write only regmap" mean read (almost) everything from cache? If so, we
don't need to care about the asymmetry read/write issue. In that case, can we
use regmap for mixer/mux controls just like other drivers do?


> 
> ------Please consider the environment before printing this e-mail.
Mark Brown June 4, 2014, 12:37 p.m. UTC | #19
On Wed, Jun 04, 2014 at 12:08:41PM +0000, Bard Liao wrote:

> > Kind of?  It solves the cache sync case and if it's write only then we won't need to
> > go to the register map for reads anyway (outside of interrupt handling and so on).

> Does "write only regmap" mean read (almost) everything from cache? If so, we
> don't need to care about the asymmetry read/write issue. In that case, can we
> use regmap for mixer/mux controls just like other drivers do?

Yes, exactly - most things should be able to get away with only using
the cache I'd expect.
diff mbox

Patch

diff --git a/include/sound/rt286.h b/include/sound/rt286.h
new file mode 100644
index 0000000..eb773d1
--- /dev/null
+++ b/include/sound/rt286.h
@@ -0,0 +1,19 @@ 
+/*
+ * linux/sound/rt286.h -- Platform data for RT286
+ *
+ * Copyright 2013 Realtek Microelectronics
+ *
+ * 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 __LINUX_SND_RT286_H
+#define __LINUX_SND_RT286_H
+
+struct rt286_platform_data {
+	bool cbj_en; /*combo jack enable*/
+	bool gpio2_en; /*GPIO2 enable*/
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f0e8401..5818391 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -69,6 +69,7 @@  config SND_SOC_ALL_CODECS
 	select SND_SOC_PCM3008
 	select SND_SOC_PCM512x_I2C if I2C
 	select SND_SOC_PCM512x_SPI if SPI_MASTER
+	select SND_SOC_RT286 if I2C
 	select SND_SOC_RT5631 if I2C
 	select SND_SOC_RT5640 if I2C
 	select SND_SOC_SGTL5000 if I2C
@@ -390,6 +391,9 @@  config SND_SOC_PCM512x_SPI
 	select SND_SOC_PCM512x
 	select REGMAP_SPI
 
+config SND_SOC_RT286
+	tristate
+
 config SND_SOC_RT5631
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 3c4d275..5ef8adc 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -58,6 +58,7 @@  snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-pcm512x-objs := pcm512x.o
 snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o
 snd-soc-pcm512x-spi-objs := pcm512x-spi.o
+snd-soc-rt286-objs := rt286.o
 snd-soc-rt5631-objs := rt5631.o
 snd-soc-rt5640-objs := rt5640.o
 snd-soc-sgtl5000-objs := sgtl5000.o
@@ -209,6 +210,7 @@  obj-$(CONFIG_SND_SOC_PCM3008)	+= snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_PCM512x)	+= snd-soc-pcm512x.o
 obj-$(CONFIG_SND_SOC_PCM512x_I2C)	+= snd-soc-pcm512x-i2c.o
 obj-$(CONFIG_SND_SOC_PCM512x_SPI)	+= snd-soc-pcm512x-spi.o
+obj-$(CONFIG_SND_SOC_RT286)	+= snd-soc-rt286.o
 obj-$(CONFIG_SND_SOC_RT5631)	+= snd-soc-rt5631.o
 obj-$(CONFIG_SND_SOC_RT5640)	+= snd-soc-rt5640.o
 obj-$(CONFIG_SND_SOC_SGTL5000)  += snd-soc-sgtl5000.o
diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c
new file mode 100644
index 0000000..a88b3500
--- /dev/null
+++ b/sound/soc/codecs/rt286.c
@@ -0,0 +1,1054 @@ 
+/*
+ * rt286.c  --  RT286 ALSA SoC audio codec driver
+ *
+ * Copyright 2013 Realtek Semiconductor Corp.
+ * Author: Bard Liao <bardliao@realtek.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/acpi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/jack.h>
+#include <linux/workqueue.h>
+#include <sound/rt286.h>
+#include <sound/hda_verbs.h>
+
+#include "rt286.h"
+
+#define RT286_VENDOR_ID 0x10ec0286
+
+#define RT286_INDEX_REG_BASE 0x100
+#define RT286_INDEX_REG_RANGE 0xff
+#define RT286_INDEX_REG_MAX (RT286_INDEX_REG_BASE + RT286_INDEX_REG_RANGE)
+
+struct rt286_priv {
+	struct snd_soc_codec *codec;
+	struct rt286_platform_data pdata;
+	struct i2c_client *i2c;
+	struct snd_soc_jack *jack;
+	struct delayed_work jack_detect_work;
+	int sys_clk;
+};
+
+static unsigned int rt286_hw_read(struct snd_soc_codec *codec,
+						unsigned int reg)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *client = rt286->i2c;
+	struct i2c_msg xfer[2];
+	int ret;
+	__be32 be_reg;
+	unsigned int index, vid, buf = 0x0;
+
+	/*index registers*/
+	if (reg >= RT286_INDEX_REG_BASE && reg < RT286_INDEX_REG_MAX) {
+		ret = snd_soc_write(codec,
+			VERB_CMD(AC_VERB_SET_COEF_INDEX,
+				RT286_VENDOR_REGISTERS, 0),
+			reg - RT286_INDEX_REG_BASE);
+
+		if (ret < 0) {
+			dev_err(codec->dev,
+				"Failed to set private addr: %d\n", ret);
+			return ret;
+		}
+
+		reg = VERB_CMD(AC_VERB_GET_PROC_COEF,
+			RT286_VENDOR_REGISTERS, 0);
+	}
+	reg = reg | 0x80000;
+	vid = (reg >> 8) & 0xfff;
+
+	if (AC_VERB_GET_AMP_GAIN_MUTE == (vid & 0xf00)) {
+		index = (reg >> 8) & 0xf;
+		reg = (reg & ~0xf0f) | index;
+	}
+	be_reg = cpu_to_be32(reg);
+
+	/* Write register */
+	xfer[0].addr = client->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = 4;
+	xfer[0].buf = (u8 *)&be_reg;
+
+	/* Read data */
+	xfer[1].addr = client->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 4;
+	xfer[1].buf = (u8 *)&buf;
+
+	ret = i2c_transfer(client->adapter, xfer, 2);
+	if (ret < 0)
+		return ret;
+	else if (ret != 2)
+		return -EIO;
+
+	return be32_to_cpu(buf);
+}
+
+static int rt286_hw_write(struct snd_soc_codec *codec,
+				unsigned int reg, unsigned int value)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *client = rt286->i2c;
+	u8 data[4];
+	int ret;
+
+	/*index registers*/
+	if (reg >= RT286_INDEX_REG_BASE && reg < RT286_INDEX_REG_MAX) {
+		ret = snd_soc_write(codec,
+			VERB_CMD(AC_VERB_SET_COEF_INDEX,
+				RT286_VENDOR_REGISTERS, 0),
+			reg - RT286_INDEX_REG_BASE);
+		if (ret < 0) {
+			dev_err(codec->dev,
+				"Failed to set private addr: %d\n", ret);
+			return -EIO;
+		}
+		reg = VERB_CMD(AC_VERB_SET_PROC_COEF,
+			RT286_VENDOR_REGISTERS, 0);
+	}
+
+	data[0] = (reg >> 24) & 0xff;
+	data[1] = (reg >> 16) & 0xff;
+	/*
+	 * 4 bit VID: reg should be 0
+	 * 12 bit VID: value should be 0
+	 * So we use an OR operator to handle it rather than use if condition.
+	 */
+	data[2] = ((reg >> 8) & 0xff) | ((value >> 8) & 0xff);
+	data[3] = value & 0xff;
+
+	ret = i2c_master_send(client, data, 4);
+	if (ret == 4)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+static int rt286_support_power_controls[] = {
+	RT286_DAC_OUT1,
+	RT286_DAC_OUT2,
+	RT286_ADC_IN1,
+	RT286_ADC_IN2,
+	RT286_MIC1,
+	RT286_DMIC1,
+	RT286_DMIC2,
+	RT286_SPK_OUT,
+	RT286_HP_OUT,
+};
+#define RT286_POWER_REG_LEN ARRAY_SIZE(rt286_support_power_controls)
+
+static int rt286_jack_detect(struct snd_soc_codec *codec, bool *hp, bool *mic)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val, buf;
+	int i;
+
+	*hp = false;
+	*mic = false;
+
+	if (rt286->pdata.cbj_en) {
+		buf = snd_soc_read(codec, RT286_GET_HP_SENSE);
+		*hp = buf & 0x80000000;
+		if (*hp) {
+			/* power on HV,VERF */
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+				RT286_POWER_CTRL1, 0x1001, 0x0);
+			/* power LDO1 */
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+				RT286_POWER_CTRL2, 0x4, 0x4);
+			snd_soc_write(codec, RT286_SET_MIC1, 0x24);
+			val = snd_soc_read(codec, RT286_INDEX_REG_BASE +
+							RT286_CBJ_CTRL2);
+
+			msleep(200);
+			i = 40;
+			while (((val & 0x0800) == 0) && (i > 0)) {
+				val = snd_soc_read(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_CBJ_CTRL2);
+				i--;
+				msleep(20);
+			}
+
+			if (0x0400 == (val & 0x0700)) {
+				*mic = false;
+
+				snd_soc_write(codec,
+					RT286_SET_MIC1, 0x20);
+				/* power off HV,VERF */
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_POWER_CTRL1, 0x1001, 0x1001);
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_A_BIAS_CTRL3, 0xc000, 0x0000);
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_CBJ_CTRL1, 0x0030, 0x0000);
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_A_BIAS_CTRL2, 0xc000, 0x0000);
+			} else if ((0x0200 == (val & 0x0700)) ||
+				(0x0100 == (val & 0x0700))) {
+				*mic = true;
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_A_BIAS_CTRL3, 0xc000, 0x8000);
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_CBJ_CTRL1, 0x0030, 0x0020);
+				snd_soc_update_bits(codec,
+					RT286_INDEX_REG_BASE +
+					RT286_A_BIAS_CTRL2, 0xc000, 0x8000);
+			} else {
+				*mic = false;
+			}
+
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+						RT286_MISC_CTRL1,
+						0x0060, 0x0000);
+		} else {
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+						RT286_MISC_CTRL1,
+						0x0060, 0x0020);
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+						RT286_A_BIAS_CTRL3,
+						0xc000, 0x8000);
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+						RT286_CBJ_CTRL1,
+						0x0030, 0x0020);
+			snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+						RT286_A_BIAS_CTRL2,
+						0xc000, 0x8000);
+
+			*mic = false;
+		}
+	} else {
+		buf = snd_soc_read(codec, RT286_GET_HP_SENSE);
+		*hp = buf & 0x80000000;
+		buf = snd_soc_read(codec, RT286_GET_MIC1_SENSE);
+		*mic = buf & 0x80000000;
+	}
+
+	return 0;
+}
+
+static void rt286_jack_detect_work(struct work_struct *work)
+{
+	struct rt286_priv *rt286 =
+		container_of(work, struct rt286_priv, jack_detect_work.work);
+	int status = 0;
+
+	bool hp = false;
+	bool mic = false;
+
+	rt286_jack_detect(rt286->codec, &hp, &mic);
+
+	if (hp == true)
+		status |= SND_JACK_HEADPHONE;
+
+	if (mic == true)
+		status |= SND_JACK_MICROPHONE;
+
+	snd_soc_jack_report(rt286->jack, status,
+		SND_JACK_MICROPHONE | SND_JACK_HEADPHONE);
+}
+
+int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	rt286->jack = jack;
+
+	/* Send an initial empty report */
+	snd_soc_jack_report(rt286->jack, 0,
+		SND_JACK_MICROPHONE | SND_JACK_HEADPHONE);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rt286_mic_detect);
+
+static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0);
+static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0);
+
+
+
+static const struct snd_kcontrol_new rt286_snd_controls[] = {
+	SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT286_DACL_GAIN,
+			    RT286_DACR_GAIN, 0, 0x7f, 0, out_vol_tlv),
+	SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT286_ADCL_GAIN,
+			    RT286_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv),
+	SOC_SINGLE_TLV("AMIC Volume", RT286_MIC_GAIN,
+			    0, 0x3, 0, mic_vol_tlv),
+	SOC_DOUBLE_R("Speaker Playback Switch", RT286_SPOL_GAIN,
+			    RT286_SPOR_GAIN, RT286_MUTE_SFT, 1, 1),
+};
+
+/* Digital Mixer */
+static const struct snd_kcontrol_new rt286_front_mix[] = {
+	SOC_DAPM_SINGLE("DAC Switch",  RT286_F_DAC_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+	SOC_DAPM_SINGLE("RECMIX Switch", RT286_F_RECMIX_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+};
+
+/* Analog Input Mixer */
+static const struct snd_kcontrol_new rt286_rec_mix[] = {
+	SOC_DAPM_SINGLE("Mic1 Switch", RT286_REC_MIC_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+	SOC_DAPM_SINGLE("I2S Switch", RT286_REC_I2S_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+	SOC_DAPM_SINGLE("Line1 Switch", RT286_REC_LINE_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+	SOC_DAPM_SINGLE("Beep Switch", RT286_REC_BEEP_SWITCH,
+			RT286_MUTE_SFT, 1, 1),
+};
+
+static const struct snd_kcontrol_new spo_enable_control =
+	SOC_DAPM_SINGLE("Switch", RT286_SET_PIN_SPK,
+			RT286_SET_PIN_SFT, 1, 0);
+
+static const struct snd_kcontrol_new hpol_enable_control =
+	SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOL_GAIN,
+			RT286_MUTE_SFT, 1, 1);
+
+static const struct snd_kcontrol_new hpor_enable_control =
+	SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOR_GAIN,
+			RT286_MUTE_SFT, 1, 1);
+
+/* ADC0 source */
+static const char * const rt286_adc_src[] = {
+	"Mic", "RECMIX", "Dmic"
+};
+
+static const int rt286_adc_values[] = {
+	0, 4, 5,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(
+	rt286_adc0_enum, RT286_ADC0_MUX, RT286_ADC_SEL_SFT,
+	RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values);
+
+static const struct snd_kcontrol_new rt286_adc0_mux =
+	SOC_DAPM_VALUE_ENUM("ADC 0 source", rt286_adc0_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(
+	rt286_adc1_enum, RT286_ADC1_MUX, RT286_ADC_SEL_SFT,
+	RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values);
+
+static const struct snd_kcontrol_new rt286_adc1_mux =
+	SOC_DAPM_VALUE_ENUM("ADC 1 source", rt286_adc1_enum);
+
+static const char * const rt286_dac_src[] = {
+	"Front", "Surround"
+};
+/* HP-OUT source */
+static SOC_ENUM_SINGLE_DECL(rt286_hpo_enum, RT286_HPO_MUX,
+				  0, rt286_dac_src);
+
+static const struct snd_kcontrol_new rt286_hpo_mux =
+SOC_DAPM_ENUM("HPO source", rt286_hpo_enum);
+
+/* SPK-OUT source */
+static SOC_ENUM_SINGLE_DECL(rt286_spo_enum, RT286_SPK_MUX,
+				  0, rt286_dac_src);
+
+static const struct snd_kcontrol_new rt286_spo_mux =
+SOC_DAPM_ENUM("SPO source", rt286_spo_enum);
+
+static int rt286_spk_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_write(codec,
+			RT286_SPK_EAPD, RT286_SET_EAPD_HIGH);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_write(codec,
+			RT286_SPK_EAPD, RT286_SET_EAPD_LOW);
+		break;
+
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static int rt286_set_dmic1_event(struct snd_soc_dapm_widget *w,
+				  struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0x20);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0);
+		break;
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static int rt286_adc_event(struct snd_soc_dapm_widget *w,
+			     struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int nid;
+
+	nid = (w->reg >> 20) & 0xff;
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_update_bits(codec,
+			VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0),
+			0x7080, 0x7000);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_update_bits(codec,
+			VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0),
+			0x7080, 0x7080);
+		break;
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = {
+	/* Input Lines */
+	SND_SOC_DAPM_INPUT("DMIC1 Pin"),
+	SND_SOC_DAPM_INPUT("DMIC2 Pin"),
+	SND_SOC_DAPM_INPUT("MIC1"),
+	SND_SOC_DAPM_INPUT("LINE1"),
+	SND_SOC_DAPM_INPUT("Beep"),
+
+	/* DMIC */
+	SND_SOC_DAPM_PGA_E("DMIC1", RT286_SET_POWER(RT286_DMIC1), 0, 1,
+		NULL, 0, rt286_set_dmic1_event,
+		SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_PGA("DMIC2", RT286_SET_POWER(RT286_DMIC2), 0, 1,
+		NULL, 0),
+	SND_SOC_DAPM_SUPPLY("DMIC Receiver", SND_SOC_NOPM,
+		0, 0, NULL, 0),
+
+	/* REC Mixer */
+	SND_SOC_DAPM_MIXER("RECMIX", SND_SOC_NOPM, 0, 0,
+		rt286_rec_mix, ARRAY_SIZE(rt286_rec_mix)),
+
+	/* ADCs */
+	SND_SOC_DAPM_ADC("ADC 0", NULL, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0),
+
+	/* ADC Mux */
+	SND_SOC_DAPM_MUX_E("ADC 0 Mux", RT286_SET_POWER(RT286_ADC_IN1), 0, 1,
+		&rt286_adc0_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD |
+		SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_MUX_E("ADC 1 Mux", RT286_SET_POWER(RT286_ADC_IN2), 0, 1,
+		&rt286_adc1_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD |
+		SND_SOC_DAPM_POST_PMU),
+
+	/* Audio Interface */
+	SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+	/* Output Side */
+	/* DACs */
+	SND_SOC_DAPM_DAC("DAC 0", NULL, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC 1", NULL, SND_SOC_NOPM, 0, 0),
+
+	/* Output Mux */
+	SND_SOC_DAPM_MUX("SPK Mux", SND_SOC_NOPM, 0, 0, &rt286_spo_mux),
+	SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt286_hpo_mux),
+
+	SND_SOC_DAPM_SUPPLY("HP Power", RT286_SET_PIN_HPO,
+		RT286_SET_PIN_SFT, 0, NULL, 0),
+
+	/* Output Mixer */
+	SND_SOC_DAPM_MIXER("Front", RT286_SET_POWER(RT286_DAC_OUT1), 0, 1,
+			rt286_front_mix, ARRAY_SIZE(rt286_front_mix)),
+	SND_SOC_DAPM_PGA("Surround", RT286_SET_POWER(RT286_DAC_OUT2), 0, 1,
+			NULL, 0),
+
+	/* Output Pga */
+	SND_SOC_DAPM_SWITCH_E("SPO", SND_SOC_NOPM, 0, 0,
+		&spo_enable_control, rt286_spk_event,
+		SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0,
+		&hpol_enable_control),
+	SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0,
+		&hpor_enable_control),
+
+	/* Output Lines */
+	SND_SOC_DAPM_OUTPUT("SPOL"),
+	SND_SOC_DAPM_OUTPUT("SPOR"),
+	SND_SOC_DAPM_OUTPUT("HPO Pin"),
+	SND_SOC_DAPM_OUTPUT("SPDIF"),
+};
+
+static const struct snd_soc_dapm_route rt286_dapm_routes[] = {
+	{"DMIC1", NULL, "DMIC1 Pin"},
+	{"DMIC2", NULL, "DMIC2 Pin"},
+	{"DMIC1", NULL, "DMIC Receiver"},
+	{"DMIC2", NULL, "DMIC Receiver"},
+
+	{"RECMIX", "Beep Switch", "Beep"},
+	{"RECMIX", "Line1 Switch", "LINE1"},
+	{"RECMIX", "Mic1 Switch", "MIC1"},
+
+	{"ADC 0 Mux", "Dmic", "DMIC1"},
+	{"ADC 0 Mux", "RECMIX", "RECMIX"},
+	{"ADC 0 Mux", "Mic", "MIC1"},
+	{"ADC 1 Mux", "Dmic", "DMIC2"},
+	{"ADC 1 Mux", "RECMIX", "RECMIX"},
+	{"ADC 1 Mux", "Mic", "MIC1"},
+
+	{"ADC 0", NULL, "ADC 0 Mux"},
+	{"ADC 1", NULL, "ADC 1 Mux"},
+
+	{"AIF1TX", NULL, "ADC 0"},
+	{"AIF2TX", NULL, "ADC 1"},
+
+	{"DAC 0", NULL, "AIF1RX"},
+	{"DAC 1", NULL, "AIF2RX"},
+
+	{"Front", "DAC Switch", "DAC 0"},
+	{"Front", "RECMIX Switch", "RECMIX"},
+
+	{"Surround", NULL, "DAC 1"},
+
+	{"SPK Mux", "Front", "Front"},
+	{"SPK Mux", "Surround", "Surround"},
+
+	{"HPO Mux", "Front", "Front"},
+	{"HPO Mux", "Surround", "Surround"},
+
+	{"SPO", "Switch", "SPK Mux"},
+	{"HPO L", "Switch", "HPO Mux"},
+	{"HPO R", "Switch", "HPO Mux"},
+	{"HPO L", NULL, "HP Power"},
+	{"HPO R", NULL, "HP Power"},
+
+	{"SPOL", NULL, "SPO"},
+	{"SPOR", NULL, "SPO"},
+	{"HPO Pin", NULL, "HPO L"},
+	{"HPO Pin", NULL, "HPO R"},
+};
+
+static int rt286_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 rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val = 0;
+	int d_len_code;
+
+	switch (params_rate(params)) {
+	/* bit 14 0:48K 1:44.1K */
+	case 44100:
+		val |= 0x4000;
+		break;
+	case 48000:
+		break;
+	default:
+		dev_err(codec->dev, "Unsupported sample rate %d\n",
+					params_rate(params));
+		return -EINVAL;
+	}
+	switch (rt286->sys_clk) {
+	case 12288000:
+	case 24576000:
+		if (params_rate(params) != 48000) {
+			dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n",
+					params_rate(params), rt286->sys_clk);
+			return -EINVAL;
+		}
+		break;
+	case 11289600:
+	case 22579200:
+		if (params_rate(params) != 44100) {
+			dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n",
+					params_rate(params), rt286->sys_clk);
+			return -EINVAL;
+		}
+		break;
+	}
+
+	if (params_channels(params) <= 16) {
+		/* bit 3:0 Number of Channel */
+		val |= (params_channels(params) - 1);
+	} else {
+		dev_err(codec->dev, "Unsupported channels %d\n",
+					params_channels(params));
+		return -EINVAL;
+	}
+
+	d_len_code = 0;
+	switch (params_format(params)) {
+	/* bit 6:4 Bits per Sample */
+	case SNDRV_PCM_FORMAT_S16_LE:
+		d_len_code = 0;
+		val |= (0x1 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		d_len_code = 2;
+		val |= (0x4 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		d_len_code = 1;
+		val |= (0x2 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		d_len_code = 2;
+		val |= (0x3 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S8:
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+		RT286_I2S_CTRL1, 0x0018, d_len_code << 3);
+	dev_dbg(codec->dev, "format val = 0x%x\n", val);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x407f, val);
+	else
+		snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x407f, val);
+
+	return 0;
+}
+
+static int rt286_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x800, 0x800);
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x800, 0x0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x300, 0x0);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x300, 0x1 << 8);
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x300, 0x2 << 8);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x300, 0x3 << 8);
+		break;
+	default:
+		return -EINVAL;
+	}
+	/* bit 15 Stream Type 0:PCM 1:Non-PCM */
+	snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x8000, 0);
+	snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x8000, 0);
+
+	return 0;
+}
+
+static int rt286_set_dai_sysclk(struct snd_soc_dai *dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "%s freq=%d\n", __func__, freq);
+
+	if (RT286_SCLK_S_MCLK == clk_id) {
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x0100, 0x0);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_PLL_CTRL1, 0x20, 0x20);
+	} else {
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x0100, 0x0100);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_PLL_CTRL, 0x4, 0x4);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_PLL_CTRL1, 0x20, 0x0);
+	}
+
+	switch (freq) {
+	case 19200000:
+		if (RT286_SCLK_S_MCLK == clk_id) {
+			dev_err(codec->dev, "Should not use MCLK\n");
+			return -EINVAL;
+		}
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x40, 0x40);
+		break;
+	case 24000000:
+		if (RT286_SCLK_S_MCLK == clk_id) {
+			dev_err(codec->dev, "Should not use MCLK\n");
+			return -EINVAL;
+		}
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x40, 0x0);
+		break;
+	case 12288000:
+	case 11289600:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x8, 0x0);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_CLK_DIV, 0xfc1e, 0x0004);
+		break;
+	case 24576000:
+	case 22579200:
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL2, 0x8, 0x8);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_CLK_DIV, 0xfc1e, 0x5406);
+		break;
+	default:
+		dev_err(codec->dev, "Unsupported system clock\n");
+		return -EINVAL;
+	}
+
+	rt286->sys_clk = freq;
+
+	return 0;
+}
+
+static int rt286_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	dev_dbg(codec->dev, "%s ratio=%d\n", __func__, ratio);
+	if (50 == ratio)
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x1000, 0x1000);
+	else
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+			RT286_I2S_CTRL1, 0x1000, 0x0);
+
+
+	return 0;
+}
+
+static int rt286_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (SND_SOC_BIAS_STANDBY == codec->dapm.bias_level)
+			snd_soc_write(codec,
+				RT286_SET_AUDIO_POWER, AC_PWRST_D0);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		snd_soc_write(codec,
+			RT286_SET_AUDIO_POWER, AC_PWRST_D3);
+		break;
+
+	default:
+		break;
+	}
+	codec->dapm.bias_level = level;
+
+	return 0;
+}
+
+static irqreturn_t rt286_irq(int irq, void *data)
+{
+	struct rt286_priv *rt286 = data;
+	bool hp = false;
+	bool mic = false;
+	int status = 0;
+
+	rt286_jack_detect(rt286->codec, &hp, &mic);
+
+	/* Clear IRQ */
+	snd_soc_update_bits(rt286->codec, RT286_INDEX_REG_BASE +
+					RT286_IRQ_CTRL, 0x1, 0x1);
+
+	if (hp == true)
+		status |= SND_JACK_HEADPHONE;
+
+	if (mic == true)
+		status |= SND_JACK_MICROPHONE;
+
+	snd_soc_jack_report(rt286->jack, status,
+		SND_JACK_MICROPHONE | SND_JACK_HEADPHONE);
+
+	pm_wakeup_event(&rt286->i2c->dev, 300);
+
+	return IRQ_HANDLED;
+}
+
+static int rt286_probe(struct snd_soc_codec *codec)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+
+	ret = snd_soc_read(codec,
+		RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID));
+	if (ret != RT286_VENDOR_ID) {
+		dev_err(codec->dev,
+			"Device with ID register %x is not rt286\n", ret);
+		return -ENODEV;
+	}
+
+	snd_soc_write(codec, RT286_SET_AUDIO_POWER, AC_PWRST_D3);
+
+	for (i = 0; i < RT286_POWER_REG_LEN; i++)
+		snd_soc_write(codec,
+			RT286_SET_POWER(rt286_support_power_controls[i]),
+			AC_PWRST_D1);
+
+	if (!rt286->pdata.cbj_en) {
+		snd_soc_write(codec, RT286_INDEX_REG_BASE +
+				RT286_CBJ_CTRL2, 0x0000);
+		snd_soc_write(codec, RT286_INDEX_REG_BASE +
+				RT286_MIC1_DET_CTRL, 0x0816);
+		snd_soc_write(codec, RT286_INDEX_REG_BASE +
+				RT286_MISC_CTRL1, 0x0000);
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+				RT286_CBJ_CTRL1, 0xf000, 0xb000);
+	} else {
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+				RT286_CBJ_CTRL1, 0xf000, 0x5000);
+	}
+
+	mdelay(10);
+
+	if (!rt286->pdata.gpio2_en)
+		snd_soc_write(codec, RT286_SET_DMIC2_DEFAULT, 0x4000);
+	else
+		snd_soc_write(codec, RT286_SET_DMIC2_DEFAULT, 0);
+
+	mdelay(10);
+
+	/*Power down LDO2*/
+	snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+				RT286_POWER_CTRL2, 0x8, 0x0);
+
+	codec->dapm.bias_level = SND_SOC_BIAS_OFF;
+	rt286->codec = codec;
+
+	if (rt286->i2c->irq) {
+		snd_soc_update_bits(codec, RT286_INDEX_REG_BASE +
+					RT286_IRQ_CTRL, 0x2, 0x2);
+
+		INIT_DELAYED_WORK(&rt286->jack_detect_work,
+					rt286_jack_detect_work);
+		schedule_delayed_work(&rt286->jack_detect_work,
+					msecs_to_jiffies(1250));
+
+		ret = request_threaded_irq(rt286->i2c->irq, NULL, rt286_irq,
+			IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt286", rt286);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to reguest IRQ: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rt286_remove(struct snd_soc_codec *codec)
+{
+	struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec);
+	cancel_delayed_work_sync(&rt286->jack_detect_work);
+
+	return 0;
+}
+
+#define RT286_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8)
+
+static const struct snd_soc_dai_ops rt286_aif_dai_ops = {
+	.hw_params = rt286_hw_params,
+	.set_fmt = rt286_set_dai_fmt,
+	.set_sysclk = rt286_set_dai_sysclk,
+	.set_bclk_ratio = rt286_set_bclk_ratio,
+};
+
+static struct snd_soc_dai_driver rt286_dai[] = {
+	{
+	.name = "rt286-aif1",
+	.id = RT286_AIF1,
+	.playback = {
+		.stream_name = "AIF1 Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = RT286_STEREO_RATES,
+		.formats = RT286_FORMATS,
+	},
+	.capture = {
+		.stream_name = "AIF1 Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = RT286_STEREO_RATES,
+		.formats = RT286_FORMATS,
+	},
+	.ops = &rt286_aif_dai_ops,
+	.symmetric_rates = 1,
+	},
+	{
+		.name = "rt286-aif2",
+		.id = RT286_AIF2,
+		.playback = {
+			.stream_name = "AIF2 Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = RT286_STEREO_RATES,
+			.formats = RT286_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF2 Capture",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = RT286_STEREO_RATES,
+			.formats = RT286_FORMATS,
+		},
+		.ops = &rt286_aif_dai_ops,
+		.symmetric_rates = 1,
+	},
+
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_rt286 = {
+	.probe = rt286_probe,
+	.remove = rt286_remove,
+	.set_bias_level = rt286_set_bias_level,
+	.idle_bias_off = true,
+	.write = rt286_hw_write,
+	.read = rt286_hw_read,
+	.controls = rt286_snd_controls,
+	.num_controls = ARRAY_SIZE(rt286_snd_controls),
+	.dapm_widgets = rt286_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(rt286_dapm_widgets),
+	.dapm_routes = rt286_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(rt286_dapm_routes),
+};
+
+static const struct i2c_device_id rt286_i2c_id[] = {
+	{"rt286", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, rt286_i2c_id);
+
+static const struct acpi_device_id rt286_acpi_match[] = {
+	{ "INT343A", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, rt286_acpi_match);
+
+static int rt286_i2c_probe(struct i2c_client *i2c,
+			   const struct i2c_device_id *id)
+{
+	struct rt286_platform_data *pdata = dev_get_platdata(&i2c->dev);
+	struct rt286_priv *rt286;
+	int ret;
+
+	rt286 = devm_kzalloc(&i2c->dev,	sizeof(*rt286),
+				GFP_KERNEL);
+	if (NULL == rt286)
+		return -ENOMEM;
+
+	rt286->i2c = i2c;
+	i2c_set_clientdata(i2c, rt286);
+
+	if (pdata)
+		rt286->pdata = *pdata;
+
+	ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt286,
+				     rt286_dai, ARRAY_SIZE(rt286_dai));
+
+	return ret;
+}
+
+static int rt286_i2c_remove(struct i2c_client *i2c)
+{
+	struct rt286_priv *rt286 = i2c_get_clientdata(i2c);
+
+	if (i2c->irq)
+		free_irq(i2c->irq, rt286);
+	snd_soc_unregister_codec(&i2c->dev);
+
+	return 0;
+}
+
+
+struct i2c_driver rt286_i2c_driver = {
+	.driver = {
+		   .name = "rt286",
+		   .owner = THIS_MODULE,
+		   .acpi_match_table = ACPI_PTR(rt286_acpi_match),
+		   },
+	.probe = rt286_i2c_probe,
+	.remove = rt286_i2c_remove,
+	.id_table = rt286_i2c_id,
+};
+
+module_i2c_driver(rt286_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC RT286 driver");
+MODULE_AUTHOR("Bard Liao <bardliao@realtek.com>");
+MODULE_LICENSE("GPL");
+
+/*
+ * ALC286 is a dual mode codec, which can run as HD-A or I2S mode.
+ * It is controlled by HD-A verb commands via I2C protocol.
+ * The following is the I/O difference between ALC286 and general I2S codecs.
+ * 1. A HD-A verb command contains three parts, NID, VID, and PID.
+ *    And an I2S command contains only two parts: address and data.
+ * 2. Not only the register address is written, but the read command also
+      includes the entire write command.
+ * 3. rt286 uses different registers for read and write the same bits.
+ * As a result, regmap is difficult to be used on ALC286.
+ * We don't request a standard I/O by snd_soc_codec_set_cache_io anymore.
+ * Now we have specific write and read functions for ALC286's I/O.
+ * A verb command contains three part: NID, VID, and PID.
+ * We combine NID and VID as reg address, and PID as reg value.
+ * And do corresponding change in rt286_hw_read to get the data properly.
+ */
diff --git a/sound/soc/codecs/rt286.h b/sound/soc/codecs/rt286.h
new file mode 100644
index 0000000..eaa093f
--- /dev/null
+++ b/sound/soc/codecs/rt286.h
@@ -0,0 +1,186 @@ 
+/*
+ * rt286.h  --  RT286 ALSA SoC audio driver
+ *
+ * Copyright 2011 Realtek Microelectronics
+ * Author: Johnny Hsu <johnnyhsu@realtek.com>
+ *
+ * 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 __RT286_H__
+#define __RT286_H__
+
+#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D)
+
+#define RT286_AUDIO_FUNCTION_GROUP			0x01
+#define RT286_DAC_OUT1					0x02
+#define RT286_DAC_OUT2					0x03
+#define RT286_ADC_IN1					0x09
+#define RT286_ADC_IN2					0x08
+#define RT286_MIXER_IN					0x0b
+#define RT286_MIXER_OUT1				0x0c
+#define RT286_MIXER_OUT2				0x0d
+#define RT286_DMIC1					0x12
+#define RT286_DMIC2					0x13
+#define RT286_SPK_OUT					0x14
+#define RT286_MIC1					0x18
+#define RT286_LINE1					0x1a
+#define RT286_BEEP					0x1d
+#define RT286_SPDIF					0x1e
+#define RT286_VENDOR_REGISTERS				0x20
+#define RT286_HP_OUT					0x21
+#define RT286_MIXER_IN1					0x22
+#define RT286_MIXER_IN2					0x23
+
+#define RT286_SET_PIN_SFT				6
+#define RT286_SET_PIN_ENABLE				0x40
+#define RT286_SET_PIN_DISABLE				0
+#define RT286_SET_EAPD_HIGH				0x2
+#define RT286_SET_EAPD_LOW				0
+
+#define RT286_MUTE_SFT					7
+
+/* Verb commands */
+#define RT286_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM)
+#define RT286_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0)
+#define RT286_SET_AUDIO_POWER RT286_SET_POWER(RT286_AUDIO_FUNCTION_GROUP)
+#define RT286_SET_HPO_POWER RT286_SET_POWER(RT286_HP_OUT)
+#define RT286_SET_SPK_POWER RT286_SET_POWER(RT286_SPK_OUT)
+#define RT286_SET_DMIC1_POWER RT286_SET_POWER(RT286_DMIC1)
+#define RT286_SPK_MUX\
+	VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_SPK_OUT, 0)
+#define RT286_HPO_MUX\
+	VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_HP_OUT, 0)
+#define RT286_ADC0_MUX\
+	VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN1, 0)
+#define RT286_ADC1_MUX\
+	VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN2, 0)
+#define RT286_SET_MIC1\
+	VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_MIC1, 0)
+#define RT286_SET_PIN_HPO\
+	VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_HP_OUT, 0)
+#define RT286_SET_PIN_SPK\
+	VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_SPK_OUT, 0)
+#define RT286_SET_PIN_DMIC1\
+	VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_DMIC1, 0)
+#define RT286_SPK_EAPD\
+	VERB_CMD(AC_VERB_SET_EAPD_BTLENABLE, RT286_SPK_OUT, 0)
+#define RT286_SET_AMP_GAIN_HPO\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0)
+#define RT286_GET_HP_SENSE\
+	VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_HP_OUT, 0)
+#define RT286_GET_MIC1_SENSE\
+	VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_MIC1, 0)
+#define RT286_SET_DMIC2_DEFAULT\
+	VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT286_DMIC2, 0)
+#define RT286_DACL_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0xa000)
+#define RT286_DACR_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0x9000)
+#define RT286_ADCL_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x6000)
+#define RT286_ADCR_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x5000)
+#define RT286_MIC_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIC1, 0x7000)
+#define RT286_SPOL_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0xa000)
+#define RT286_SPOR_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0x9000)
+#define RT286_HPOL_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0xa000)
+#define RT286_HPOR_GAIN\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0x9000)
+#define RT286_F_DAC_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7000)
+#define RT286_F_RECMIX_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7100)
+#define RT286_REC_MIC_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7000)
+#define RT286_REC_I2S_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7100)
+#define RT286_REC_LINE_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7200)
+#define RT286_REC_BEEP_SWITCH\
+	VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7300)
+#define RT286_DAC_FORMAT\
+	VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_DAC_OUT1, 0)
+#define RT286_ADC_FORMAT\
+	VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_ADC_IN1, 0)
+
+
+/* Index registers */
+#define RT286_A_BIAS_CTRL1	0x01
+#define RT286_A_BIAS_CTRL2	0x02
+#define RT286_POWER_CTRL1	0x03
+#define RT286_A_BIAS_CTRL3	0x04
+#define RT286_POWER_CTRL2	0x08
+#define RT286_I2S_CTRL1		0x09
+#define RT286_I2S_CTRL2		0x0a
+#define RT286_CLK_DIV		0x0b
+#define RT286_POWER_CTRL3	0x0f
+#define RT286_MIC1_DET_CTRL	0x19
+#define RT286_MISC_CTRL1	0x20
+#define RT286_IRQ_CTRL		0x33
+#define RT286_PLL_CTRL1		0x49
+#define RT286_CBJ_CTRL1		0x4f
+#define RT286_CBJ_CTRL2		0x50
+#define RT286_PLL_CTRL		0x63
+
+/* SPDIF (0x06) */
+#define RT286_SPDIF_SEL_SFT	0
+#define RT286_SPDIF_SEL_PCM0	0
+#define RT286_SPDIF_SEL_PCM1	1
+#define RT286_SPDIF_SEL_SPOUT	2
+#define RT286_SPDIF_SEL_PP	3
+
+/* RECMIX (0x0b) */
+#define RT286_M_REC_BEEP_SFT	0
+#define RT286_M_REC_LINE1_SFT	1
+#define RT286_M_REC_MIC1_SFT	2
+#define RT286_M_REC_I2S_SFT	3
+
+/* Front (0x0c) */
+#define RT286_M_FRONT_DAC_SFT	0
+#define RT286_M_FRONT_REC_SFT	1
+
+/* SPK-OUT (0x14) */
+#define RT286_M_SPK_MUX_SFT	14
+#define RT286_SPK_SEL_MASK	0x1
+#define RT286_SPK_SEL_SFT	0
+#define RT286_SPK_SEL_F		0
+#define RT286_SPK_SEL_S		1
+
+/* HP-OUT (0x21) */
+#define RT286_M_HP_MUX_SFT	14
+#define RT286_HP_SEL_MASK	0x1
+#define RT286_HP_SEL_SFT	0
+#define RT286_HP_SEL_F		0
+#define RT286_HP_SEL_S		1
+
+/* ADC (0x22) (0x23) */
+#define RT286_ADC_SEL_MASK	0x7
+#define RT286_ADC_SEL_SFT	0
+#define RT286_ADC_SEL_SURR	0
+#define RT286_ADC_SEL_FRONT	1
+#define RT286_ADC_SEL_DMIC	2
+#define RT286_ADC_SEL_BEEP	4
+#define RT286_ADC_SEL_LINE1	5
+#define RT286_ADC_SEL_I2S	6
+#define RT286_ADC_SEL_MIC1	7
+
+#define RT286_SCLK_S_MCLK	0
+#define RT286_SCLK_S_PLL	1
+
+enum {
+	RT286_AIF1,
+	RT286_AIF2,
+	RT286_AIFS,
+};
+
+int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack);
+
+#endif /* __RT286_H__ */
+