* Update on TLV320AIC3204 Driver
@ 2010-06-11 5:55 Stuart Longland
2010-06-11 10:18 ` Mark Brown
0 siblings, 1 reply; 22+ messages in thread
From: Stuart Longland @ 2010-06-11 5:55 UTC (permalink / raw)
To: alsa-devel
Cc: Takashi Iwai, Mark Brown, Eric =?unknown-8bit?Q?B=C3=A9nard?=,
Liam Girdwood
Hi all,
I've managed to get significantly further along with a driver
for the TLV320AIC3204, to the point now that I'm starting to look at
further things to tidy up for its inclusion into the kernel. Some
issues I've faced however:
(1) When using the SOC_DOUBLE_R_SX_TLV controls, I notice there's an
odd interaction between the mute switch associated with the
control, and its corresponding gain setting.
Nothing changes in the actual registers (except the mute bit of
course) but the displayed gain shoots up to infinity if the mute
is toggled when the gain associated with that mute control is
set below 0dB. When the gain is at 0dB or above, toggling the
mute has no effect on the displayed gain setting.
Has anyone noticed this before and what would be the cause?
For reference; the controls are defined in lines 395..414 of
tlv320aic3204.c.
(2) On the TLV320AIC3204, the clocks used on the audio bus can either
be sourced from the ADC clocks, or the DAC clocks. Naturally,
before you see any clocks on the bus, you must first power up
either the DAC or ADC (or both), and choose the appropriate
source. I do this currently in the hw_params callback (right
where I *used* to power up the DACs/ADCs, DAPM does this now).
The issue I see, is what happens when a playback stream starts,
followed later by a recording stream. All will be fine until
the recording stream stops -- if the playback is still going, I
fear the clocks will get shut off with the DAC, and the
recording will come to a grinding halt. Duplex will be okay
since both recording and playback are synchronised events in
this case... but I can see the potential for this alternate case
causing problems.
Therefore, I ask: Is there a way to control this via DAPM, so
that, when the component supplying the bit clocks is shut down,
the serial interface clock source is switched automatically to
the alternate source? (i.e. if DAC is shut down, switch to
using the ADC clocks)
For reference; the switching presently occurs in lines 917..924
of tlv320aic3204.c.
(3) The sysfs interface of my driver still remains. I had a look at
using the ASoC debugfs interface, however there are a *lot* of
registers on the 'AIC3204 in a sparse memory map. When I try to
inspect the registers via
/sys/kernel/debug/asoc/tlv320aic3204.0-0018/codec_reg ... I
notice there are more registers in the device than are
accessible via this interface:
karo ~ # tail
/sys/kernel/debug/asoc/tlv320aic3204.0-0018/codec_reg
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 01
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 02
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 03
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 00
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 01
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 02
tlv320aic3204-i2c 0-0018: aic3204_write: pg 0 reg 0[0000] <= 03
1a7: 0
1a8: 0
1a9: 0
1aa: 0
1ab: 0
1ac: 0
1ad: 0
1ae: 0
1af: 0
1b0: karo ~ #
Notice it stops at 0x01b0 (corresponding to page 3, register
48), with an incomplete line. There were more registers, than
there was buffer space to write the file. Is there a way to
increase this buffer?
Or alternatively, should I perhaps port my sysfs interface to
debugfs, and modify it to augment the existing debugfs
interface?
The driver is still using the registration model that was used in kernel
2.6.34. I've managed to backport the ALSA tree to 2.6.28 for our
project (since that's what's officially supported by Ka-Ro) and so far,
it's working, although things are still quite crude.
Since my last contact I've managed to figure out some aspects of DAPM,
for instance the DACs and ADCs are powered up and down by DAPM, whereas
I used to do this when setting the PLL manually.
I cache more of the registers now, I tried doing something "clever" and
skipping the pages of registers that aren't used and trying to cache
just the pages that were meaningful, but I noticed that the rest of ALSA
more or less assumed the register address used with cache was the real
address. Digging around on the mailing list about how to handle a
sparse register map lead me to the solution of just biting the bullet
and caching all 80-odd pages. Wasteful on memory, but I won't miss
anything.
The driver still has an initialisation script facility, whereby custom
register settings can be made at init. I'm still undecided as to
whether to keep this or not... it's not the "done thing", but if I leave
it there, it allows for custom adaptive filtering coefficients and other
parameters to be set by the machine driver, which may be useful in some
applications. Is it worth cleaning this up, or should it be ditched
before the driver is submitted for inclusion?
The driver is accessible online at
<http://www.longlandclan.yi.org/~stuartl/asoc/> along with some comments
about how to add it to your kernel sources. I'd appreciate any feedback
or advice on how the driver can be improved.
Regards,
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-11 5:55 Update on TLV320AIC3204 Driver Stuart Longland
@ 2010-06-11 10:18 ` Mark Brown
2010-06-15 0:27 ` Stuart Longland
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
0 siblings, 2 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-11 10:18 UTC (permalink / raw)
To: Stuart Longland; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
> (1) When using the SOC_DOUBLE_R_SX_TLV controls, I notice there's an
> odd interaction between the mute switch associated with the
> control, and its corresponding gain setting.
> Nothing changes in the actual registers (except the mute bit of
> course) but the displayed gain shoots up to infinity if the mute
> is toggled when the gain associated with that mute control is
> set below 0dB. When the gain is at 0dB or above, toggling the
> mute has no effect on the displayed gain setting.
This is almost always due to having an overlap between the bitfield for
the volume and the mute bit.
> (2) On the TLV320AIC3204, the clocks used on the audio bus can either
> be sourced from the ADC clocks, or the DAC clocks. Naturally,
> before you see any clocks on the bus, you must first power up
> either the DAC or ADC (or both), and choose the appropriate
> source. I do this currently in the hw_params callback (right
> where I *used* to power up the DACs/ADCs, DAPM does this now).
SND_SOC_DAPM_SUPPLY can help you here - you can have a conditional
supply.
> (3) The sysfs interface of my driver still remains. I had a look at
> using the ASoC debugfs interface, however there are a *lot* of
> registers on the 'AIC3204 in a sparse memory map. When I try to
> inspect the registers via
> /sys/kernel/debug/asoc/tlv320aic3204.0-0018/codec_reg ... I
> notice there are more registers in the device than are
> accessible via this interface:
Provide a display_register() callback which filters out the undefined
registers or otherwise improve the standard functionality.
> Notice it stops at 0x01b0 (corresponding to page 3, register
> 48), with an incomplete line. There were more registers, than
> there was buffer space to write the file. Is there a way to
> increase this buffer?
No, it's a sysfs limit.
> The driver is still using the registration model that was used in kernel
> 2.6.34. I've managed to backport the ALSA tree to 2.6.28 for our
> project (since that's what's officially supported by Ka-Ro) and so far,
> it's working, although things are still quite crude.
If you could convince Ka-Ro to work with mainline that'd be nice :/
> I cache more of the registers now, I tried doing something "clever" and
> skipping the pages of registers that aren't used and trying to cache
> just the pages that were meaningful, but I noticed that the rest of ALSA
> more or less assumed the register address used with cache was the real
> address. Digging around on the mailing list about how to handle a
> sparse register map lead me to the solution of just biting the bullet
> and caching all 80-odd pages. Wasteful on memory, but I won't miss
> anything.
If the registers are mostly clustered at the starts of pages then an
array of pointers to per-page arrays might do the trick.
> The driver still has an initialisation script facility, whereby custom
> register settings can be made at init. I'm still undecided as to
> whether to keep this or not... it's not the "done thing", but if I leave
> it there, it allows for custom adaptive filtering coefficients and other
> parameters to be set by the machine driver, which may be useful in some
> applications. Is it worth cleaning this up, or should it be ditched
> before the driver is submitted for inclusion?
If the data is stuff that's calculated off-line then it's reasonable to
supply specific bits of data via platform data, several existing drivers
do this for filter coefficients. Providing completely arbatrary
register writes is not suitable for mainline.
> The driver is accessible online at
> <http://www.longlandclan.yi.org/~stuartl/asoc/> along with some comments
> about how to add it to your kernel sources. I'd appreciate any feedback
> or advice on how the driver can be improved.
Please post to the mailing list if you want review - the standard
workflow is to provide comments by replying to the patch.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-11 10:18 ` Mark Brown
@ 2010-06-15 0:27 ` Stuart Longland
2010-06-15 5:11 ` Stuart Longland
2010-06-15 7:21 ` Update on TLV320AIC3204 Driver Mark Brown
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
1 sibling, 2 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 0:27 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Fri, Jun 11, 2010 at 11:18:04AM +0100, Mark Brown wrote:
> On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
>
> > (1) When using the SOC_DOUBLE_R_SX_TLV controls, I notice there's an
> > odd interaction between the mute switch associated with the
> > control, and its corresponding gain setting.
>
> > Nothing changes in the actual registers (except the mute bit of
> > course) but the displayed gain shoots up to infinity if the mute
> > is toggled when the gain associated with that mute control is
> > set below 0dB. When the gain is at 0dB or above, toggling the
> > mute has no effect on the displayed gain setting.
>
> This is almost always due to having an overlap between the bitfield for
> the volume and the mute bit.
I'll have a closer look, perhaps there's some misunderstanding on my
part as to how the macros work.
I've started documenting some of this stuff on the ALSA Wiki at
<http://www.alsa-project.org/main/index.php/DAPM>. I'm hoping this
might be a useful resource for others. Was about to do it on an
intranet wiki here, but figured it'd be of benefit to others too.
> > (2) On the TLV320AIC3204, the clocks used on the audio bus can either
> > be sourced from the ADC clocks, or the DAC clocks. Naturally,
> > before you see any clocks on the bus, you must first power up
> > either the DAC or ADC (or both), and choose the appropriate
> > source. I do this currently in the hw_params callback (right
> > where I *used* to power up the DACs/ADCs, DAPM does this now).
>
> SND_SOC_DAPM_SUPPLY can help you here - you can have a conditional
> supply.
Is there an example of the supply doing something like this? I'm
thinking a mux might be the way to represent it, but it's then a matter
of getting DAPM to switch the MUX over to the other source when the
current source gets shut down.
The DAC clocks disappear when the DAC powers down. Likewise with the
ADC in this case. I'm not sure how to represent this in terms of DAPM
widgets, but I'll have a closer look at the other CODEC drivers.
> > (3) The sysfs interface of my driver still remains. I had a look at
> > using the ASoC debugfs interface, however there are a *lot* of
> > registers on the 'AIC3204 in a sparse memory map. When I try to
> > inspect the registers via
> > /sys/kernel/debug/asoc/tlv320aic3204.0-0018/codec_reg ... I
> > notice there are more registers in the device than are
> > accessible via this interface:
>
> Provide a display_register() callback which filters out the undefined
> registers or otherwise improve the standard functionality.
I shall look into this... but a thought, is there any merrit in the
two-file (register select" and "register data") interface to the CODEC
registers? The current interface looks great if you want to quickly get
a register dump, but the solution I have is easily used in scripts.
If there's some interest I can port my SYSFS interface over to DebugFS,
and move it into the DAPM core where it can be used by others.
Basically the interface I had come up with, was meant to directly
replace i2cset and i2cget which become unusable once a driver claims
control of an I²C device.
> > The driver is still using the registration model that was used in kernel
> > 2.6.34. I've managed to backport the ALSA tree to 2.6.28 for our
> > project (since that's what's officially supported by Ka-Ro) and so far,
> > it's working, although things are still quite crude.
>
> If you could convince Ka-Ro to work with mainline that'd be nice :/
Indeed, I'll start looking into that at a later date, as it was a major
thorn in our side not being able to grab the latest mainline kernel and
have it "just work". That said, I can also understand the trouble it
might cause from a support point-of-view. Hence the softly-softly
approach. :-)
I have a patch for 2.6.34 that adds support for the Ka-Ro TX27, but I'd
like to run it past Ka-Ro before I make it publically available ... to
prevent them getting a barrage of queries about a kernel they don't
support.
In any case, the move from 2.6.34 to git HEAD is not a big one, and I'll
do that as one of the final steps before submitting a patch of the
driver.
> > I cache more of the registers now, I tried doing something "clever" and
> > skipping the pages of registers that aren't used and trying to cache
> > just the pages that were meaningful, but I noticed that the rest of ALSA
> > more or less assumed the register address used with cache was the real
> > address. Digging around on the mailing list about how to handle a
> > sparse register map lead me to the solution of just biting the bullet
> > and caching all 80-odd pages. Wasteful on memory, but I won't miss
> > anything.
>
> If the registers are mostly clustered at the starts of pages then an
> array of pointers to per-page arrays might do the trick.
As in this?
static u8 aic3204_reg_page0[128];
static u8 aic3204_reg_page1[128];
static u8 aic3204_reg_page8[128];
static u8 aic3204_reg_page9[128];
/* ... */
static u8* aic3204_reg_cache[81] = {
aic3204_reg_page0,
aic3204_reg_page1,
NULL,
NULL,
NULL,
/* ... */
aic3204_reg_page8,
aic3204_reg_page9,
/* ... */
};
I wasn't sure how compatible that would be with the rest of ALSA, but I
might give that a try shortly.
> > The driver still has an initialisation script facility, whereby custom
> > register settings can be made at init. I'm still undecided as to
> > whether to keep this or not... it's not the "done thing", but if I leave
> > it there, it allows for custom adaptive filtering coefficients and other
> > parameters to be set by the machine driver, which may be useful in some
> > applications. Is it worth cleaning this up, or should it be ditched
> > before the driver is submitted for inclusion?
>
> If the data is stuff that's calculated off-line then it's reasonable to
> supply specific bits of data via platform data, several existing drivers
> do this for filter coefficients. Providing completely arbatrary
> register writes is not suitable for mainline.
I didn't think it would be somehow. :-) I'll have a look for a few
examples and see if I can convert this driver across. For what it's
worth, I haven't yet looked at the adaptive filtering capabilities of
this CODEC -- it does have them.
> > The driver is accessible online at
> > <http://www.longlandclan.yi.org/~stuartl/asoc/> along with some comments
> > about how to add it to your kernel sources. I'd appreciate any feedback
> > or advice on how the driver can be improved.
>
> Please post to the mailing list if you want review - the standard
> workflow is to provide comments by replying to the patch.
No problems, I'll post the code to the list in a moment. Last time I
tried this it got held for moderation because the email was too big.
Hence my posting it to a website and linking to it. I'll post the files
up in my next email. When things are a little more mature, I'll post
the patch for the driver.
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 1/3]
2010-06-11 10:18 ` Mark Brown
2010-06-15 0:27 ` Stuart Longland
@ 2010-06-15 0:29 ` Stuart Longland
2010-06-15 0:33 ` Update on TLV320AIC3204 Driver [File 2/3] Stuart Longland
` (2 more replies)
1 sibling, 3 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 0:29 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
[-- Attachment #1: Type: text/plain, Size: 821 bytes --]
On Fri, Jun 11, 2010 at 11:18:04AM +0100, Mark Brown wrote:
> On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
> > The driver is accessible online at
> > <http://www.longlandclan.yi.org/~stuartl/asoc/> along with some comments
> > about how to add it to your kernel sources. I'd appreciate any feedback
> > or advice on how the driver can be improved.
>
> Please post to the mailing list if you want review - the standard
> workflow is to provide comments by replying to the patch.
Attached :- sound/soc/codecs/tlv320aic3204.c
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
[-- Attachment #2: tlv320aic3204.c --]
[-- Type: text/x-c, Size: 45311 bytes --]
/*
* ALSA SoC TLV320AIC3204 codec driver
*
* Author: Stuart Longland, <redhatter@gentoo.org>
* Copyright: (C) 2010 Jacques Electronics Pty. Ltd.
*
* Based upon the TLV320AIC3X driver:
* Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
* Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.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 DEBUG
#define DEBUG
#endif
#include <linux/device.h>
#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/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <linux/slab.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/tlv320aic3204.h>
#include "tlv320aic3204.h"
/* TODO: PLL */
#define ENABLE_PLL
/* TODO: Make this configurable */
#define AIC3204_OSR 128
static int aic3204_read(struct snd_soc_codec *codec, unsigned int reg,
u8 *value);
/* SYSFS Interface -- we should move this to debugfs */
static ssize_t aic3204_show_regsel(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t aic3204_store_regsel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t aic3204_show_regdata(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t aic3204_store_regdata(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static DEVICE_ATTR(regsel, S_IWUSR | S_IRUGO,
aic3204_show_regsel, aic3204_store_regsel);
static DEVICE_ATTR(regdata, S_IWUSR | S_IRUGO,
aic3204_show_regdata, aic3204_store_regdata);
#define AIC3204_NUM_SUPPLIES 4
static const char *aic3204_supply_names[AIC3204_NUM_SUPPLIES] = {
"IOVDD", /* I/O Voltage */
"DVDD", /* Digital Core Voltage */
"AVDD", /* Analog DAC Voltage */
"LDOin", /* Supply for internal LDOs and output amplifiers */
};
/* codec private data */
struct aic3204_priv {
struct snd_soc_codec codec;
struct regulator_bulk_data supplies[AIC3204_NUM_SUPPLIES];
unsigned int sysclk;
int gpio_reset;
/* ADC/DAC Channels enabled for use */
u8 channels;
/* For SYSFS; register selection */
u16 sysfs_reg;
};
static int aic3204_read(struct snd_soc_codec *codec, unsigned int reg,
u8 *value);
/*
* read aic3204 register cache
*/
static inline unsigned int aic3204_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
u8 *cache = codec->reg_cache;
if (reg >= AIC3204_CACHEREGNUM) {
dev_err( codec->dev, "%s: reg 0x%08x out of range!\n",
__func__, reg );
return -EINVAL;
}
if ( reg ) {
/* Refresh cache */
u8 val;
int ret = aic3204_read( codec, reg, &val );
if ( ret >= 0 )
cache[reg] = val;
}
return cache[reg];
}
/*
* write aic3204 register cache
*/
static inline void aic3204_write_reg_cache(struct snd_soc_codec *codec,
u16 reg, u8 value)
{
u8 *cache = codec->reg_cache;
if (reg >= AIC3204_CACHEREGNUM)
return;
cache[reg] = value;
}
/*
* write to the aic3204 register space
*/
static int aic3204_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
/* data is
* D15..D8 aic3204 register offset
* D7...D0 register data
*/
u8 data[2];
/*
* Register number; upper 8 bits indicate page
*/
if ( reg && (aic3204_read_reg_cache( codec, AIC3204_PAGE_SELECT )
!= ( reg >> 7 )) ) {
/* Select the required page */
data[0] = 0;
data[1] = reg >> 7;
if (codec->hw_write(codec->control_data, data, 2) != 2)
return -EIO;
aic3204_write_reg_cache(codec, AIC3204_PAGE_SELECT, reg >> 7);
dev_dbg( codec->dev, "%s: Page %d selected\n",
__func__, reg >> 7 );
}
data[0] = reg & 0x7f;
data[1] = value & 0xff;
if (codec->hw_write(codec->control_data, data, 2) != 2)
return -EIO;
dev_dbg( codec->dev, "%s: pg %d reg %d[%04x] <= %02x\n",
__func__, reg >> 7, reg & 0x7f, reg, value);
aic3204_write_reg_cache(codec, reg, value);
return 0;
}
/*
* read from the aic3204 register space
*/
static int aic3204_read(struct snd_soc_codec *codec, unsigned int reg,
u8 *value)
{
*value = reg & 0x7f;
/*
* Register number; upper 8 bits indicate page
*/
if ( reg && (aic3204_read_reg_cache( codec, AIC3204_PAGE_SELECT )
!= ( reg >> 7 )) )
/* Select the required page */
aic3204_write( codec, AIC3204_PAGE_SELECT, (reg >> 7) );
value[0] = i2c_smbus_read_byte_data(codec->control_data, value[0]);
#if 0
dev_dbg( &codec->dev, "%s: pg %d reg %d[%04x] => %02x\n",
__func__, reg >> 7, reg & 0xff, reg, value[0] );
#endif
aic3204_write_reg_cache(codec, reg, *value);
return 0;
}
/*
* Perform a read/modify/write cycle on a register.
*
* This is a shorthand function, it reads the specified register, masks out the
* bits in and_mask, applies bits in or_mask, then writes out the result to the
* register.
*
* It returns the modified value; or a negative error code.
*/
static inline int aic3204_mod( struct snd_soc_codec *codec, unsigned int reg,
u8 and_mask, u8 or_mask )
{
int result;
u8 value = aic3204_read_reg_cache( codec, reg );
value &= and_mask;
value |= or_mask;
result = aic3204_write( codec, reg, value );
if ( !result )
result = value;
return result;
}
static ssize_t aic3204_show_regsel(struct device *dev,
struct device_attribute *attr, char *buf) {
struct i2c_client *client = to_i2c_client(dev);
struct aic3204_priv *aic3204 = i2c_get_clientdata(client);
if ( aic3204 == NULL )
return snprintf(buf, PAGE_SIZE, "no codec privdata!\n");
return snprintf(buf, PAGE_SIZE, "0x%04x\n", aic3204->sysfs_reg );
}
static ssize_t aic3204_store_regsel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count) {
u16 reg = simple_strtoul( buf, NULL, 16 ) & 0xffff;
struct i2c_client *client = to_i2c_client(dev);
struct aic3204_priv *aic3204 = i2c_get_clientdata(client);
if ( aic3204 == NULL )
return 0;
aic3204->sysfs_reg = reg;
return strnlen(buf, PAGE_SIZE);
}
static ssize_t aic3204_show_regdata(struct device *dev,
struct device_attribute *attr, char *buf) {
u8 value;
struct i2c_client *client = to_i2c_client(dev);
struct aic3204_priv *aic3204 = i2c_get_clientdata(client);
if ( aic3204 == NULL )
return snprintf(buf, PAGE_SIZE, "no codec privdata!\n");
/* Read the register */
aic3204_read( &aic3204->codec, aic3204->sysfs_reg, &value);
return snprintf(buf, PAGE_SIZE, "0x%02x\n", value );
}
static ssize_t aic3204_store_regdata(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count) {
u8 value = simple_strtoul( buf, NULL, 16 ) & 0xff;
struct i2c_client *client = to_i2c_client(dev);
struct aic3204_priv *aic3204 = i2c_get_clientdata(client);
if ( aic3204 == NULL )
return 0;
/* Write the register */
aic3204_write( &aic3204->codec, aic3204->sysfs_reg, value);
return strnlen(buf, PAGE_SIZE);
}
/*
* For MICPGA inputs; inputs may either be disconnected, or connected via one
* of three resistances.
*/
static const char *aic3204_micpga_input_mux[] = {
"not-connected", "10k", "20k", "40k"
};
static struct soc_enum aic3204_enum_in1l_lmicpgap =
SOC_ENUM_SINGLE(AIC3204_LMICPGAPROUTE,
6, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in2l_lmicpgap =
SOC_ENUM_SINGLE(AIC3204_LMICPGAPROUTE,
4, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in3l_lmicpgap =
SOC_ENUM_SINGLE(AIC3204_LMICPGAPROUTE,
2, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in1r_lmicpgap =
SOC_ENUM_SINGLE(AIC3204_LMICPGAPROUTE,
0, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_cm1l_lmicpgan =
SOC_ENUM_SINGLE(AIC3204_LMICPGANROUTE,
6, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in2r_lmicpgan =
SOC_ENUM_SINGLE(AIC3204_LMICPGANROUTE,
4, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in3r_lmicpgan =
SOC_ENUM_SINGLE(AIC3204_LMICPGANROUTE,
2, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_cm2l_lmicpgan =
SOC_ENUM_SINGLE(AIC3204_LMICPGANROUTE,
0, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in1r_rmicpgap =
SOC_ENUM_SINGLE(AIC3204_RMICPGAPROUTE,
6, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in2r_rmicpgap =
SOC_ENUM_SINGLE(AIC3204_RMICPGAPROUTE,
4, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in3r_rmicpgap =
SOC_ENUM_SINGLE(AIC3204_RMICPGAPROUTE,
2, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in2l_rmicpgap =
SOC_ENUM_SINGLE(AIC3204_RMICPGAPROUTE,
0, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_cm1r_rmicpgan =
SOC_ENUM_SINGLE(AIC3204_RMICPGANROUTE,
6, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in1l_rmicpgan =
SOC_ENUM_SINGLE(AIC3204_RMICPGANROUTE,
4, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_in3l_rmicpgan =
SOC_ENUM_SINGLE(AIC3204_RMICPGANROUTE,
2, 4, aic3204_micpga_input_mux);
static struct soc_enum aic3204_enum_cm2r_rmicpgan =
SOC_ENUM_SINGLE(AIC3204_RMICPGANROUTE,
0, 4, aic3204_micpga_input_mux);
/*
* MICBIAS Voltages; The exact voltage depends on the common mode voltage
* setting...
*
* CM=0.75V CM=0.9V
* Low 1.04V 1.25V
* Medium 1.425V 1.7V
* High 2.075V 2.5V
* V+ Connects to power supply directly in both cases.
*/
static const char *aic3204_micbias_voltages[] = {
"Low", "Med", "High", "V+"
};
static struct soc_enum aic3204_enum_micbias_voltage =
SOC_ENUM_SINGLE(AIC3204_MICBIAS,
4, 4, aic3204_micbias_voltages);
/*
* MICBIAS Voltage Source
*/
static const char *aic3204_micbias_srcs[] = {
"AVDD", "LDOIN"
};
static struct soc_enum aic3204_enum_micbias_src =
SOC_ENUM_SINGLE(AIC3204_MICBIAS,
3, 2, aic3204_micbias_srcs);
/*
* DAC digital volumes. From -63.5 to +24 dB in 0.5 dB steps.
* Does not mute control.
*/
static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0);
/*
* ADC gain volumes. From -12dB to 20 dB in 0.5dB steps.
* Does not mute control.
*/
static DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 50, 0);
#if 0
/*
* ADC Fine gain volumes. From -0.4 to 0 dB in 0.1dB steps
* Does not mute control.
*/
static DECLARE_TLV_DB_SCALE(adc_fine_tlv, -40, 10, 0);
#endif
/*
* Output driver stage volumes; -6dB through to +29dB in 1dB steps
* Does not mute control.
*/
static DECLARE_TLV_DB_SCALE(output_stage_tlv, -600, 100, 0);
/*
* MICPGA volumes; 0dB through to +47.5dB in 0.5dB steps.
* Does not mute control
*/
static DECLARE_TLV_DB_SCALE(micpga_tlv, 0, 50, 0);
static const struct snd_kcontrol_new aic3204_snd_controls[] = {
/* Output */
/*
* Note regarding SOC_DOUBLE_R_SX_TLV...
*
* This is a bit misleading; xshift refers to the bit one bit *past*
* the most significant bit. Or at least that's what it appears to be
* doing the math. Mask needs to select the last 6 bits; which is the
* signed bitfield specifiying the gain in dB.
*/
SOC_DOUBLE_R_SX_TLV("Headphone Output Volume",
AIC3204_HPLGAIN, AIC3204_HPRGAIN,
6, -6, 29, output_stage_tlv),
SOC_DOUBLE_R("Headphone Output Playback Switch",
AIC3204_HPLGAIN, AIC3204_HPRGAIN,
AIC3204_HPLGAIN_MUTE_SHIFT, 0x01, 1),
SOC_DOUBLE_R_SX_TLV("Line Output Playback Volume",
AIC3204_LOLGAIN, AIC3204_LORGAIN,
6, -6, 29, output_stage_tlv),
SOC_DOUBLE_R("Line Output Playback Switch", AIC3204_LOLGAIN,
AIC3204_LORGAIN, AIC3204_LOLGAIN_MUTE_SHIFT, 0x01, 1),
SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
AIC3204_LDACVOL, AIC3204_RDACVOL,
8, -127, 48, dac_tlv),
SOC_DOUBLE("PCM Playback Switch", AIC3204_DACS2,
AIC3204_DACS2_LEFT_MUTE_SHIFT,
AIC3204_DACS2_RIGHT_MUTE_SHIFT, 0x01, 1),
SOC_DOUBLE_R_SX_TLV("Mixer Amplifier Volume",
AIC3204_MALGAIN, AIC3204_MARGAIN,
6, -6, 29, output_stage_tlv),
/* MICPGA Options */
SOC_DOUBLE_R_TLV("MICPGA Capture Volume",
AIC3204_LMICPGAVOL, AIC3204_RMICPGAVOL,
0, 95, 0, micpga_tlv),
SOC_DOUBLE_R("MICPGA Capture Switch",
AIC3204_LMICPGAVOL, AIC3204_RMICPGAVOL, 7, 0x1, 1 ),
/* MICBIAS Options */
SOC_SINGLE("MICBIAS Enable", AIC3204_MICBIAS, 6, 0x1, 0 ),
SOC_ENUM("MICBIAS Level", aic3204_enum_micbias_voltage ),
SOC_ENUM("MICBIAS Source", aic3204_enum_micbias_src ),
/* ADC */
SOC_DOUBLE_R_SX_TLV("PCM Capture Volume",
AIC3204_LADCVOL, AIC3204_RADCVOL,
7, -24, 40, adc_tlv),
SOC_DOUBLE("PCM Capture Switch", AIC3204_ADCFINE, 7, 3, 0x01, 1 ),
/* Driver/Amplifier Selection -- DAPM should handle this! */
SOC_DOUBLE("HP Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_HPL_UP_SHIFT,
AIC3204_OUTDRV_HPR_UP_SHIFT, 0x1, 0 ),
SOC_DOUBLE("LO Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_LOL_UP_SHIFT,
AIC3204_OUTDRV_LOR_UP_SHIFT, 0x1, 0 ),
SOC_DOUBLE("MA Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_MAL_UP_SHIFT,
AIC3204_OUTDRV_MAR_UP_SHIFT, 0x1, 0 ),
};
/* Driver/Amplifier Selection */
static const struct snd_kcontrol_new aic3204_hpl_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_HPL_UP_SHIFT, 0x1, 0 );
static const struct snd_kcontrol_new aic3204_hpr_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_HPR_UP_SHIFT, 0x1, 0 );
static const struct snd_kcontrol_new aic3204_lol_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_LOL_UP_SHIFT, 0x1, 0 );
static const struct snd_kcontrol_new aic3204_lor_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_LOR_UP_SHIFT, 0x1, 0 );
static const struct snd_kcontrol_new aic3204_mal_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_MAL_UP_SHIFT, 0x1, 0 );
static const struct snd_kcontrol_new aic3204_mar_switch =
SOC_DAPM_SINGLE("Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_MAR_UP_SHIFT, 0x1, 0 );
/* Line Output Left Driver Inputs */
static const struct snd_kcontrol_new aic3204_lol_in_controls[] = {
SOC_DAPM_SINGLE("LOR Switch", AIC3204_LOLROUTE,
AIC3204_LOLROUTE_LOR_SHIFT, 1, 0 ),
SOC_DAPM_SINGLE("MAL Switch", AIC3204_LOLROUTE,
AIC3204_LOLROUTE_MAL_SHIFT, 1, 0 ),
SOC_DAPM_SINGLE("LDAC+ Switch", AIC3204_LOLROUTE,
AIC3204_LOLROUTE_LDACP_SHIFT, 1, 0 ),
SOC_DAPM_SINGLE("RDAC- Switch", AIC3204_LOLROUTE,
AIC3204_LOLROUTE_RDACN_SHIFT, 1, 0 ),
};
/* Line Output Right Driver Inputs */
static const struct snd_kcontrol_new aic3204_lor_in_controls[] = {
SOC_DAPM_SINGLE("MAR Switch", AIC3204_LORROUTE,
AIC3204_LORROUTE_MAR_SHIFT, 1, 0 ),
SOC_DAPM_SINGLE("RDAC+ Switch", AIC3204_LORROUTE,
AIC3204_LORROUTE_RDACP_SHIFT, 1, 0 ),
};
/* Headphone Output Left Driver Inputs */
static const struct snd_kcontrol_new aic3204_hpl_in_controls[] = {
SOC_DAPM_SINGLE("MAR Switch", AIC3204_HPLROUTE,
AIC3204_HPLROUTE_MAR_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("MAL Switch", AIC3204_HPLROUTE,
AIC3204_HPLROUTE_MAL_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("IN1L Switch", AIC3204_HPLROUTE,
AIC3204_HPLROUTE_IN1L_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("LDAC+ Switch", AIC3204_HPLROUTE,
AIC3204_HPLROUTE_LDACP_SHIFT, 0x1, 0 ),
};
/* Headphone Output Right Driver Inputs */
static const struct snd_kcontrol_new aic3204_hpr_in_controls[] = {
SOC_DAPM_SINGLE("HPL Switch", AIC3204_HPRROUTE,
AIC3204_HPRROUTE_HPL_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("MAR Switch", AIC3204_HPRROUTE,
AIC3204_HPRROUTE_MAR_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("IN1R Switch", AIC3204_HPRROUTE,
AIC3204_HPRROUTE_IN1R_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("RDAC+ Switch", AIC3204_HPRROUTE,
AIC3204_HPRROUTE_RDACP_SHIFT, 0x1, 0 ),
SOC_DAPM_SINGLE("LDAC- Switch", AIC3204_HPRROUTE,
AIC3204_HPRROUTE_LDACN_SHIFT, 0x1, 0 ),
};
static const struct snd_kcontrol_new aic3204_lmicpgap_in_controls[] = {
SOC_DAPM_ENUM("IN1L Switch", aic3204_enum_in1l_lmicpgap ),
SOC_DAPM_ENUM("IN2L Switch", aic3204_enum_in2l_lmicpgap ),
SOC_DAPM_ENUM("IN3L Switch", aic3204_enum_in3l_lmicpgap ),
SOC_DAPM_ENUM("IN1R Switch", aic3204_enum_in1r_lmicpgap ),
};
static const struct snd_kcontrol_new aic3204_lmicpgan_in_controls[] = {
SOC_DAPM_ENUM("CM1L Switch", aic3204_enum_cm1l_lmicpgan ),
SOC_DAPM_ENUM("IN2R Switch", aic3204_enum_in2r_lmicpgan ),
SOC_DAPM_ENUM("IN3R Switch", aic3204_enum_in3r_lmicpgan ),
SOC_DAPM_ENUM("CM2L Switch", aic3204_enum_cm2l_lmicpgan ),
};
static const struct snd_kcontrol_new aic3204_rmicpgap_in_controls[] = {
SOC_DAPM_ENUM("IN1R Switch", aic3204_enum_in1r_rmicpgap ),
SOC_DAPM_ENUM("IN2R Switch", aic3204_enum_in2r_rmicpgap ),
SOC_DAPM_ENUM("IN3R Switch", aic3204_enum_in3r_rmicpgap ),
SOC_DAPM_ENUM("IN2L Switch", aic3204_enum_in2l_rmicpgap ),
};
static const struct snd_kcontrol_new aic3204_rmicpgan_in_controls[] = {
SOC_DAPM_ENUM("CM1R Switch", aic3204_enum_cm1r_rmicpgan ),
SOC_DAPM_ENUM("IN1L Switch", aic3204_enum_in1l_rmicpgan ),
SOC_DAPM_ENUM("IN3L Switch", aic3204_enum_in3l_rmicpgan ),
SOC_DAPM_ENUM("CM2R Switch", aic3204_enum_cm2r_rmicpgan ),
};
static const struct snd_soc_dapm_widget aic3204_dapm_widgets[] = {
/* Driver/Amplifier Selection */
SND_SOC_DAPM_SWITCH("HPL Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_HPL_UP_SHIFT, 0,
&aic3204_hpl_switch ),
SND_SOC_DAPM_SWITCH("HPR Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_HPR_UP_SHIFT, 0,
&aic3204_hpr_switch ),
SND_SOC_DAPM_SWITCH("LOL Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_LOL_UP_SHIFT, 0,
&aic3204_lol_switch ),
SND_SOC_DAPM_SWITCH("LOR Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_LOR_UP_SHIFT, 0,
&aic3204_lor_switch ),
SND_SOC_DAPM_SWITCH("MAL Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_MAL_UP_SHIFT, 0,
&aic3204_mal_switch ),
SND_SOC_DAPM_SWITCH("MAR Playback Switch", AIC3204_OUTDRV,
AIC3204_OUTDRV_MAR_UP_SHIFT, 0,
&aic3204_mar_switch ),
/* Line Output Driver Inputs */
SND_SOC_DAPM_MIXER("Left Line Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_lol_in_controls,
ARRAY_SIZE(aic3204_lol_in_controls)),
SND_SOC_DAPM_MIXER("Right Line Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_lor_in_controls,
ARRAY_SIZE(aic3204_lor_in_controls)),
/* Headphone Output Driver Inputs */
SND_SOC_DAPM_MIXER("Left Headphone Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_hpl_in_controls,
ARRAY_SIZE(aic3204_hpl_in_controls)),
SND_SOC_DAPM_MIXER("Right Headphone Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_hpr_in_controls,
ARRAY_SIZE(aic3204_hpr_in_controls)),
/* DACs, ADCs and audio interfaces */
SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC3204_DACS1,
AIC3204_DACS1_LDAC_UP_SHIFT, 0),
SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC3204_DACS1,
AIC3204_DACS1_RDAC_UP_SHIFT, 0),
SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC3204_ADCS,
AIC3204_ADCS_RADC_UP_SHIFT, 0),
SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC3204_ADCS,
AIC3204_ADCS_RADC_UP_SHIFT, 0),
SND_SOC_DAPM_AIF_IN("Left Data In", "Left Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN("Right Data In", "Right Playback", 1, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Left Data Out", "Left Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("Right Data Out", "Right Capture", 1, SND_SOC_NOPM, 0, 0),
/* MICPGA Input Sources */
SND_SOC_DAPM_MIXER("Left MICPGA+ Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_lmicpgap_in_controls,
ARRAY_SIZE(aic3204_lmicpgap_in_controls)),
SND_SOC_DAPM_MIXER("Left MICPGA- Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_lmicpgan_in_controls,
ARRAY_SIZE(aic3204_lmicpgan_in_controls)),
SND_SOC_DAPM_MIXER("Right MICPGA+ Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_rmicpgap_in_controls,
ARRAY_SIZE(aic3204_rmicpgap_in_controls)),
SND_SOC_DAPM_MIXER("Right MICPGA- Output Mixer", SND_SOC_NOPM, 0, 0,
aic3204_rmicpgan_in_controls,
ARRAY_SIZE(aic3204_rmicpgan_in_controls)),
SND_SOC_DAPM_PGA("Left MICPGA", SND_SOC_NOPM, 0, 0, NULL, 0 ),
SND_SOC_DAPM_PGA("Right MICPGA", SND_SOC_NOPM, 0, 0, NULL, 0 ),
SND_SOC_DAPM_MICBIAS("Mic Bias", AIC3204_MICBIAS, 6, 0),
/* Bypass PGAs */
SND_SOC_DAPM_PGA("IN1L Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0 ),
SND_SOC_DAPM_PGA("IN1R Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0 ),
/* Input Pins */
SND_SOC_DAPM_INPUT("IN1L"),
SND_SOC_DAPM_INPUT("IN1R"),
SND_SOC_DAPM_INPUT("IN2L"),
SND_SOC_DAPM_INPUT("IN2R"),
SND_SOC_DAPM_INPUT("IN3L"),
SND_SOC_DAPM_INPUT("IN3R"),
/* Output Pins */
SND_SOC_DAPM_OUTPUT("LOL"),
SND_SOC_DAPM_OUTPUT("LOR"),
SND_SOC_DAPM_OUTPUT("HPL"),
SND_SOC_DAPM_OUTPUT("HPR"),
/* Common-mode pins */
SND_SOC_DAPM_VMID("CM1L"),
SND_SOC_DAPM_VMID("CM1R"),
SND_SOC_DAPM_VMID("CM2L"),
SND_SOC_DAPM_VMID("CM2R"),
};
static const struct snd_soc_dapm_route intercon[] = {
/* Data Connections */
{"Left Data Out", NULL, "Left ADC"},
{"Right Data Out", NULL, "Right ADC"},
{"Left DAC", NULL, "Left Data In"},
{"Right DAC", NULL, "Right Data In"},
/* Line Output */
{"Left Line Output Mixer", "LOR Switch", "Right Line Output Mixer"},
{"Left Line Output Mixer", "MAL Switch", "Left MICPGA"},
{"Left Line Output Mixer", "LDAC+ Switch", "Left DAC"},
{"Left Line Output Mixer", "RDAC- Switch", "Right DAC"},
{"Right Line Output Mixer", "MAR Switch", "Right MICPGA"},
{"Right Line Output Mixer", "RDAC+ Switch", "Right DAC"},
{"LOL", "LOL Playback Switch", "Left Line Output Mixer"},
{"LOR", "LOR Playback Switch", "Right Line Output Mixer"},
/* Headphone Output */
{"Left Headphone Output Mixer", "MAR Switch", "Right MICPGA"},
{"Left Headphone Output Mixer", "MAL Switch", "Left MICPGA"},
{"Left Headphone Output Mixer", "IN1L Switch", "IN1L Bypass PGA"},
{"Left Headphone Output Mixer", "LDAC+ Switch", "Left DAC"},
{"Right Headphone Output Mixer", "HPL Switch", "Left Headphone Output Mixer"},
{"Right Headphone Output Mixer", "MAR Switch", "Right MICPGA"},
{"Right Headphone Output Mixer", "IN1R Switch", "IN1R Bypass PGA"},
{"Right Headphone Output Mixer", "RDAC+ Switch", "Right DAC"},
{"Right Headphone Output Mixer", "LDAC- Switch", "Left DAC"},
{"HPL", "HPL Playback Switch", "Left Headphone Output Mixer"},
{"HPR", "HPR Playback Switch", "Right Headphone Output Mixer"},
/* MICPGA Input */
{"Left MICPGA+ Output Mixer", "IN1L Switch", "IN1L"},
{"Left MICPGA+ Output Mixer", "IN2L Switch", "IN2L"},
{"Left MICPGA+ Output Mixer", "IN3L Switch", "IN3L"},
{"Left MICPGA+ Output Mixer", "IN1R Switch", "IN1R"},
{"Left MICPGA- Output Mixer", "CM1L Switch", "CM1L"},
{"Left MICPGA- Output Mixer", "IN2R Switch", "IN2R"},
{"Left MICPGA- Output Mixer", "IN3R Switch", "IN3R"},
{"Left MICPGA- Output Mixer", "CM2L Switch", "CM2L"},
{"Right MICPGA+ Output Mixer", "IN1R Switch", "IN1R"},
{"Right MICPGA+ Output Mixer", "IN2R Switch", "IN2R"},
{"Right MICPGA+ Output Mixer", "IN3R Switch", "IN3R"},
{"Right MICPGA+ Output Mixer", "IN2L Switch", "IN2L"},
{"Right MICPGA- Output Mixer", "CM1R Switch", "CM1R"},
{"Right MICPGA- Output Mixer", "IN1L Switch", "IN1L"},
{"Right MICPGA- Output Mixer", "IN3L Switch", "IN3L"},
{"Right MICPGA- Output Mixer", "CM2R Switch", "CM2R"},
{"Left MICPGA", "MAL Playback Switch", "Left MICPGA+ Output Mixer"},
{"Right MICPGA", "MAR Playback Switch", "Right MICPGA+ Output Mixer"},
{"Left MICPGA", "MAL Playback Switch", "Left MICPGA- Output Mixer"},
{"Right MICPGA", "MAR Playback Switch", "Right MICPGA- Output Mixer"},
{"Left ADC", NULL, "Left MICPGA"},
{"Right ADC", NULL, "Right MICPGA"},
};
static int aic3204_add_widgets(struct snd_soc_codec *codec)
{
snd_soc_dapm_new_controls(codec, aic3204_dapm_widgets,
ARRAY_SIZE(aic3204_dapm_widgets));
/* set up audio path interconnects */
snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
return 0;
}
static int aic3204_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->card->codec;
struct aic3204_priv *aic3204 = snd_soc_codec_get_drvdata(codec);
int dac_clk = 0, bit_clk = 0;
int codec_clk = 0, bypass_pll = 0, last_clk = 0, last_error = 0;
u8 data = 0, ndac = 0, mdac = 0, pll_p = 1, pll_r = 1, pll_j = 1, bdiv = 1;
u16 pll_d = 0;
int this_clk = 0, this_error = 0;
unsigned int this_abs_error;
u8 j, this_ndac;
u16 d;
/* Determine the DAC clock and bit clock needed */
dac_clk = AIC3204_OSR * params_rate(params);
bit_clk = params_rate(params) * params_channels(params);
/*
* Select data word length.
* Note that the number of bits per frame used
* to determine BDIV is double the number of bits per sample, as there
* are two samples per frame.
*/
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
pr_debug("%s: Sample format 16 bits\n", __func__);
bit_clk *= 16;
data = AIC3204_AISR1_WL_16BITS;
break;
case SNDRV_PCM_FORMAT_S20_3LE:
pr_debug("%s: Sample format 20 bits\n", __func__);
bit_clk *= 32;
data = AIC3204_AISR1_WL_20BITS;
break;
case SNDRV_PCM_FORMAT_S24_LE:
pr_debug("%s: Sample format 24 bits\n", __func__);
bit_clk *= 40;
data = AIC3204_AISR1_WL_24BITS;
break;
case SNDRV_PCM_FORMAT_S32_LE:
pr_debug("%s: Sample format 32 bits\n", __func__);
bit_clk *= 48;
data = AIC3204_AISR1_WL_32BITS;
break;
}
aic3204_mod(codec, AIC3204_AISR1, ~AIC3204_AISR1_WL, data);
pr_debug("%s: DAC Clock = %d Hz, Bit clock = %d Hz\n", __func__,
dac_clk, bit_clk );
#ifndef ENABLE_PLL
/* If PLL is disabled; always bypass the PLL */
bypass_pll = 1;
#endif
/* Try to find a value for div which allows us to bypass the PLL and
* generate CODEC_CLK directly. */
for (this_ndac = 128; this_ndac >= 1; this_ndac--) {
this_clk = aic3204->sysclk / this_ndac;
if (this_clk == dac_clk) {
pr_debug("%s: Scored exact match. NDAC=%d; DAC_CLK=%d\n",
__func__, this_ndac, this_clk );
bypass_pll = 1;
ndac = this_ndac;
break;
#ifndef ENABLE_PLL
} else {
this_error = this_clk - dac_clk;
this_abs_error = abs(this_error);
pr_debug("%s: NDAC=%d; DAC_CLK=%d Error=%d\n",
__func__, this_ndac,
this_clk, this_error );
if ( !last_clk || (this_abs_error < last_error) ) {
last_clk = this_clk;
last_error = this_abs_error;
ndac = this_ndac;
}
#endif
}
}
#ifdef ENABLE_PLL
if (bypass_pll) {
#endif
/* Select MCLK as CODEC_CLKIN */
aic3204_write(codec, AIC3204_CLK1, AIC3204_CLK1_CODECCLK_MCLK );
codec_clk = aic3204->sysclk;
#ifdef ENABLE_PLL
goto setup_codec;
}
/*
* Determine PLL Parameters... the input/output frequency ratio
* of the PLL is given in the datasheet:
*
* out R * J * (D/10000) J D
* --- = ----------------- = R * - * -------
* in P P 10000
*
* Limitations:
* - R range: 1..4
* - J range: 4..63
* - D range: 0..9999
* - P range: 1..8
* - If D == 0;
* 512 kHz <= in / P <= 20 MHz
* else
* 10 MHz <= in / P <= 20 MHz
*
* We will try for 8* the target frequency; then use NDAC/NADC to divide
* that down. If we set P = 1, and R = 1; we simplify the solution.
*/
pll_p = 1;
pll_r = 1;
for ( this_ndac = 1; this_ndac <= 128; this_ndac++ ) {
int ratio, init_ratio;
codec_clk = dac_clk * this_ndac;
/* Ensure maximum CODEC frequency is not exceeded */
if ( codec_clk > 137000000 ) continue;
if ( ( this_ndac % 2 ) && ( codec_clk > 110000000 ) ) continue;
/* Initial Fixed-point ratio ... this will be slightly lower */
init_ratio = codec_clk / ( aic3204->sysclk / 10000 );
pr_debug("%s: NDAC = %d, PLL J.D * 10000 = %d\n",
__func__, this_ndac, init_ratio );
if ( init_ratio < 40000 ) continue;
if ( init_ratio > 639999 ) continue;
for( ratio = init_ratio; (ratio < init_ratio + 10000)
&& (ratio < 639999); ratio++ ) {
j = ratio / 10000;
d = ratio % 10000;
this_clk = aic3204->sysclk / 10000;
this_clk *= (10000 * j) + d;
this_error = this_clk - codec_clk;
this_abs_error = abs(this_error);
if ( this_abs_error > 1000 ) continue;
pr_debug("%s: Fractional PLL search; "
"P=%d R=%d J=%d D=%04d => "
"(%d * %d) / 10000 => %d "
"(%d error) [NDAC=%d]\n",
__func__, pll_p, pll_r, j, d, aic3204->sysclk,
ratio, this_clk, this_error, this_ndac);
if ( !last_clk || (this_abs_error < last_error) ) {
last_clk = this_clk;
last_error = this_abs_error;
ndac = this_ndac;
pll_j = j;
pll_d = d;
/* Early finish for exact match */
if ( ! this_abs_error )
break;
}
}
}
/* If we still haven't found anything; bail out here! */
if ( !last_clk ) goto fail;
pr_debug("%s: Best Output: %d (%d Hz Error)\n",
__func__, last_clk, last_error );
pr_debug( "%s: setting up PLL: P=%d R=%d J.D=%d.%04d\n",
__func__, pll_p, pll_r, pll_j, pll_d );
codec_clk = last_clk;
aic3204_write(codec, AIC3204_CLK4, (pll_d >> AIC3204_CLK4_PLL_D_VSHIFT)
& AIC3204_CLK4_PLL_D); /* PLL D[14:8] */
aic3204_write(codec, AIC3204_CLK5, pll_d & AIC3204_CLK5_PLL_D);
/* PLL D[7:0] */
aic3204_write(codec, AIC3204_CLK3, pll_j & AIC3204_CLK3_PLL_J);
/* PLL J */
aic3204_write(codec, AIC3204_CLK2,
( pll_r & AIC3204_CLK2_PLL_R ) /* PLL R */
| ( ( pll_p << AIC3204_CLK2_PLL_P_SHIFT )
& AIC3204_CLK2_PLL_P )
/* PLL P */
| AIC3204_CLK2_PLL_ON ); /* PLL on */
/* Select PLL as CODEC_CLKIN */
aic3204_write(codec, AIC3204_CLK1, AIC3204_CLK1_CODECCLK_PLL );
setup_codec:
#endif
pr_debug("%s: CODEC CLK = %d => DAC CLK = %d\n",
__func__, codec_clk, codec_clk / ndac );
/* Calculate BDIV and NDAC */
mdac = codec_clk / (ndac * dac_clk);
if ( !mdac ) mdac = 1;
bdiv = dac_clk / bit_clk;
if ( !bdiv ) bdiv = 1;
/* Set AOSR and DOSR */
aic3204_write(codec, AIC3204_DOSR1,
(AIC3204_OSR >> AIC3204_DOSR1_MSB_VSHIFT)
& AIC3204_DOSR1_MSB ); /* DOSR[9:8] */
aic3204_write(codec, AIC3204_DOSR2, AIC3204_OSR
& AIC3204_DOSR2_LSB ); /* DOSR[7:0] */
aic3204_write(codec, AIC3204_AOSR, AIC3204_OSR); /* AOSR */
pr_debug("%s: NDAC = %d\n", __func__, ndac);
/* Set NDAC to divider value */
aic3204_write(codec, AIC3204_CLK6, AIC3204_CLK6_NDAC_STATE_ON | ndac );
/* Set NADC to match NDAC */
aic3204_write(codec, AIC3204_CLK8, AIC3204_CLK8_NADC_STATE_ON | ndac );
/* Set MDAC and turn on */
aic3204_write(codec, AIC3204_CLK7, AIC3204_CLK7_MDAC_STATE_ON | mdac );
/* Use MDAC for MADC */
aic3204_write(codec, AIC3204_CLK9, AIC3204_CLK7_MDAC_STATE_ON | mdac );
/* Set BCLK and WCLK */
pr_debug("%s: Bit clock divider = %d\n", __func__, bdiv);
/* Turn on and set BCLK N divider */
aic3204_write(codec, AIC3204_CLK12, bdiv
| AIC3204_CLK12_BCLK_STATE_ON );
/* Set BCLK and WCLK sources */
aic3204_mod(codec, AIC3204_AISR3, ~AIC3204_AISR3_BDIV,
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
? AIC3204_AISR3_BDIV_DAC
: AIC3204_AISR3_BDIV_ADC );
aic3204_mod(codec, AIC3204_AISR6, ~AIC3204_AISR6_WCLKOUT,
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
? AIC3204_AISR6_WCLKOUT_DAC
: AIC3204_AISR6_WCLKOUT_ADC );
return 0;
fail:
printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
return -EINVAL;
}
static int aic3204_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u8 dacs2;
dacs2 = aic3204_read_reg_cache(codec, AIC3204_DACS2);
if (mute) {
dacs2 |= (1 << AIC3204_DACS2_LEFT_MUTE_SHIFT)
| (1 << AIC3204_DACS2_RIGHT_MUTE_SHIFT);
} else {
dacs2 &= ~((1 << AIC3204_DACS2_LEFT_MUTE_SHIFT)
| (1 << AIC3204_DACS2_RIGHT_MUTE_SHIFT));
}
aic3204_write(codec, AIC3204_DACS2, dacs2 ); /* Unmute DAC */
return 0;
}
static int aic3204_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct aic3204_priv *aic3204 = snd_soc_codec_get_drvdata(codec);
aic3204->sysclk = freq;
return 0;
}
static int aic3204_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
u8 aisr1 = 0, aisr2 = 0, aisr3 = 0;
/* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
aisr1 |= AIC3204_AISR1_BCLK_OUT
| AIC3204_AISR1_WCLK_OUT;
break;
case SND_SOC_DAIFMT_CBM_CFS:
aisr1 |= AIC3204_AISR1_BCLK_OUT
| AIC3204_AISR1_WCLK_IN;
break;
case SND_SOC_DAIFMT_CBS_CFM:
aisr1 |= AIC3204_AISR1_BCLK_IN
| AIC3204_AISR1_WCLK_OUT;
break;
case SND_SOC_DAIFMT_CBS_CFS:
aisr1 |= AIC3204_AISR1_BCLK_IN
| AIC3204_AISR1_WCLK_IN;
break;
default:
printk( KERN_ERR "%s: Clock mode not supported "
"(fmt=0x%08x)\n", __func__, fmt );
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
aisr1 |= AIC3204_AISR1_INT_I2S;
break;
case SND_SOC_DAIFMT_DSP_A:
aisr2 = 1;
case SND_SOC_DAIFMT_DSP_B:
aisr1 |= AIC3204_AISR1_INT_DSP;
break;
case SND_SOC_DAIFMT_RIGHT_J:
aisr1 |= AIC3204_AISR1_INT_RJF;
break;
case SND_SOC_DAIFMT_LEFT_J:
aisr1 |= AIC3204_AISR1_INT_LJF;
break;
default:
printk( KERN_ERR "%s: SSI mode not supported "
"(fmt=0x%08x)\n", __func__, fmt );
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
aisr3 |= AIC3204_AISR3_BCLKPOL_NOR;
break;
case SND_SOC_DAIFMT_IB_NF:
aisr3 |= AIC3204_AISR3_BCLKPOL_INV;
break;
default:
printk( KERN_ERR "%s: Clock inversion mode not "
"supported (fmt=0x%08x)\n", __func__, fmt );
return -EINVAL;
}
/* set iface */
aic3204_write(codec, AIC3204_AISR1, aisr1);
aic3204_write(codec, AIC3204_AISR2, aisr2);
aic3204_write(codec, AIC3204_AISR3, aisr3);
return 0;
}
static int aic3204_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
#if 0
switch (level) {
case SND_SOC_BIAS_ON:
case SND_SOC_BIAS_PREPARE:
printk("%s: Powering up ADC/DAC\n", __func__);
/* Turn on ADC and DAC */
aic3204_write(codec, AIC3204_DACS1,
AIC3204_DACS1_LDAC_UP |
AIC3204_DACS1_RDAC_UP |
AIC3204_DACS1_LDACD_LEFT |
AIC3204_DACS1_RDACD_RIGHT |
AIC3204_DACS1_SOFT_DIS );
/* TODO; put these registers into the header */
aic3204_write(codec, AIC3204_PGREG(0, 81), 0xc0 );
break;
case SND_SOC_BIAS_STANDBY:
case SND_SOC_BIAS_OFF:
printk("%s: Powering down ADC/DAC\n", __func__);
/* Turn off ADC and DAC */
aic3204_write(codec, AIC3204_DACS1, 0 );
/* TODO; put these registers into the header */
aic3204_write(codec, AIC3204_PGREG(0, 81), 0 );
break;
}
#endif
codec->bias_level = level;
return 0;
}
#define AIC3204_RATES SNDRV_PCM_RATE_8000_96000
#define AIC3204_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_ops aic3204_dai_ops = {
.hw_params = aic3204_hw_params,
.digital_mute = aic3204_mute,
.set_sysclk = aic3204_set_dai_sysclk,
.set_fmt = aic3204_set_dai_fmt,
};
struct snd_soc_dai aic3204_dai = {
.name = "tlv320aic3204",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3204_RATES,
.formats = AIC3204_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3204_RATES,
.formats = AIC3204_FORMATS,},
.ops = &aic3204_dai_ops,
};
EXPORT_SYMBOL_GPL(aic3204_dai);
static int aic3204_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->card->codec;
aic3204_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
static int aic3204_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->card->codec;
int i;
u8 data[2];
u8 *cache = codec->reg_cache;
/* Sync reg_cache with the hardware */
for (i = 0; i < AIC3204_CACHEREGNUM; i++) {
data[0] = i;
data[1] = cache[i];
codec->hw_write(codec->control_data, data, 2);
}
aic3204_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
}
/*
* initialise the AIC3204 driver
* register the mixer and dsp interfaces with the kernel
*/
static int aic3204_init(struct snd_soc_codec *codec)
{
int reg;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
codec->name = "tlv320aic3204";
codec->owner = THIS_MODULE;
codec->read = aic3204_read_reg_cache;
codec->write = aic3204_write;
codec->set_bias_level = aic3204_set_bias_level;
codec->dai = &aic3204_dai;
codec->num_dai = 1;
codec->reg_cache_size = AIC3204_CACHEREGNUM;
codec->reg_cache = kmalloc(AIC3204_CACHEREGNUM, GFP_KERNEL);
if (codec->reg_cache == NULL)
return -ENOMEM;
/* Reset the CODEC */
aic3204_write(codec, AIC3204_RESET, AIC3204_RESET_SOFT);
/* Read in the cache */
for( reg=0; reg < AIC3204_CACHEREGNUM; reg++ ) {
u8 temp;
aic3204_read( codec, reg, &temp );
}
return 0;
}
static struct snd_soc_codec *aic3204_codec;
static int aic3204_register(struct snd_soc_codec *codec)
{
int ret;
ret = aic3204_init(codec);
if (ret < 0) {
dev_err(codec->dev, "Failed to initialise device\n");
return ret;
}
aic3204_codec = codec;
ret = snd_soc_register_codec(codec);
if (ret) {
dev_err(codec->dev, "Failed to register codec\n");
return ret;
}
ret = snd_soc_register_dai(&aic3204_dai);
if (ret) {
dev_err(codec->dev, "Failed to register dai\n");
snd_soc_unregister_codec(codec);
return ret;
}
return 0;
}
static int aic3204_unregister(struct aic3204_priv *aic3204)
{
aic3204_set_bias_level(&aic3204->codec, SND_SOC_BIAS_OFF);
snd_soc_unregister_dai(&aic3204_dai);
snd_soc_unregister_codec(&aic3204->codec);
if (aic3204->gpio_reset >= 0) {
gpio_set_value(aic3204->gpio_reset, 0);
gpio_free(aic3204->gpio_reset);
}
regulator_bulk_disable(ARRAY_SIZE(aic3204->supplies), aic3204->supplies);
regulator_bulk_free(ARRAY_SIZE(aic3204->supplies), aic3204->supplies);
kfree(aic3204);
aic3204_codec = NULL;
return 0;
}
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
/*
* If the i2c layer weren't so broken, we could pass this kind of data
* around
*/
static int aic3204_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct snd_soc_codec *codec;
struct aic3204_priv *aic3204;
struct aic3204_pdata *pdata = i2c->dev.platform_data;
int ret, i;
aic3204 = kzalloc(sizeof(struct aic3204_priv), GFP_KERNEL);
if (aic3204 == NULL) {
dev_err(&i2c->dev, "failed to create private data\n");
return -ENOMEM;
}
codec = &aic3204->codec;
codec->dev = &i2c->dev;
snd_soc_codec_set_drvdata(codec, aic3204);
codec->control_data = i2c;
codec->hw_write = (hw_write_t) i2c_master_send;
i2c_set_clientdata(i2c, aic3204);
aic3204->gpio_reset = -1;
if (pdata && pdata->gpio_reset >= 0) {
ret = gpio_request(pdata->gpio_reset, "tlv320aic3204 reset");
if (ret != 0)
goto err_gpio;
aic3204->gpio_reset = pdata->gpio_reset;
gpio_direction_output(aic3204->gpio_reset, 0);
}
/* Initialise the channel configuration ; enable all channels */
aic3204->channels = AIC3204_LDAC_EN | AIC3204_RDAC_EN
| AIC3204_LADC_EN | AIC3204_RADC_EN;
for (i = 0; i < ARRAY_SIZE(aic3204->supplies); i++)
aic3204->supplies[i].supply = aic3204_supply_names[i];
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3204->supplies),
aic3204->supplies);
if (ret != 0) {
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
goto err_get;
}
ret = regulator_bulk_enable(ARRAY_SIZE(aic3204->supplies),
aic3204->supplies);
if (ret != 0) {
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
goto err_enable;
}
if (aic3204->gpio_reset >= 0) {
udelay(1);
gpio_set_value(aic3204->gpio_reset, 1);
}
if ( device_create_file( &i2c->dev, &dev_attr_regsel ) )
dev_dbg( &i2c->dev, "%s: failed to register sysfs"
"file for I2C register selection\n",
__func__);
if ( device_create_file( &i2c->dev, &dev_attr_regdata ) )
dev_dbg( &i2c->dev, "%s: failed to register sysfs"
"file for I2C register data\n",
__func__);
return aic3204_register(codec);
err_enable:
regulator_bulk_free(ARRAY_SIZE(aic3204->supplies), aic3204->supplies);
err_get:
if (aic3204->gpio_reset >= 0)
gpio_free(aic3204->gpio_reset);
err_gpio:
kfree(aic3204);
return ret;
}
static int aic3204_i2c_remove(struct i2c_client *client)
{
struct aic3204_priv *aic3204 = i2c_get_clientdata(client);
return aic3204_unregister(aic3204);
}
static const struct i2c_device_id aic3204_i2c_id[] = {
{ "tlv320aic3204", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, aic3204_i2c_id);
/* machine i2c codec control layer */
static struct i2c_driver aic3204_i2c_driver = {
.driver = {
.name = "tlv320aic3204-i2c",
.owner = THIS_MODULE,
},
.probe = aic3204_i2c_probe,
.remove = aic3204_i2c_remove,
.id_table = aic3204_i2c_id,
};
static inline void aic3204_i2c_init(void)
{
int ret;
ret = i2c_add_driver(&aic3204_i2c_driver);
if (ret)
printk(KERN_ERR "%s: error regsitering i2c driver, %d\n",
__func__, ret);
}
static inline void aic3204_i2c_exit(void)
{
i2c_del_driver(&aic3204_i2c_driver);
}
#else
static inline void aic3204_i2c_init(void) { }
static inline void aic3204_i2c_exit(void) { }
#endif
static int aic3204_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct aic3204_setup_data *setup;
struct snd_soc_codec *codec;
int ret = 0;
codec = aic3204_codec;
if (!codec) {
dev_err(&pdev->dev, "Codec not registered\n");
return -ENODEV;
}
socdev->card->codec = codec;
setup = socdev->codec_data;
if (setup) {
struct aic3204_priv *aic3204 = snd_soc_codec_get_drvdata(codec);
/* Run setup script; if any */
if ( setup->init_reg_list ) {
int length = setup->init_reg_length;
const struct aic3204_init_reg *init_reg =
setup->init_reg_list;
while( length-- ) {
int ret = 0;
u8 regval = aic3204_read_reg_cache( codec, init_reg->reg );
switch ( init_reg->flags &
AIC3204_INITFLAG_OPMASK ) {
case AIC3204_INITFLAG_SET:
regval = init_reg->value;
break;
case AIC3204_INITFLAG_AND:
regval &= init_reg->value;
break;
case AIC3204_INITFLAG_OR:
regval |= init_reg->value;
break;
case AIC3204_INITFLAG_XOR:
regval ^= init_reg->value;
break;
case AIC3204_INITFLAG_FAILNEQ:
if ( regval != init_reg->value )
ret = -EIO;
else
ret = 1;
break;
case AIC3204_INITFLAG_FAILEQ:
if ( regval == init_reg->value )
ret = -EIO;
else
ret = 1;
break;
case AIC3204_INITFLAG_FAILNSET:
if ( !( regval & init_reg->value ) )
ret = -EIO;
else
ret = 1;
break;
case AIC3204_INITFLAG_FAILSET:
if ( regval & init_reg->value )
ret = -EIO;
else
ret = 1;
break;
case AIC3204_INITFLAG_FAILNCLR:
if ( ( regval | init_reg->value ) != 0xff )
ret = -EIO;
else
ret = 1;
break;
case AIC3204_INITFLAG_FAILCLR:
if ( ( regval & init_reg->value ) == 0xff )
ret = -EIO;
else
ret = 1;
break;
default:
printk( KERN_ERR "%s: Instruction "
"not understood\n", __func__ );
ret = -EINVAL;
}
if ( !ret )
ret = aic3204_write(codec,
init_reg->reg,
regval );
if ( ret < 0 ) {
printk( KERN_ERR "%s: Initialisation operation "
"reg=0x%04x flags=0x%02x value=0x%02x "
"returns %d\n", __func__,
init_reg->reg, init_reg->flags,
init_reg->value, ret );
return ret;
}
/* Move to next instruction */
init_reg++;
}
}
/* Copy over channel information */
aic3204->channels = setup->channels;
}
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "aic3204: failed to create pcms\n");
goto pcm_err;
}
snd_soc_add_controls(codec, aic3204_snd_controls,
ARRAY_SIZE(aic3204_snd_controls));
aic3204_add_widgets(codec);
return ret;
pcm_err:
kfree(codec->reg_cache);
return ret;
}
static int aic3204_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->card->codec;
/* power down chip */
if (codec->control_data)
aic3204_set_bias_level(codec, SND_SOC_BIAS_OFF);
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
kfree(codec->reg_cache);
return 0;
}
struct snd_soc_codec_device soc_codec_dev_aic3204 = {
.probe = aic3204_probe,
.remove = aic3204_remove,
.suspend = aic3204_suspend,
.resume = aic3204_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_aic3204);
static int __init aic3204_modinit(void)
{
aic3204_i2c_init();
return 0;
}
module_init(aic3204_modinit);
static void __exit aic3204_exit(void)
{
aic3204_i2c_exit();
}
module_exit(aic3204_exit);
MODULE_DESCRIPTION("ASoC TLV320AIC3204 codec driver");
MODULE_AUTHOR("Jacques Electronics");
MODULE_LICENSE("GPL");
[-- Attachment #3: Type: text/plain, Size: 160 bytes --]
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 2/3]
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
@ 2010-06-15 0:33 ` Stuart Longland
2010-06-15 0:34 ` Update on TLV320AIC3204 Driver [File 3/3] Stuart Longland
2010-06-15 14:20 ` Update on TLV320AIC3204 Driver [File 1/3] Mark Brown
2 siblings, 0 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 0:33 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
[-- Attachment #1: Type: text/plain, Size: 821 bytes --]
On Fri, Jun 11, 2010 at 11:18:04AM +0100, Mark Brown wrote:
> On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
> > The driver is accessible online at
> > <http://www.longlandclan.yi.org/~stuartl/asoc/> along with some comments
> > about how to add it to your kernel sources. I'd appreciate any feedback
> > or advice on how the driver can be improved.
>
> Please post to the mailing list if you want review - the standard
> workflow is to provide comments by replying to the patch.
Attached :- sound/soc/codecs/tlv320aic3204.h
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
[-- Attachment #2: tlv320aic3204.h --]
[-- Type: text/x-c, Size: 51749 bytes --]
/*
* ALSA SoC TLV320AIC3204 codec driver
*
* Author: Stuart Longland, <redhatter@gentoo.org>
* Copyright: (C) 2010 Jacques Electronics, Pty, Ltd.
*
* Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
* Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.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 _AIC3204_H
#define _AIC3204_H
/*
* The AIC3204's registers are divided into separate pages, the first page of
* every register is a "page select", which sets the current page being
* accessed. For the purposes of simplicity; we shall consider all registers
* as being addressed by a 16-bit value, consisting of the page number as
* most-significant 9 bits, followed by the register number as given in the
* datasheet.
*
* The register access functions will look after page flipping from here.
*/
/*
* AIC3204 register space; 128 registers each page, we pages 0..127. It's not
* clear from the datasheet exactly how many pages are in use... and it's a
* sparse register map! Ideally, I'd like to just cache the pages that are
* needed, but the ALSA framework doesn't allow this easily.
*
* It does look as if up to and including page 70 is needed. For now, we will
* cache up to page 128 however; as this ensures we get *everything*.
*/
#define AIC3204_CACHEREGNUM (128*128)
/* Page select register */
#define AIC3204_PAGE_SELECT 0
/* Register Page/Number => Address Mapping Macro */
#define AIC3204_PGREG(page,reg) (((page) << 7) | ((reg) & 0x7f))
/* Software reset register */
#define AIC3204_RESET AIC3204_PGREG(0, 1)
/* Perform software reset */
#define AIC3204_RESET_SOFT 1
/* Clock Setting Register 1, Multiplexers */
#define AIC3204_CLK1 AIC3204_PGREG(0, 4)
/* PLL Range Select: Low Range */
#define AIC3204_CLK1_PLLRANGE_LOW (0 << 6)
/* PLL Range Select: High Range */
#define AIC3204_CLK1_PLLRANGE_HIGH (1 << 6)
/* PLL Range Select Mask */
#define AIC3204_CLK1_PLLRANGE (1 << 6)
/* PLL Input Clock Select: MCLK */
#define AIC3204_CLK1_PLLSRC_MCLK (0 << 2)
/* PLL Input Clock Select: BCLK */
#define AIC3204_CLK1_PLLSRC_BCLK (1 << 2)
/* PLL Input Clock Select: GPIO */
#define AIC3204_CLK1_PLLSRC_GPIO (2 << 2)
/* PLL Input Clock Select: Data In */
#define AIC3204_CLK1_PLLSRC_DIN (3 << 2)
/* PLL Input Clock Select Mask */
#define AIC3204_CLK1_PLLSRC (3 << 2)
/* CODEC Clock Source Select: MCLK */
#define AIC3204_CLK1_CODECCLK_MCLK (0)
/* CODEC Clock Source Select: BCLK */
#define AIC3204_CLK1_CODECCLK_BCLK (1)
/* CODEC Clock Source Select: GPIO */
#define AIC3204_CLK1_CODECCLK_GPIO (2)
/* CODEC Clock Source Select: PLL */
#define AIC3204_CLK1_CODECCLK_PLL (3)
/* CODEC Clock Source Select Mask */
#define AIC3204_CLK1_CODECCLK (3)
/* Clock Setting Register 2, PLL P & R Values */
#define AIC3204_CLK2 AIC3204_PGREG(0, 5)
/* PLL State: Powered Down */
#define AIC3204_CLK2_PLL_OFF (0 << 7)
/* PLL State: Powered Up */
#define AIC3204_CLK2_PLL_ON (1 << 7)
/* PLL State Mask */
#define AIC3204_CLK2_PLL (1 << 7)
/* PLL P Divider value bitfield location */
#define AIC3204_CLK2_PLL_P_SHIFT (4)
/* PLL P Divider value bitfield mask */
#define AIC3204_CLK2_PLL_P (7 << 4)
/* PLL R Divider value bitfield location */
#define AIC3204_CLK2_PLL_R_SHIFT (0)
/* PLL R Divider value bitfield mask */
#define AIC3204_CLK2_PLL_R (0xf)
/* Clock Setting Register 3, PLL J Values */
#define AIC3204_CLK3 AIC3204_PGREG(0, 6)
/* PLL J Divider value bitfield location */
#define AIC3204_CLK3_PLL_J_SHIFT (0)
/* PLL J Divider value bitfield mask */
#define AIC3204_CLK3_PLL_J (0x3f)
/* Clock Setting Register 4, PLL D MSB */
#define AIC3204_CLK4 AIC3204_PGREG(0, 7)
/* PLL D most significant portion bitfield location */
#define AIC3204_CLK4_PLL_D_SHIFT (0)
/* PLL D most significant portion bitfield mask */
#define AIC3204_CLK4_PLL_D (0x3f)
/* PLL D most significant portion value shift */
#define AIC3204_CLK4_PLL_D_VSHIFT (8)
/* Clock Setting Register 5, PLL D LSB */
#define AIC3204_CLK5 AIC3204_PGREG(0, 8)
/* PLL D least significant portion bitfield location */
#define AIC3204_CLK5_PLL_D_SHIFT (0)
/* PLL D least significant portion bitfield mask */
#define AIC3204_CLK5_PLL_D (0xff)
/* PLL D least significant portion value shift */
#define AIC3204_CLK5_PLL_D_VSHIFT (0)
/* Clock Setting Register 6, NDAC Values */
#define AIC3204_CLK6 AIC3204_PGREG(0, 11)
/* NDAC State: Powered Down */
#define AIC3204_CLK6_NDAC_STATE_OFF (0 << 7)
/* NDAC State: Powered Up */
#define AIC3204_CLK6_NDAC_STATE_ON (1 << 7)
/* NDAC State Mask */
#define AIC3204_CLK6_NDAC_STATE (1 << 7)
/* NDAC value bitfield location */
#define AIC3204_CLK6_NDAC_SHIFT (0)
/* NDAC value bitfield mask */
#define AIC3204_CLK6_NDAC (0x7f)
/* Clock Setting Register 7, MDAC Values */
#define AIC3204_CLK7 AIC3204_PGREG(0, 12)
/* MDAC State: Powered Down */
#define AIC3204_CLK7_MDAC_STATE_OFF (0 << 7)
/* MDAC State: Powered Up */
#define AIC3204_CLK7_MDAC_STATE_ON (1 << 7)
/* MDAC State Mask */
#define AIC3204_CLK7_MDAC_STATE (1 << 7)
/* MDAC value bitfield location */
#define AIC3204_CLK7_MDAC_SHIFT (0)
/* MDAC value bitfield mask */
#define AIC3204_CLK7_MDAC (0x7f)
/* DAC OSR Setting Register 1, MSB */
#define AIC3204_DOSR1 AIC3204_PGREG(0, 13)
/* DOSR most significant portion bitfield location */
#define AIC3204_DOSR1_MSB_SHIFT (0)
/* DOSR most significant portion bitfield mask */
#define AIC3204_DOSR1_MSB (3)
/* DOSR most significant portion value shift */
#define AIC3204_DOSR1_MSB_VSHIFT (8)
/* DAC OSR Setting Register 2, LSB */
#define AIC3204_DOSR2 AIC3204_PGREG(0, 14)
/* DOSR least significant portion bitfield location */
#define AIC3204_DOSR2_LSB_SHIFT (0)
/* DOSR least significant portion bitfield mask */
#define AIC3204_DOSR2_LSB (0xff)
/* DOSR least significant portion value shift */
#define AIC3204_DOSR2_LSB_VSHIFT (0)
/* Clock Setting Register 8, NADC Values */
#define AIC3204_CLK8 AIC3204_PGREG(0, 18)
/* NADC State: Powered Down */
#define AIC3204_CLK8_NADC_STATE_OFF (0 << 7)
/* NADC State: Powered Up */
#define AIC3204_CLK8_NADC_STATE_ON (1 << 7)
/* NADC State Mask */
#define AIC3204_CLK8_NADC_STATE (1 << 7)
/* NADC value bitfield location */
#define AIC3204_CLK8_NADC_SHIFT (0)
/* NADC value bitfield mask */
#define AIC3204_CLK8_NADC (0x7f)
/* Clock Setting Register 9, MADC Values */
#define AIC3204_CLK9 AIC3204_PGREG(0, 19)
/* MADC State: Powered Down */
#define AIC3204_CLK9_MADC_STATE_OFF (0 << 7)
/* MADC State: Powered Up */
#define AIC3204_CLK9_MADC_STATE_ON (1 << 7)
/* MADC State Mask */
#define AIC3204_CLK9_MADC_STATE (1 << 7)
/* MADC value bitfield location */
#define AIC3204_CLK9_MADC_SHIFT (0)
/* MADC value bitfield mask */
#define AIC3204_CLK9_MADC (0x7f)
/* ADC OSR Setting Register */
#define AIC3204_AOSR AIC3204_PGREG(0, 20)
/*
* Clock Register setting 10, Multiplexers
*
* XXX: Note that the datasheet goofs up here, calling this register 9, but we
* already had a register 9 just before AOSR. Therefore, we will call this
* register 10, and will increment all following by one to correct TI's
* mistake.
*/
#define AIC3204_CLK10 AIC3204_PGREG(0, 25)
/* CDIV Clock Source: MCLK */
#define AIC3204_CLK10_CDIV_MCLK (0)
/* CDIV Clock Source: BCLK */
#define AIC3204_CLK10_CDIV_BCLK (1)
/* CDIV Clock Source: Data IN */
#define AIC3204_CLK10_CDIV_DIN (2)
/* CDIV Clock Source: PLL */
#define AIC3204_CLK10_CDIV_PLL (3)
/* CDIV Clock Source: DAC */
#define AIC3204_CLK10_CDIV_DAC (4)
/* CDIV Clock Source: DAC Modulation */
#define AIC3204_CLK10_CDIV_DAC_MOD (5)
/* CDIV Clock Source: ADC */
#define AIC3204_CLK10_CDIV_ADC (6)
/* CDIV Clock Source: ADC Modulation */
#define AIC3204_CLK10_CDIV_ADC_MOD (7)
/* CDIV Clock Source Mask */
#define AIC3204_CLK10_CDIV (7)
/* Clock Register setting 11, CLKOUT M divider value */
#define AIC3204_CLK11 AIC3204_PGREG(0, 26)
/* CLKOUT Divider State: Off */
#define AIC3204_CLK11_CLKOUT_OFF (0 << 7)
/* CLKOUT Divider State: On */
#define AIC3204_CLK11_CLKOUT_ON (1 << 7)
/* CLKOUT Divider State Mask */
#define AIC3204_CLK11_CLKOUT (1 << 7)
/* CLKOUT Divider M value bitfield location */
#define AIC3204_CLK11_CLKOUTM_SHIFT (0)
/* CLKOUT Divider M value bitfield mask */
#define AIC3204_CLK11_CLKOUTM (0x7f)
/* Audio Interface Setting Register 1 */
#define AIC3204_AISR1 AIC3204_PGREG(0, 27)
/* Audio Interface Select: I2S */
#define AIC3204_AISR1_INT_I2S (0 << 6)
/* Audio Interface Select: DSP */
#define AIC3204_AISR1_INT_DSP (1 << 6)
/* Audio Interface Select: Right-Justified Format */
#define AIC3204_AISR1_INT_RJF (2 << 6)
/* Audio Interface Select: Left-Justified Format */
#define AIC3204_AISR1_INT_LJF (3 << 6)
/* Audio Interface Select Mask */
#define AIC3204_AISR1_INT (3 << 6)
/* Audio Data Word Length: 16 bits */
#define AIC3204_AISR1_WL_16BITS (0 << 4)
/* Audio Data Word Length: 20 bits */
#define AIC3204_AISR1_WL_20BITS (1 << 4)
/* Audio Data Word Length: 24 bits */
#define AIC3204_AISR1_WL_24BITS (2 << 4)
/* Audio Data Word Length: 32 bits */
#define AIC3204_AISR1_WL_32BITS (3 << 4)
/* Audio Data Word Length Mask */
#define AIC3204_AISR1_WL (3 << 4)
/* BCLK Direction Control: Input */
#define AIC3204_AISR1_BCLK_IN (0 << 3)
/* BCLK Direction Control: Output */
#define AIC3204_AISR1_BCLK_OUT (1 << 3)
/* BCLK Direction Control: Mask */
#define AIC3204_AISR1_BCLK (1 << 3)
/* WCLK Direction Control: Input */
#define AIC3204_AISR1_WCLK_IN (0 << 2)
/* WCLK Direction Control: Output */
#define AIC3204_AISR1_WCLK_OUT (1 << 2)
/* WCLK Direction Control: Mask */
#define AIC3204_AISR1_WCLK (1 << 2)
/* DOUT High Impedance Output Control: Never high impedance */
#define AIC3204_AISR1_HIZ_NEVER (0 << 0)
/* DOUT High Impedance Output Control: High impedance when idle */
#define AIC3204_AISR1_HIZ_IDLE (1 << 0)
/* DOUT High Impedance Output Control Mask */
#define AIC3204_AISR1_HIZ (1 << 0)
/* Audio Interface Register 2, Data offset setting */
#define AIC3204_AISR2 AIC3204_PGREG(0, 28)
/* Audio Interface Register 3 */
#define AIC3204_AISR3 AIC3204_PGREG(0, 29)
/* Audio Data Loopback Control: Disabled */
#define AIC3204_AISR3_ADLO_OFF (0 << 5)
/* Audio Data Loopback Control: Enabled */
#define AIC3204_AISR3_ADLO_ON (1 << 5)
/* Audio Data Loopback Control Mask */
#define AIC3204_AISR3_ADLO (1 << 5)
/* ADC->DAC Loopback Control: Disabled */
#define AIC3204_AISR3_ADDALO_OFF (0 << 4)
/* ADC->DAC Loopback Control: Enabled */
#define AIC3204_AISR3_ADDALO_ON (1 << 4)
/* ADC->DAC Loopback Control Mask */
#define AIC3204_AISR3_ADDALO (1 << 4)
/* Audio Bit Clock Polarity: Normal */
#define AIC3204_AISR3_BCLKPOL_NOR (0 << 3)
/* Audio Bit Clock Polarity: Inverted */
#define AIC3204_AISR3_BCLKPOL_INV (1 << 3)
/* Audio Bit Clock Polarity Mask */
#define AIC3204_AISR3_BCLKPOL (1 << 3)
/* Audio Data Interface Clock Buffers: Always powered */
#define AIC3204_AISR3_ADICLK_ALWAYS (0 << 2)
/* Audio Data Interface Clock Buffers: Powered with CODEC only */
#define AIC3204_AISR3_ADICLK_CODEC (1 << 2)
/* Audio Data Interface Clock Buffers' State */
#define AIC3204_AISR3_ADICLK (1 << 2)
/* Audio Bit Clock Divider Source: DAC */
#define AIC3204_AISR3_BDIV_DAC (0 << 0)
/* Audio Bit Clock Divider Source: DAC Modulation */
#define AIC3204_AISR3_BDIV_DAC_MOD (1 << 0)
/* Audio Bit Clock Divider Source: ADC */
#define AIC3204_AISR3_BDIV_ADC (2 << 0)
/* Audio Bit Clock Divider Source: ADC Modulation */
#define AIC3204_AISR3_BDIV_ADC_MOD (3 << 0)
/* Audio Bit Clock Divider Source Mask */
#define AIC3204_AISR3_BDIV (3 << 0)
/* Clock Setting Register 12, BCLK N Divider */
#define AIC3204_CLK12 AIC3204_PGREG(0, 30)
/* BCLK N State: Powered Down */
#define AIC3204_PG0_CLK12_BCLK_STATE_OFF (0 << 7)
/* BCLK N State: Powered Up */
#define AIC3204_CLK12_BCLK_STATE_ON (1 << 7)
/* BCLK N State Mask */
#define AIC3204_CLK12_BCLK_STATE (1 << 7)
/* BCLK N value bitfield location */
#define AIC3204_CLK12_BCLK_SHIFT (0)
/* BCLK N value bitfield mask */
#define AIC3204_CLK12_BCLK (0x7f)
/* Audio Interface Setting Register 4, Secondary Audio Interface */
#define AIC3204_AISR4 AIC3204_PGREG(0, 31)
/* Secondary Bit Clock: GPIO */
#define AIC3204_AISR4_SECBCLK_GPIO (0 << 5)
/* Secondary Bit Clock: SCLK */
#define AIC3204_AISR4_SECBCLK_SCLK (1 << 5)
/* Secondary Bit Clock: MISO */
#define AIC3204_AISR4_SECBCLK_MISO (2 << 5)
/* Secondary Bit Clock: DOUT */
#define AIC3204_AISR4_SECBCLK_DOUT (3 << 5)
/* Secondary Bit Clock Mask */
#define AIC3204_AISR4_SECBCLK (3 << 5)
/* Secondary Word Clock: GPIO */
#define AIC3204_AISR4_SECWCLK_GPIO (0 << 3)
/* Secondary Word Clock: SCLK */
#define AIC3204_AISR4_SECWCLK_SCLK (1 << 3)
/* Secondary Word Clock: MISO */
#define AIC3204_AISR4_SECWCLK_MISO (2 << 3)
/* Secondary Word Clock: DOUT */
#define AIC3204_AISR4_SECWCLK_DOUT (3 << 3)
/* Secondary Word Clock Mask */
#define AIC3204_AISR4_SECWCLK (3 << 3)
/* ADC Word Clock: GPIO */
#define AIC3204_AISR4_ADCWCLK_GPIO (0 << 3)
/* ADC Word Clock: SCLK */
#define AIC3204_AISR4_ADCWCLK_SCLK (1 << 3)
/* ADC Word Clock: MISO */
#define AIC3204_AISR4_ADCWCLK_MISO (2 << 3)
/* ADC Word Clock Mask */
#define AIC3204_AISR4_ADCWCLK (3 << 3)
/* Secondary Data Input: GPIO */
#define AIC3204_AISR4_SECDIN_GPIO (0 << 0)
/* Secondary Data Input: SCLK */
#define AIC3204_AISR4_SECDIN_SCLK (1 << 0)
/* Secondary Data Input Mask */
#define AIC3204_AISR4_SECDIN (1 << 0)
/* Audio Interface Setting Register 5 */
#define AIC3204_AISR5 AIC3204_PGREG(0, 32)
/* Audio Interface Bit Clock: Primary (BCLK) */
#define AIC3204_AISR5_BCLKIN_PRI (0 << 3)
/* Audio Interface Bit Clock: Secondary */
#define AIC3204_AISR5_BCLKIN_SEC (1 << 3)
/* Audio Interface Bit Clock Mask */
#define AIC3204_AISR5_WCLKIN (1 << 2)
/* Audio Interface Word Clock: Primary (BCLK) */
#define AIC3204_AISR5_WCLKIN_PRI (0 << 2)
/* Audio Interface Word Clock: Secondary */
#define AIC3204_AISR5_WCLKIN_SEC (1 << 2)
/* Audio Interface Word Clock Mask */
#define AIC3204_AISR5_WCLKIN (1 << 2)
/* ADC Word Clock Control: ADC WCLK = DAC WCLK */
#define ADC3204_PG0_AISR5_ADCWCLK_DAC (0 << 1)
/* ADC Word Clock Control: ADC WCLK = Secondary ADC WCLK */
#define ADC3204_PG0_AISR5_ADCWCLK_SEC (1 << 1)
/* ADC Word Clock Control Mask */
#define ADC3204_PG0_AISR5_ADCWCLK (1 << 1)
/* Audio Data In: Primary Data In */
#define ADC3204_PG0_AISR5_DIN_PRI (0 << 0)
/* Audio Data In: Secondary Data In */
#define ADC3204_PG0_AISR5_DIN_SEC (1 << 0)
/* Audio Data In Mask */
#define ADC3204_PG0_AISR5_DIN (1 << 0)
/* Audio Interface Setting Register 6 */
#define AIC3204_AISR6 AIC3204_PGREG(0, 33)
/* BCLK Output Control: Generated Primary Bit Clock */
#define AIC3204_AISR6_BCLKOUT_GEN (0 << 7)
/* BCLK Output Control: Secondary Bit Clock */
#define AIC3204_AISR6_BCLKOUT_SEC (1 << 7)
/* BCLK Output Control Mask */
#define AIC3204_AISR6_BCLKOUT (1 << 7)
/* Secondary BCLK Output Control: Primary Bit Clock Input */
#define AIC3204_AISR6_SBCLKOUT_BCLK (0 << 6)
/* Secondary BCLK Output Control: Generated Primary Bit Clock */
#define AIC3204_AISR6_SBCLKOUT_GEN (1 << 6)
/* Secondary BCLK Output Control Mask */
#define AIC3204_AISR6_SBCLKOUT (1 << 6)
/* WCLK Output Control: Generated DAC_FS */
#define AIC3204_AISR6_WCLKOUT_DAC (0 << 4)
/* WCLK Output Control: Generated ADC_FS */
#define AIC3204_AISR6_WCLKOUT_ADC (1 << 4)
/* WCLK Output Control: Secondary WCLK Input */
#define AIC3204_AISR6_WCLKOUT_SWCLK (2 << 4)
/* WCLK Output Control Mask */
#define AIC3204_AISR6_WCLKOUT (3 << 4)
/* Secondary WCLK Output Control: WCLK Input */
#define AIC3204_AISR6_SWCLKOUT_WCLK (0 << 2)
/* Secondary WCLK Output Control: Generated DAC_FS */
#define AIC3204_AISR6_SWCLKOUT_DAC (1 << 2)
/* Secondary WCLK Output Control: Generated ADC_FS */
#define AIC3204_AISR6_SWCLKOUT_ADC (2 << 2)
/* Secondary WCLK Output Control Mask */
#define AIC3204_AISR6_SWCLKOUT (3 << 2)
/* Primary Data Output Control: Serial Interface */
#define AIC3204_AISR6_DOUT_INT (0 << 1)
/* Primary Data Output Control: Secondary Data Input */
#define AIC3204_AISR6_DOUT_SDIN (1 << 1)
/* Primary Data Output Control Mask */
#define AIC3204_AISR6_DOUT (1 << 1)
/* Secondary Data Output Control: Primary Data In */
#define AIC3204_AISR6_SDOUT_DIN (0 << 0)
/* Secondary Data Output Control: Serial Interface */
#define AIC3204_AISR6_SDOUT_INT (1 << 0)
/* Secondary Data Output Control Mask */
#define AIC3204_AISR6_SDOUT (1 << 0)
/* Digital Interface Misc. Setting Register */
#define AIC3204_DIMISC AIC3204_PGREG(0, 34)
/* I2C General Call Address Configuration: Ignore */
#define AIC3204_DIMISC_I2CGC_IGNORE (0 << 5)
/* I2C General Call Address Configuration: Accept */
#define AIC3204_DIMISC_I2CGC_ACCEPT (1 << 5)
/* ADC Flag Register */
#define AIC3204_ADCF AIC3204_PGREG(0, 36)
/* Left ADC PGA Status: Gain is set */
#define AIC3204_ADCF_LEFT_PGASET (1 << 7)
/* Left ADC Power Status: Powered Up */
#define AIC3204_ADCF_LEFT_UP (1 << 6)
/* Left ADC AGC Status: Gain is saturated */
#define AIC3204_ADCF_LEFT_AGCSAT (1 << 5)
/* Right ADC PGA Status: Gain is set */
#define AIC3204_ADCF_RIGHT_PGASET (1 << 3)
/* Right ADC Power Status: Powered Up */
#define AIC3204_ADCF_RIGHT_UP (1 << 2)
/* Right ADC AGC Status: Gain is saturated */
#define AIC3204_ADCF_RIGHT_AGCSAT (1 << 1)
/* DAC Flag Register 1 */
#define AIC3204_DACF1 AIC3204_PGREG(0, 37)
/* Left DAC powered up */
#define AIC3204_DACF1_LEFT_UP (1 << 7)
/* Left Line Output Driver powered up */
#define AIC3204_DACF1_LOL_UP (1 << 6)
/* Left Headphone Output Driver powered up */
#define AIC3204_DACF1_HPL_UP (1 << 5)
/* Right DAC powered up */
#define AIC3204_DACF1_RIGHT_UP (1 << 7)
/* Right Line Output Driver powered up */
#define AIC3204_DACF1_LOR_UP (1 << 6)
/* Right Headphone Output Driver powered up */
#define AIC3204_DACF1_HPR_UP (1 << 5)
/* DAC Flag Register 2 */
#define AIC3204_DACF2 AIC3204_PGREG(0, 38)
/* Left DAC PGA Status: Gain is set */
#define AIC3204_DACF2_LEFT_PGASET (1 << 4)
/* Right DAC PGA Status: Gain is set */
#define AIC3204_DACF2_RIGHT_PGASET (1 << 0)
/* Sticky Flag Register 1 */
#define AIC3204_STICK1 AIC3204_PGREG(0, 42)
/* Left DAC Overflow */
#define AIC3204_STICK1_LDAC_OVER (1 << 7)
/* Right DAC Overflow */
#define AIC3204_STICK1_RDAC_OVER (1 << 6)
/* Left ADC Overflow */
#define AIC3204_STICK1_LADC_OVER (1 << 3)
/* Right ADC Overflow */
#define AIC3204_STICK1_RADC_OVER (1 << 2)
/* Interrupt Flag Register 1 */
#define AIC3204_INTF1 AIC3204_PGREG(0, 43)
/* Left DAC Overflow */
#define AIC3204_INTF1_LDAC_OVER (1 << 7)
/* Right DAC Overflow */
#define AIC3204_INTF1_RDAC_OVER (1 << 6)
/* Left ADC Overflow */
#define AIC3204_INTF1_LADC_OVER (1 << 3)
/* Right ADC Overflow */
#define AIC3204_INTF1_RADC_OVER (1 << 2)
/* Sticky Flag Register 2 */
#define AIC3204_STICK2 AIC3204_PGREG(0, 44)
/* Left Headphone Driver Over Current */
#define AIC3204_STICK2_HPL_OVER (1 << 7)
/* Right Headphone Driver Over Current */
#define AIC3204_STICK2_HPR_OVER (1 << 6)
/* Headset button pressed */
#define AIC3204_STICK2_HS_BUTTON (1 << 5)
/* Headset plug inserted/removed */
#define AIC3204_STICK2_HS_PLUGGED (1 << 4)
/* Left Channel DRC: Over threshold */
#define AIC3204_STICK2_LDRC_OVER (1 << 3)
/* Right Channel DRC: Over threshold */
#define AIC3204_STICK2_RDRC_OVER (1 << 2)
/* Sticky Flag Register 3 */
#define AIC3204_STICK3 AIC3204_PGREG(0, 45)
/* Left AGC Noise Threshold Flag: Over threshold */
#define AIC3204_STICK3_LAGC_OVER (1 << 6)
/* Right AGC Noise Threshold Flag: Over threshold */
#define AIC3204_STICK3_RAGC_OVER (1 << 5)
/* Left ADC DC Measurement Available */
#define AIC3204_STICK3_LADC_DC (1 << 2)
/* Right ADC DC Measurement Available */
#define AIC3204_STICK3_RADC_DC (1 << 1)
/* Interrupt Flag Register 2 */
#define AIC3204_INTF2 AIC3204_PGREG(0, 46)
/* Left Headphone Driver Over Current */
#define AIC3204_INTF2_HPL_OVER (1 << 7)
/* Right Headphone Driver Over Current */
#define AIC3204_INTF2_HPR_OVER (1 << 6)
/* Headset button pressed */
#define AIC3204_INTF2_HS_BUTTON (1 << 5)
/* Headset plug inserted/removed */
#define AIC3204_INTF2_HS_PLUGGED (1 << 4)
/* Left Channel DRC: Over threshold */
#define AIC3204_INTF2_LDRC_OVER (1 << 3)
/* Right Channel DRC: Over threshold */
#define AIC3204_INTF2_RDRC_OVER (1 << 2)
/* Interrupt Flag Register 3 */
#define AIC3204_INTF3 AIC3204_PGREG(0, 47)
/* Left AGC Noise Threshold Flag: Over threshold */
#define AIC3204_INTF3_LAGC_OVER (1 << 6)
/* Right AGC Noise Threshold Flag: Over threshold */
#define AIC3204_INTF3_RAGC_OVER (1 << 5)
/* Left ADC DC Measurement Available */
#define AIC3204_INTF3_LADC_DC (1 << 2)
/* Right ADC DC Measurement Available */
#define AIC3204_INTF3_RADC_DC (1 << 1)
/* INT1 Interrupt Control Register */
#define AIC3204_INT1 AIC3204_PGREG(0, 48)
/* INT1 Generated on Headset insertion */
#define AIC3204_INT1_HS_PLUG (1 << 7)
/* INT1 Generated on Headset Button press */
#define AIC3204_INT1_HS_BUTTON (1 << 6)
/* INT1 Generated on DAC DRC Signal Threshold */
#define AIC3204_INT1_DAC_DRC (1 << 5)
/* INT1 Generated on AGC Noise Interrupt */
#define AIC3204_INT1_ADC_NOISE (1 << 4)
/* INT1 Generated on Over Current */
#define AIC3204_INT1_HP_OVERCURRENT (1 << 3)
/* INT1 Generated on overflow event */
#define AIC3204_INT1_OVERFLOW (1 << 2)
/* INT1 Generated on DC measurement */
#define AIC3204_INT1_DC (1 << 1)
/* INT1 pulse control: Continuous pulse train */
#define AIC3204_INT1_CONT_PULSE (1 << 0)
/* INT2 Interrupt Control Register */
#define AIC3204_INT2 AIC3204_PGREG(0, 49)
/* INT2 Generated on Headset insertion */
#define AIC3204_INT2_HS_PLUG (1 << 7)
/* INT2 Generated on Headset Button press */
#define AIC3204_INT2_HS_BUTTON (1 << 6)
/* INT2 Generated on DAC DRC Signal Threshold */
#define AIC3204_INT2_DAC_DRC (1 << 5)
/* INT2 Generated on AGC Noise Interrupt */
#define AIC3204_INT2_ADC_NOISE (1 << 4)
/* INT2 Generated on Over Current */
#define AIC3204_INT2_HP_OVERCURRENT (1 << 3)
/* INT2 Generated on overflow event */
#define AIC3204_INT2_OVERFLOW (1 << 2)
/* INT2 Generated on DC measurement */
#define AIC3204_INT2_DC (1 << 1)
/* INT2 pulse control: Continuous pulse train */
#define AIC3204_INT2_CONT_PULSE (1 << 0)
/* GPIO/MFP5 Control Register */
#define AIC3204_MFP5 AIC3204_PGREG(0, 52)
/* GPIO Control: Disabled */
#define AIC3204_MFP5_FUNC_DISABLED (0 << 2)
/* GPIO Control: Secondary audio interface/digital microphone/clock input */
#define AIC3204_MFP5_FUNC_SAI_DM_CI (1 << 2)
/* GPIO Control: General Purpose Input */
#define AIC3204_MFP5_FUNC_INPUT (2 << 2)
/* GPIO Control: General Purpose Output */
#define AIC3204_MFP5_FUNC_OUTPUT (3 << 2)
/* GPIO Control: CLKOUT Output */
#define AIC3204_MFP5_FUNC_CLKOUT (4 << 2)
/* GPIO Control: INT1 Output */
#define AIC3204_MFP5_FUNC_INT1 (4 << 2)
/* GPIO Control: INT2 Output */
#define AIC3204_MFP5_FUNC_INT2 (4 << 2)
/* GPIO Control: ADC_WCLK */
#define AIC3204_MFP5_FUNC_ADC_WCLK (4 << 2)
/* GPIO Control: Secondary Bit Clock */
#define AIC3204_MFP5_FUNC_SEC_BCLK (4 << 2)
/* GPIO Control: Secondary Word Clock */
#define AIC3204_MFP5_FUNC_SEC_WCLK (4 << 2)
/* GPIO Control: Digital Microphone Clock */
#define AIC3204_MFP5_FUNC_DMIC_CLK (4 << 2)
/* GPIO Control Mask */
#define AIC3204_MFP5_FUNC (15 << 2)
/* GPIO Input State */
#define AIC3204_MFP5_IN (1 << 1)
/* GPIO Output State */
#define AIC3204_MFP5_OUT (1 << 0)
/* DOUT/MFP2 Function Control Register */
#define AIC3204_MFP2 AIC3204_PGREG(0, 53)
/* DOUT Bus Keeper Enabled */
#define AIC3204_MFP2_BK (1 << 4)
/* DOUT MUX Control: Disabled */
#define AIC3204_MFP2_FUNC_DISABLED (0 << 1)
/* DOUT MUX Control: Primary DOUT */
#define AIC3204_MFP2_FUNC_PRI_DOUT (1 << 1)
/* DOUT MUX Control: General Purpose Output */
#define AIC3204_MFP2_FUNC_OUTPUT (2 << 1)
/* DOUT MUX Control: CLKOUT Clock Output */
#define AIC3204_MFP2_FUNC_CLKOUT (3 << 1)
/* DOUT MUX Control: INT1 Output */
#define AIC3204_MFP2_FUNC_INT1 (4 << 1)
/* DOUT MUX Control: INT2 Output */
#define AIC3204_MFP2_FUNC_INT2 (5 << 1)
/* DOUT MUX Control: Secondary Bit Clock */
#define AIC3204_MFP2_FUNC_SEC_BCLK (6 << 1)
/* DOUT MUX Control: Secondary Word Clock */
#define AIC3204_MFP2_FUNC_SEC_WCLK (7 << 1)
/* DOUT MUX Control Mask */
#define AIC3204_MFP2_FUNC (7 << 1)
/* DOUT General Purpose Output State */
#define AIC3204_MFP2_OUT (1 << 0)
/* DIN/MFP1 Function Control Register */
#define AIC3204_MFP1 AIC3204_PGREG(0, 54)
/* DIN Function Control: Disabled */
#define AIC3204_MFP1_FUNC_DISABLED (0 << 1)
/* DIN Function Control: Primary Data Input/Digital Microphone/Clock Input */
#define AIC3204_MFP1_FUNC_DIN_DM_CI (1 << 1)
/* DIN Function Control: General Purpose Input */
#define AIC3204_MFP1_FUNC_INPUT (2 << 1)
/* DIN Function Control Mask */
#define AIC3204_MFP1_FUNC (3 << 1)
/* DIN Input State */
#define AIC3204_MFP1_IN (1 << 0)
/* MISO/MFP4 Function Control Register */
#define AIC3204_MFP4 AIC3204_PGREG(0, 55)
/* MISO Function Control: Disabled */
#define AIC3204_MFP4_FUNC_DISABLED (0 << 1)
/* MISO Function Control: SPI Data Output (disabled in I2C mode) */
#define AIC3204_MFP4_FUNC_SPI_OUT (1 << 1)
/* MISO Function Control: General Purpose Output */
#define AIC3204_MFP4_FUNC_OUTPUT (2 << 1)
/* MISO Function Control: CLKOUT Clock Output */
#define AIC3204_MFP4_FUNC_CLKOUT (3 << 1)
/* MISO Function Control: INT1 Output */
#define AIC3204_MFP4_FUNC_INT1 (4 << 1)
/* MISO Function Control: INT2 Output */
#define AIC3204_MFP4_FUNC_INT2 (5 << 1)
/* MISO Function Control: ADC Word Clock Output */
#define AIC3204_MFP4_FUNC_ADC_WCLK (6 << 1)
/* MISO Function Control: Digital Microphone Clock Output */
#define AIC3204_MFP4_FUNC_DMIC_CLK (7 << 1)
/* MISO Function Control: Secondary Data Output */
#define AIC3204_MFP4_FUNC_SEC_DOUT (8 << 1)
/* MISO Function Control: Secondary Bit Clock */
#define AIC3204_MFP4_FUNC_SEC_BCLK (9 << 1)
/* MISO Function Control: Secondary Word Clock */
#define AIC3204_MFP4_FUNC_SEC_WCLK (10 << 1)
/* MISO Function Control Mask */
#define AIC3204_MFP4_FUNC (15 << 1)
/* MISO Output State */
#define AIC3204_MFP4_OUT (1 << 0)
/* SCLK/MFP3 Function Control Register */
#define AIC3204_MFP3 AIC3204_PGREG(0, 56)
/* SCLK Function Control: Disabled */
#define AIC3204_MFP3_FUNC_DISABLED (0 << 1)
/* SCLK Function Control: SPI Clock / Secondary Interface / Digital Mic Input */
#define AIC3204_MFP3_FUNC_SPI_SI_DM (1 << 1)
/* SCLK Function Control: General Purpose Input */
#define AIC3204_MFP3_FUNC_INPUT (2 << 1)
/* SCLK Function Control Mask */
#define AIC3204_MFP3_FUNC (3 << 1)
/* SCLK Input State */
#define AIC3204_MFP3_IN (1 << 0)
/* DAC Signal Processing Block Control Register */
#define AIC3204_DACSPB AIC3204_PGREG(0, 60)
/* DAC Signal Processing Block Mask */
#define AIC3204_DACSPB_MASK 0x1f
/* ADC Signal Processing Block Control Register */
#define AIC3204_ADCSPB AIC3204_PGREG(0, 61)
/* ADC Signal Processing Block Mask */
#define AIC3204_ADCSPB_MASK 0x1f
/* DAC Channel Setup Register 1 */
#define AIC3204_DACS1 AIC3204_PGREG(0, 63)
/* Left DAC Powered Up */
#define AIC3204_DACS1_LDAC_UP_SHIFT (7)
/* Right DAC Powered Up */
#define AIC3204_DACS1_RDAC_UP_SHIFT (6)
/* Left DAC Data Path Control: Disabled */
#define AIC3204_DACS1_LDACD_DIS (0 << 4)
/* Left DAC Data Path Control: Left Data */
#define AIC3204_DACS1_LDACD_LEFT (1 << 4)
/* Left DAC Data Path Control: Right Data */
#define AIC3204_DACS1_LDACD_RIGHT (2 << 4)
/* Left DAC Data Path Control: Left + Right */
#define AIC3204_DACS1_LDACD_MIX (3 << 4)
/* Left DAC Data Path Control Mask */
#define AIC3204_DACS1_LDACD (3 << 4)
/* Right DAC Data Path Control: Disabled */
#define AIC3204_DACS1_RDACD_DIS (0 << 2)
/* Right DAC Data Path Control: Right Data */
#define AIC3204_DACS1_RDACD_RIGHT (1 << 2)
/* Right DAC Data Path Control: Left Data */
#define AIC3204_DACS1_RDACD_LEFT (2 << 2)
/* Right DAC Data Path Control: Left + Right */
#define AIC3204_DACS1_RDACD_MIX (3 << 2)
/* Right DAC Data Path Control Mask */
#define AIC3204_DACS1_RDACD (3 << 2)
/* DAC Soft-Step: Disabled */
#define AIC3204_DACS1_SOFT_DIS (0 << 0)
/* DAC Soft-Step: 1 step every clock */
#define AIC3204_DACS1_SOFT_1SEC (1 << 0)
/* DAC Soft-Step: 1 step every 2 clocks */
#define AIC3204_DACS1_SOFT_1SE2C (2 << 0)
/* DAC Soft-Step Mask */
#define AIC3204_DACS1_SOFT (3 << 0)
/* DAC Channel Setup Register 2 */
#define AIC3204_DACS2 AIC3204_PGREG(0, 64)
/* Right Modulator Output Control: Output is inverted left output */
#define AIC3204_DACS2_RMOD_INV (1 << 7)
/* DAC Auto Mute: Disabled */
#define AIC3204_DACS2_AMUTE_DIS (0 << 4)
/* DAC Auto Mute: After 100 samples of DC */
#define AIC3204_DACS2_AMUTE_100 (1 << 4)
/* DAC Auto Mute: After 200 samples of DC */
#define AIC3204_DACS2_AMUTE_200 (2 << 4)
/* DAC Auto Mute: After 400 samples of DC */
#define AIC3204_DACS2_AMUTE_400 (3 << 4)
/* DAC Auto Mute: After 800 samples of DC */
#define AIC3204_DACS2_AMUTE_800 (4 << 4)
/* DAC Auto Mute: After 1600 samples of DC */
#define AIC3204_DACS2_AMUTE_1600 (5 << 4)
/* DAC Auto Mute: After 3200 samples of DC */
#define AIC3204_DACS2_AMUTE_3200 (6 << 4)
/* DAC Auto Mute: After 6400 samples of DC */
#define AIC3204_DACS2_AMUTE_6400 (7 << 4)
/* DAC Auto Mute Mask */
#define AIC3204_DACS2_AMUTE (7 << 4)
/* Left DAC Muted */
#define AIC3204_DACS2_LEFT_MUTE_SHIFT (3)
/* Right DAC Muted */
#define AIC3204_DACS2_RIGHT_MUTE_SHIFT (2)
/* DAC Master Volume Control: Independant */
#define AIC3204_DACS2_VOL_IND (0 << 0)
/* DAC Master Volume Control: Left controls right */
#define AIC3204_DACS2_VOL_LEFT (1 << 0)
/* DAC Master Volume Control: Right controls left */
#define AIC3204_DACS2_VOL_RIGHT (2 << 0)
/* DAC Master Volume Control Mask */
#define AIC3204_DACS2_VOL (3 << 0)
/* Left DAC Digital Volume Control */
#define AIC3204_LDACVOL AIC3204_PGREG(0, 65)
/* Right DAC Digital Volume Control */
#define AIC3204_RDACVOL AIC3204_PGREG(0, 66)
/* Headset Detection Configuration Register */
#define AIC3204_HSDET AIC3204_PGREG(0, 67)
/* Enable Headset Detection */
#define AIC3204_HSDET_ENABLE (1 << 7)
/* Headset Type: No Headset */
#define AIC3204_HSDET_NONE (0 << 5)
/* Headset Type: Stereo Headset */
#define AIC3204_HSDET_STEREO (1 << 5)
/* Headset Type: Cellular Stereo Headset */
#define AIC3204_HSDET_CELLSTEREO (3 << 5)
/* Headset Detection Debounce Time: 16ms */
#define AIC3204_HSDET_PLUGDT_16 (0 << 2)
/* Headset Detection Debounce Time: 32ms */
#define AIC3204_HSDET_PLUGDT_32 (1 << 2)
/* Headset Detection Debounce Time: 64ms */
#define AIC3204_HSDET_PLUGDT_64 (2 << 2)
/* Headset Detection Debounce Time: 128ms */
#define AIC3204_HSDET_PLUGDT_128 (3 << 2)
/* Headset Detection Debounce Time: 256ms */
#define AIC3204_HSDET_PLUGDT_256 (4 << 2)
/* Headset Detection Debounce Time: 512ms */
#define AIC3204_HSDET_PLUGDT_512 (5 << 2)
/* Headset Detection Debounce Time Mask */
#define AIC3204_HSDET_PLUGDT (7 << 2)
/* Headset Button Debounce Time: 8ms */
#define AIC3204_HSDET_BTNDT_8 (0 << 0)
/* Headset Button Debounce Time: 16ms */
#define AIC3204_HSDET_BTNDT_16 (1 << 0)
/* Headset Button Debounce Time: 32ms */
#define AIC3204_HSDET_BTNDT_32 (2 << 0)
/* Headset Button Debounce Time Mask */
#define AIC3204_HSDET_BTNDT (3 << 0)
/* DRC Control Register 1 */
#define AIC3204_DRC1 AIC3204_PGREG(0, 68)
/* Left Channel DRC Enable */
#define AIC3204_DRC1_LEFT_EN (1 << 6)
/* Right Channel DRC Enable */
#define AIC3204_DRC1_RIGHT_EN (1 << 5)
/* DRC Threshold: -3dBFS */
#define AIC3204_DRC1_THOLD_3DBFS (0 << 2)
/* DRC Threshold: -6dBFS */
#define AIC3204_DRC1_THOLD_6DBFS (1 << 2)
/* DRC Threshold: -9dBFS */
#define AIC3204_DRC1_THOLD_9DBFS (2 << 2)
/* DRC Threshold: -12dBFS */
#define AIC3204_DRC1_THOLD_12DBFS (3 << 2)
/* DRC Threshold: -15dBFS */
#define AIC3204_DRC1_THOLD_15DBFS (4 << 2)
/* DRC Threshold: -18dBFS */
#define AIC3204_DRC1_THOLD_18DBFS (5 << 2)
/* DRC Threshold: -21dBFS */
#define AIC3204_DRC1_THOLD_21DBFS (6 << 2)
/* DRC Threshold: -24dBFS */
#define AIC3204_DRC1_THOLD_24DBFS (7 << 2)
/* DRC Threshold Mask */
#define AIC3204_DRC1_THOLD (7 << 2)
/* DRC Hysteresis: 0dB */
#define AIC3204_DRC1_HYST_0DB (0 << 0)
/* DRC Hysteresis: 1dB */
#define AIC3204_DRC1_HYST_1DB (1 << 0)
/* DRC Hysteresis: 2dB */
#define AIC3204_DRC1_HYST_2DB (2 << 0)
/* DRC Hysteresis: 3dB */
#define AIC3204_DRC1_HYST_3DB (3 << 0)
/* DRC Hysteresis Mask */
#define AIC3204_DRC1_HYST (3 << 0)
/* DRC Control Register 2 */
#define AIC3204_DRC2 AIC3204_PGREG(0, 69)
/* DRC Hold: Disabled */
#define AIC3204_DRC2_HOLD_DISABLED (0 << 3)
/* DRC Hold: 32 Word Clocks */
#define AIC3204_DRC2_HOLD_32WC (1 << 3)
/* DRC Hold: 64 Word Clocks */
#define AIC3204_DRC2_HOLD_64WC (2 << 3)
/* DRC Hold: 128 Word Clocks */
#define AIC3204_DRC2_HOLD_128WC (3 << 3)
/* DRC Hold: 256 Word Clocks */
#define AIC3204_DRC2_HOLD_256WC (4 << 3)
/* DRC Hold: 512 Word Clocks */
#define AIC3204_DRC2_HOLD_512WC (5 << 3)
/* DRC Hold: 1024 Word Clocks */
#define AIC3204_DRC2_HOLD_1024WC (6 << 3)
/* DRC Hold: 2048 Word Clocks */
#define AIC3204_DRC2_HOLD_2048WC (7 << 3)
/* DRC Hold: 4096 Word Clocks */
#define AIC3204_DRC2_HOLD_4096WC (8 << 3)
/* DRC Hold: 8192 Word Clocks */
#define AIC3204_DRC2_HOLD_8192WC (9 << 3)
/* DRC Hold: 16384 Word Clocks */
#define AIC3204_DRC2_HOLD_16384WC (10 << 3)
/* DRC Hold: 32768 Word Clocks */
#define AIC3204_DRC2_HOLD_32768WC (11 << 3)
/* DRC Hold: 65536 Word Clocks */
#define AIC3204_DRC2_HOLD_65536WC (12 << 3)
/* DRC Hold: 98304 Word Clocks */
#define AIC3204_DRC2_HOLD_98304WC (13 << 3)
/* DRC Hold: 131072 Word Clocks */
#define AIC3204_DRC2_HOLD_131072WC (14 << 3)
/* DRC Hold: 163840 Word Clocks */
#define AIC3204_DRC2_HOLD_163840WC (15 << 3)
/* DRC Hold Mask */
#define AIC3204_DRC2_HOLD (15 << 3)
/* DRC Control Register 3 */
#define AIC3204_DRC3 AIC3204_PGREG(0, 70)
/* DRC Attack Rate Shift */
#define AIC3204_DRC3_ATTACK_SHIFT 4
/* DRC Attack Rate Mask */
#define AIC3204_DRC3_ATTACK (15 << AIC3204_DRC3_ATTACK_SHIFT)
/* DRC Decay Rate Shift */
#define AIC3204_DRC3_DECAY_SHIFT 0
/* DRC Decay Rate Mask */
#define AIC3204_DRC3_DECAY (15 << AIC3204_DRC3_DECAY_SHIFT)
/* Beep Generator Register 1 */
#define AIC3204_BEEP1 AIC3204_PGREG(0, 71)
/* Enable Beep Generator */
#define AIC3204_BEEP1_EN (1 << 7)
/* Left Channel Beep Volume Shift */
#define AIC3204_BEEP1_VOL_SHIFT 0
/* Left Channel Beep Volume Mask */
#define AIC3204_BEEP1_VOL (0x3f)
/* Beep Generator Register 2 */
#define AIC3204_BEEP2 AIC3204_PGREG(0, 72)
/* Beep Generator Master Volume: Independant Left/Right Channels */
#define AIC3204_BEEP2_MVOL_IND (0 << 6)
/* Beep Generator Master Volume: Left Channel controls Right */
#define AIC3204_BEEP2_MVOL_LEFT (1 << 6)
/* Beep Generator Master Volume: Right Channel controls Left */
#define AIC3204_BEEP2_MVOL_RIGHT (2 << 6)
/* Beep Generator Master Volume Mask */
#define AIC3204_BEEP2_MVOL (3 << 6)
/* Beep Generator Register 3 */
#define AIC3204_BEEP3 AIC3204_PGREG(0, 73)
/* Sample Length Value Shift */
#define AIC3204_BEEP3_LENGTH_VSHIFT 16
/* Sample Length Shift */
#define AIC3204_BEEP3_LENGTH_SHIFT 0
/* Sample Length Mask */
#define AIC3204_BEEP3_LENGTH (0xff)
/* Beep Generator Register 4 */
#define AIC3204_BEEP4 AIC3204_PGREG(0, 74)
/* Sample Length Value Shift */
#define AIC3204_BEEP4_LENGTH_VSHIFT 8
/* Sample Length Shift */
#define AIC3204_BEEP4_LENGTH_SHIFT 0
/* Sample Length Mask */
#define AIC3204_BEEP4_LENGTH (0xff)
/* Beep Generator Register 5 */
#define AIC3204_BEEP5 AIC3204_PGREG(0, 75)
/* Sample Length Value Shift */
#define AIC3204_BEEP5_LENGTH_VSHIFT 0
/* Sample Length Shift */
#define AIC3204_BEEP5_LENGTH_SHIFT 0
/* Sample Length Mask */
#define AIC3204_BEEP5_LENGTH (0xff)
/* Beep Generator Register 6 */
#define AIC3204_BEEP6 AIC3204_PGREG(0, 76)
/* Relative Sine Frequency Value Shift */
#define AIC3204_BEEP6_SINFREQ_VSHIFT 8
/* Relative Sine Frequency Shift */
#define AIC3204_BEEP6_SINFREQ_SHIFT 0
/* Relative Sine Frequency Mask */
#define AIC3204_BEEP6_SINFREQ (0xff)
/* Beep Generator Register 7 */
#define AIC3204_BEEP7 AIC3204_PGREG(0, 77)
/* Relative Sine Frequency Value Shift */
#define AIC3204_BEEP7_SINFREQ_VSHIFT 0
/* Relative Sine Frequency Shift */
#define AIC3204_BEEP7_SINFREQ_SHIFT 0
/* Relative Sine Frequency Mask */
#define AIC3204_BEEP7_SINFREQ (0xff)
/* Beep Generator Register 8 */
#define AIC3204_BEEP8 AIC3204_PGREG(0, 78)
/* Relative Cosine Frequency Value Shift */
#define AIC3204_BEEP8_COSFREQ_VSHIFT 8
/* Relative Cosine Frequency Shift */
#define AIC3204_BEEP8_COSFREQ_SHIFT 0
/* Relative Cosine Frequency Mask */
#define AIC3204_BEEP8_COSFREQ (0xff)
/* Beep Generator Register 9 */
#define AIC3204_BEEP9 AIC3204_PGREG(0, 79)
/* Relative Cosine Frequency Value Shift */
#define AIC3204_BEEP9_COSFREQ_VSHIFT 0
/* Relative Cosine Frequency Shift */
#define AIC3204_BEEP9_COSFREQ_SHIFT 0
/* Relative Cosine Frequency Mask */
#define AIC3204_BEEP9_COSFREQ (0xff)
/* ADC Channel Setup Register */
#define AIC3204_ADCS AIC3204_PGREG(0, 81)
/* Left Channel ADC Powered Up */
#define AIC3204_ADCS_LADC_UP_SHIFT (7)
/* Right Channel ADC Powered Up */
#define AIC3204_ADCS_RADC_UP_SHIFT (6)
/* Digital Microphone Input: GPIO */
#define AIC3204_ADC5_DMICIN_GPIO (0 << 4)
/* Digital Microphone Input: SCLK */
#define AIC3204_ADC5_DMICIN_SCLK (1 << 4)
/* Digital Microphone Input: DIN */
#define AIC3204_ADC5_DMICIN_DIN (2 << 4)
/* Digital Microphone Input Mask */
#define AIC3204_ADC5_DMICIN (3 << 4)
/* Left ADC Source: Digital Microphone */
#define AIC3204_ADC5_LADC_DMIC (1 << 3)
/* Right ADC Source: Digital Microphone */
#define AIC3204_ADC5_RADC_DMIC (1 << 2)
/* ADC Volume Soft Step: 1 step / word clock */
#define AIC3204_ADC5_VOLSS_1SPWC (0 << 0)
/* ADC Volume Soft Step: 1 step / 2 word clocks */
#define AIC3204_ADC5_VOLSS_1SP2WC (1 << 0)
/* ADC Volume Soft Step: Disabled */
#define AIC3204_ADC5_VOLSS_DISABLED (2 << 0)
/* ADC Volume Soft Step Mask */
#define AIC3204_ADC5_VOLSS (3 << 0)
/* ADC Fine Gain Adjust Register */
#define AIC3204_ADCFINE AIC3204_PGREG(0, 82)
/* Left ADC Mute */
#define AIC3204_ADCFINE_LEFT_MUTE (1 << 7)
/* Left ADC Gain: 0dB */
#define AIC3204_ADCFINE_LEFT_0DB (0 << 4)
/* Left ADC Gain: -0.1dB */
#define AIC3204_ADCFINE_LEFT_0DB1 (1 << 4)
/* Left ADC Gain: -0.2dB */
#define AIC3204_ADCFINE_LEFT_0DB2 (2 << 4)
/* Left ADC Gain: -0.3dB */
#define AIC3204_ADCFINE_LEFT_0DB3 (2 << 4)
/* Left ADC Gain: -0.4dB */
#define AIC3204_ADCFINE_LEFT_0DB4 (3 << 4)
/* Left ADC Gain Mask */
#define AIC3204_ADCFINE_LEFT (7 << 4)
/* Right ADC Mute */
#define AIC3204_ADCFINE_RIGHT_MUTE (1 << 3)
/* Right ADC Gain: 0dB */
#define AIC3204_ADCFINE_RIGHT_0DB (0 << 0)
/* Right ADC Gain: -0.1dB */
#define AIC3204_ADCFINE_RIGHT_0DB1 (1 << 0)
/* Right ADC Gain: -0.2dB */
#define AIC3204_ADCFINE_RIGHT_0DB2 (2 << 0)
/* Right ADC Gain: -0.3dB */
#define AIC3204_ADCFINE_RIGHT_0DB3 (2 << 0)
/* Right ADC Gain: -0.4dB */
#define AIC3204_ADCFINE_RIGHT_0DB4 (3 << 0)
/* Right ADC Gain Mask */
#define AIC3204_ADCFINE_RIGHT (7 << 0)
/* Left ADC Channel Volume Register */
#define AIC3204_LADCVOL AIC3204_PGREG(0, 83)
/* Left ADC Channel Volume Mask */
#define AIC3204_LADCVOL_MASK 0x7f
/* Right ADC Channel Volume Register */
#define AIC3204_RADCVOL AIC3204_PGREG(0, 84)
/* Right ADC Channel Volume Mask */
#define AIC3204_RADCVOL_MASK 0x7f
/* ADC Phase Adjust Register */
#define AIC3204_ADCPHASE AIC3204_PGREG(0, 85)
/* Left Channel AGC Control Register 1 */
#define AIC3204_LAGC1 AIC3204_PGREG(0, 86)
/* Left Channel AGC Enabled */
#define AIC3204_LAGC1_ENABLE (1 << 7)
/* Left Channel Target Level: -5.5dBFS */
#define AIC3204_LAGC1_LEVEL_5DBFS5 (0 << 4)
/* Left Channel Target Level: -8.0dBFS */
#define AIC3204_LAGC1_LEVEL_8DBFS (1 << 4)
/* Left Channel Target Level: -10dBFS */
#define AIC3204_LAGC1_LEVEL_10DBFS (2 << 4)
/* Left Channel Target Level: -12dBFS */
#define AIC3204_LAGC1_LEVEL_12DBFS (3 << 4)
/* Left Channel Target Level: -14dBFS */
#define AIC3204_LAGC1_LEVEL_14DBFS (4 << 4)
/* Left Channel Target Level: -17dBFS */
#define AIC3204_LAGC1_LEVEL_17DBFS (5 << 4)
/* Left Channel Target Level: -20dBFS */
#define AIC3204_LAGC1_LEVEL_20DBFS (6 << 4)
/* Left Channel Target Level: -24dBFS */
#define AIC3204_LAGC1_LEVEL_24DBFS (7 << 4)
/* Left Channel Target Level Mask */
#define AIC3204_LAGC1_LEVEL (7 << 4)
/* Left Channel Gain Hysteresis: Disabled */
#define AIC3204_LAGC1_GAINHYST_DISABLED (0 << 0)
/* Left Channel Gain Hysteresis: 0.5dB */
#define AIC3204_LAGC1_GAINHYST_0DB5 (1 << 0)
/* Left Channel Gain Hysteresis: 1dB */
#define AIC3204_LAGC1_GAINHYST_1DB (2 << 0)
/* Left Channel Gain Hysteresis: 1.5dB */
#define AIC3204_LAGC1_GAINHYST_1DB5 (3 << 0)
/* Left Channel Gain Hysteresis Mask */
#define AIC3204_LAGC1_GAINHYST (3 << 0)
/* Left Channel AGC Control Register 2 */
#define AIC3204_LAGC2 AIC3204_PGREG(0, 87)
/* Left Channel AGC Hysteresis: 1dB */
#define AIC3204_LAGC2_HYST_1DB (0 << 6)
/* Left Channel AGC Hysteresis: 2dB */
#define AIC3204_LAGC2_HYST_2DB (1 << 6)
/* Left Channel AGC Hysteresis: 4dB */
#define AIC3204_LAGC2_HYST_4DB (2 << 6)
/* Left Channel AGC Hysteresis: Disabled */
#define AIC3204_LAGC2_HYST_DISABLED (3 << 6)
/* Left Channel AGC Hysteresis Mask */
#define AIC3204_LAGC2_HYST (3 << 6)
/* Left Channel AGC Noise Gate Threshold Mask */
#define AIC3204_LAGC2_NGATE (0x1f)
/* Left Channel AGC Noise Gate Threshold Shift */
#define AIC3204_LAGC2_NGATE_SHIFT 1
/* TODO: Remaining AGC registers, DC measurement registers */
/* Power Configuration Register */
#define AIC3204_POWER AIC3204_PGREG(1, 1)
/* Disable AVDD -> DVDD Link */
#define AIC3204_POWER_AVDD_DVDD_UNLINK (1 << 3)
/* LDO Control Register */
#define AIC3204_LDO AIC3204_PGREG(1, 2)
/* DVDD LDO: 1.72V */
#define AIC3204_LDO_DVDD_1V72 (0 << 6)
/* DVDD LDO: 1.67V */
#define AIC3204_LDO_DVDD_1V67 (1 << 6)
/* DVDD LDO: 1.77V */
#define AIC3204_LDO_DVDD_1V77 (2 << 6)
/* DVDD LDO Mask */
#define AIC3204_LDO_DVDD (3 << 6)
/* AVDD LDO: 1.72V */
#define AIC3204_LDO_AVDD_1V72 (0 << 4)
/* AVDD LDO: 1.67V */
#define AIC3204_LDO_AVDD_1V67 (1 << 4)
/* AVDD LDO: 1.77V */
#define AIC3204_LDO_AVDD_1V77 (2 << 4)
/* AVDD LDO Mask */
#define AIC3204_LDO_AVDD (3 << 4)
/* Analog Blocks Enabled */
#define AIC3204_LDO_ANALOG_ENABLED (0 << 3)
/* Analog Blocks Disabled */
#define AIC3204_LDO_ANALOG_DISABLED (1 << 3)
/* Analog Block Status */
#define AIC3204_LDO_ANALOG (1 << 3)
/* DVDD Over Current Detect */
#define AIC3204_LDO_DVDD_OVERCURRENT (1 << 2)
/* AVDD Over Current Detect */
#define AIC3204_LDO_AVDD_OVERCURRENT (1 << 1)
/* AVDD Powered Up */
#define AIC3204_LDO_AVDD_UP (1 << 0)
/* TODO: Playback Configuration Registers */
/* Output Driver Power Control Register */
#define AIC3204_OUTDRV AIC3204_PGREG(1, 9)
/* Left Headphone Driver Up */
#define AIC3204_OUTDRV_HPL_UP_SHIFT (5)
/* Right Headphone Driver Up */
#define AIC3204_OUTDRV_HPR_UP_SHIFT (4)
/* Left Line Output Driver Up */
#define AIC3204_OUTDRV_LOL_UP_SHIFT (3)
/* Right Line Output Driver Up */
#define AIC3204_OUTDRV_LOR_UP_SHIFT (2)
/* Left Mixer Amplifier Up */
#define AIC3204_OUTDRV_MAL_UP_SHIFT (1)
/* Right Mixer Amplifier Up */
#define AIC3204_OUTDRV_MAR_UP_SHIFT (0)
/* Common Mode Control Register */
#define AIC3204_CMCTL AIC3204_PGREG(1, 10)
/* Full Chip Common Mode = 0.9V */
#define AIC3204_CMCTL_FCCM_0V9 (0 << 6)
/* Full Chip Common Mode = 0.75V */
#define AIC3204_CMCTL_FCCM_0V75 (1 << 6)
/* Full Chip Common Mode Mask */
#define AIC3204_CMCTL_FCCM (1 << 6)
/* Headphone Output Common Mode = Full Chip Common Mode */
#define AIC3204_CMCTL_HPCM_FCCM (0 << 4)
/* Headphone Output Common Mode = 1.25V */
#define AIC3204_CMCTL_HPCM_1V25 (1 << 4)
/* Headphone Output Common Mode = 1.5V */
#define AIC3204_CMCTL_HPCM_1V5 (2 << 4)
/* Headphone Output Common Mode = 1.65V */
#define AIC3204_CMCTL_HPCM_1V65 (3 << 4)
/* Headphone Output Common Mode Mask */
#define AIC3204_CMCTL_HPCM (3 << 4)
/* Line Output Common Mode = Full Chip Common Mode */
#define AIC3204_CMCTL_LOCM_FCCM (0 << 3)
/* Line Output Common Mode = 1.65V from LDOin */
#define AIC3204_CMCTL_LOCM_1V65LDOIN (1 << 3)
/* Line Output Common Mode Mask */
#define AIC3204_CMCTL_LOCM (1 << 3)
/* Headphone Output Common Mode Source = AVDD */
#define AIC3204_CMCTL_HPCMSRC_AVDD (0 << 1)
/* Headphone Output Common Mode Source = LDOin */
#define AIC3204_CMCTL_HPCMSRC_LDOIN (1 << 1)
/* Headphone Output Common Mode Source Mask */
#define AIC3204_CMCTL_HPCMSRC (1 << 1)
/* LDO Input Range = 1.5~1.95V */
#define AIC3204_CMCTL_LDORANGE_1V5_1V95 (0 << 0)
/* LDO Input Range = 1.8~3.6V */
#define AIC3204_CMCTL_LDORANGE_1V8_3V6 (1 << 0)
/* LDO Input Range Mask (effective when HPCMSRC = LDOIN) */
#define AIC3204_CMCTL_LDORANGE (1 << 0)
/* Over Current Protection Configuration Register TODO */
#define AIC3204_OCP AIC3204_PGREG(1, 11)
/* Left Headphone Driver Routing Register */
#define AIC3204_HPLROUTE AIC3204_PGREG(1, 12)
/* Left Headphone Driver connects to LDAC+ */
#define AIC3204_HPLROUTE_LDACP_SHIFT (3)
/* Left Headphone Driver connects to IN1L */
#define AIC3204_HPLROUTE_IN1L_SHIFT (2)
/* Left Headphone Driver connects to MAL */
#define AIC3204_HPLROUTE_MAL_SHIFT (1)
/* Left Headphone Driver connects to MAR */
#define AIC3204_HPLROUTE_MAR_SHIFT (0)
/* Right Headphone Driver Routing Register */
#define AIC3204_HPRROUTE AIC3204_PGREG(1, 13)
/* Right Headphone Driver connects to LDAC- */
#define AIC3204_HPRROUTE_LDACN_SHIFT (4)
/* Right Headphone Driver connects to RDAC+ */
#define AIC3204_HPRROUTE_RDACP_SHIFT (3)
/* Right Headphone Driver connects to IN1L */
#define AIC3204_HPRROUTE_IN1R_SHIFT (2)
/* Right Headphone Driver connects to MAL */
#define AIC3204_HPRROUTE_MAR_SHIFT (1)
/* Right Headphone Driver connects to HPL */
#define AIC3204_HPRROUTE_HPL_SHIFT (0)
/* Left Line Output Routing Register */
#define AIC3204_LOLROUTE AIC3204_PGREG(1, 14)
/* Left Line Output Driver connects to RDAC- */
#define AIC3204_LOLROUTE_RDACN_SHIFT (4)
/* Left Line Output Driver connects to LDAC+ */
#define AIC3204_LOLROUTE_LDACP_SHIFT (3)
/* Left Line Output Driver connects to MAL */
#define AIC3204_LOLROUTE_MAL_SHIFT (1)
/* Left Line Output Driver connects to LOR */
#define AIC3204_LOLROUTE_LOR_SHIFT (0)
/* Right Line Output Routing Register */
#define AIC3204_LORROUTE AIC3204_PGREG(1, 15)
/* Right Line Output Driver connects to RDAC+ */
#define AIC3204_LORROUTE_RDACP_SHIFT (3)
/* Right Line Output Driver connects to MAR */
#define AIC3204_LORROUTE_MAR_SHIFT (1)
/* Left Headphone Gain Setting Register */
#define AIC3204_HPLGAIN AIC3204_PGREG(1, 16)
/* Left Headphone Mute */
#define AIC3204_HPLGAIN_MUTE_SHIFT (6)
/* Left Headphone Gain Mask */
#define AIC3204_HPLGAIN_MASK (0x3f)
/* Right Headphone Gain Setting Register */
#define AIC3204_HPRGAIN AIC3204_PGREG(1, 17)
/* Right Headphone Mute */
#define AIC3204_HPRGAIN_MUTE_SHIFT (6)
/* Right Headphone Gain Mask */
#define AIC3204_HPRGAIN_MASK (0x3f)
/* Left Line Output Gain Setting Register */
#define AIC3204_LOLGAIN AIC3204_PGREG(1, 18)
/* Left Line Output Mute */
#define AIC3204_LOLGAIN_MUTE_SHIFT (6)
/* Left Line Output Gain Mask */
#define AIC3204_LOLGAIN_MASK (0x3f)
/* Right Line Output Gain Setting Register */
#define AIC3204_LORGAIN AIC3204_PGREG(1, 19)
/* Right Line Output Mute */
#define AIC3204_LORGAIN_MUTE_SHIFT (6)
/* Right Line Output Gain Mask */
#define AIC3204_LORGAIN_MASK (0x3f)
/* Left Mixer Amplifier Gain Setting Register */
#define AIC3204_MALGAIN AIC3204_PGREG(1, 24)
/* Left Mixer Amplifier Gain Mask */
#define AIC3204_MALGAIN_MASK (0x3f)
/* Right Mixer Amplifier Gain Setting Register */
#define AIC3204_MARGAIN AIC3204_PGREG(1, 25)
/* Right Mixer Amplifier Gain Mask */
#define AIC3204_MARGAIN_MASK (0x3f)
#define AIC3204_MICBIAS AIC3204_PGREG(1, 51)
#define AIC3204_LMICPGAPROUTE AIC3204_PGREG(1, 52)
#define AIC3204_LMICPGANROUTE AIC3204_PGREG(1, 54)
#define AIC3204_RMICPGAPROUTE AIC3204_PGREG(1, 55)
#define AIC3204_RMICPGANROUTE AIC3204_PGREG(1, 57)
#define AIC3204_LMICPGAVOL AIC3204_PGREG(1, 59)
#define AIC3204_RMICPGAVOL AIC3204_PGREG(1, 60)
/* ADC Power Tune Mode Register */
#define AIC3204_ADCPTM AIC3204_PGREG(1, 61)
/* ADC Power Tune Mode: PTM_R4 */
#define AIC3204_ADCPTM_R4 (0x00)
/* ADC Power Tune Mode: PTM_R3 */
#define AIC3204_ADCPTM_R3 (0x64)
/* ADC Power Tune Mode: PTM_R2 */
#define AIC3204_ADCPTM_R2 (0xb6)
/* ADC Power Tune Mode: PTM_R1 */
#define AIC3204_ADCPTM_R1 (0xff)
/* Analogue Input Quick Charging Configuration Register */
#define AIC3204_AIQC AIC3204_PGREG(1, 71)
/* Power up time: Default Value (whatever that is) */
#define AIC3204_AIQC_PWRUP_DEFAULT (0x00)
/* Power up time: 3.1ms */
#define AIC3204_AIQC_PWRUP_3M1S (0x31)
/* Power up time: 6.4ms */
#define AIC3204_AIQC_PWRUP_6M4S (0x32)
/* Power up time: 1.6ms */
#define AIC3204_AIQC_PWRUP_1M6S (0x33)
/* Power up time mask */
#define AIC3204_AIQC_PWRUP (0x3f)
/* Reference Power-Up Configuration Register */
#define AIC3204_REFPU AIC3204_PGREG(1, 123)
/* Reference Power-Up delay: Slow */
#define AIC3204_REFPU_DELAY_SLOW (0 << 0)
/* Reference Power-Up delay: 40ms */
#define AIC3204_REFPU_DELAY_40MS (1 << 0)
/* Reference Power-Up delay: 80ms */
#define AIC3204_REFPU_DELAY_80MS (2 << 0)
/* Reference Power-Up delay: 120ms */
#define AIC3204_REFPU_DELAY_120MS (3 << 0)
/* Force power-up */
#define AIC3204_REFPU_FORCE (1 << 2)
/* TODO: Define the remaining registers ... this will do for now */
/*
* Since the AIC3204 has a great variety of settings to configure, rather than
* provide individual module parameters for each of them, instead the machine
* driver may pass in a const array of structs like this.
*
* This script of register modifications is run at initialisation, and is
* intended to configure the AIC3204 for use with the particular hardware
* surrounding it, e.g. GPIOs, filter coefficients, etc.
*/
struct aic3204_init_reg {
/*
* The register to manipulate. This register address contains the
* page number in the upper 8 bits -- use the AIC3204_PGREG macro
* to generate such a number.
*/
u16 reg;
/*
* Flags: See the flags below; but this basically allows for the
* value field to be ANDed or ORed with the register value, rather
* than setting it explicitly.
*/
u8 flags;
/*
* The value to use in the operation
*/
u8 value;
};
/* Set Register = Value */
#define AIC3204_INITFLAG_SET (0 << 0)
/* Set Register = Register & Value */
#define AIC3204_INITFLAG_AND (1 << 0)
/* Set Register = Register | Value */
#define AIC3204_INITFLAG_OR (2 << 0)
/* Set Register = Register ^ Value */
#define AIC3204_INITFLAG_XOR (3 << 0)
/* Fail on Register != Value */
#define AIC3204_INITFLAG_FAILNEQ (4 << 0)
/* Fail on Register == Value */
#define AIC3204_INITFLAG_FAILEQ (5 << 0)
/* Fail on (Register & Value) == 0 */
#define AIC3204_INITFLAG_FAILNSET (6 << 0)
/* Fail on (Register & Value) != 0 */
#define AIC3204_INITFLAG_FAILSET (7 << 0)
/* Fail on (Register | Value) == 0xff */
#define AIC3204_INITFLAG_FAILCLR (8 << 0)
/* Fail on (Register | Value) != 0xff */
#define AIC3204_INITFLAG_FAILNCLR (9 << 0)
/* Register operation mask */
#define AIC3204_INITFLAG_OPMASK (15 << 0)
struct aic3204_setup_data {
/* List of initialisation settings to perform at startup */
const struct aic3204_init_reg *init_reg_list;
/* Length of initialisation register list */
int init_reg_length;
/* ADC/DAC channel mask */
u8 channels;
};
#define AIC3204_LDAC_EN (1 << 3)
#define AIC3204_RDAC_EN (1 << 2)
#define AIC3204_LADC_EN (1 << 1)
#define AIC3204_RADC_EN (1 << 0)
extern struct snd_soc_dai aic3204_dai;
extern struct snd_soc_codec_device soc_codec_dev_aic3204;
#endif /* _AIC3204_H */
[-- Attachment #3: Type: text/plain, Size: 160 bytes --]
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 3/3]
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
2010-06-15 0:33 ` Update on TLV320AIC3204 Driver [File 2/3] Stuart Longland
@ 2010-06-15 0:34 ` Stuart Longland
2010-06-15 14:20 ` Update on TLV320AIC3204 Driver [File 1/3] Mark Brown
2 siblings, 0 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 0:34 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
[-- Attachment #1: Type: text/plain, Size: 317 bytes --]
Attached :- include/sound/tlv320aic3204.h
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
[-- Attachment #2: tlv320aic3204.h --]
[-- Type: text/x-c, Size: 565 bytes --]
/*
* Platform data for Texas Instruments TLV320AIC3204 codec
*
* Copyright (C) 2010 Jacques Electronics Pty Ltd
* Author: Stuart Longland <redhatter@gentoo.org>
*
* Based on tlv320aic3x.h:
* Author: Jarkko Nikula <jhnikula@gmail.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 __TLV320AIC3204_H__
#define __TLV320AIC3204_H__
struct aic3204_pdata {
int gpio_reset; /* < 0 if not used */
};
#endif
[-- Attachment #3: Type: text/plain, Size: 160 bytes --]
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 0:27 ` Stuart Longland
@ 2010-06-15 5:11 ` Stuart Longland
2010-06-15 5:23 ` Stuart Longland
` (2 more replies)
2010-06-15 7:21 ` Update on TLV320AIC3204 Driver Mark Brown
1 sibling, 3 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 5:11 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 10:27:34AM +1000, Stuart Longland wrote:
> On Fri, Jun 11, 2010 at 11:18:04AM +0100, Mark Brown wrote:
> > On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
> >
> > > (1) When using the SOC_DOUBLE_R_SX_TLV controls, I notice there's an
> > > odd interaction between the mute switch associated with the
> > > control, and its corresponding gain setting.
> >
> > > Nothing changes in the actual registers (except the mute bit of
> > > course) but the displayed gain shoots up to infinity if the mute
> > > is toggled when the gain associated with that mute control is
> > > set below 0dB. When the gain is at 0dB or above, toggling the
> > > mute has no effect on the displayed gain setting.
> >
> > This is almost always due to having an overlap between the bitfield for
> > the volume and the mute bit.
>
> I'll have a closer look, perhaps there's some misunderstanding on my
> part as to how the macros work.
Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
are implemented. I couldn't find where in the git trees the control had
been added, I wound up applying this patch myself in my tree... it
apparently got applied in the official trees, but I cannot find it.
<http://permalink.gmane.org/gmane.linux.alsa.devel/72893>
Hopefully below is telling everyone what we already know... and I'm just
checking that I understand this code properly. If I'm misunderstanding
something, please let me know.
So the macro is defined as follows (in include/sound.soc.h):
#define SOC_DOUBLE_R_SX_TLV(xname, xreg_left, xreg_right, xshift,\
xmin, xmax, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw_2r_sx, \
.get = snd_soc_get_volsw_2r_sx, \
.put = snd_soc_put_volsw_2r_sx, \
.private_value = (unsigned long)&(struct soc_mixer_control) \
{.reg = xreg_left, \
.rreg = xreg_right, .shift = xshift, \
.min = xmin, .max = xmax} }
When a read is performed, this structure points to this function as the
means for reading the register (defined in sound/soc-core.c):
/**
* snd_soc_get_volsw_2r_sx - double with tlv and variable data size
* mixer get callback
* @kcontrol: mixer control
* @uinfo: control element information
*
* Returns 0 for success.
*/
int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int mask = (1<<mc->shift)-1;
int min = mc->min;
int val = snd_soc_read(codec, mc->reg) & mask;
int valr = snd_soc_read(codec, mc->rreg) & mask;
ucontrol->value.integer.value[0] = ((val & 0xff)-min);
ucontrol->value.integer.value[1] = ((valr & 0xff)-min);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
Now for my data, almost all the driver gain registers have the
following format:
Bit 7 6 5 4 3 2 1 0
| | \ /
Resvd Mute '------Signed Gain setting in dB------'
Therefore; I want the 'mask' variable in the above function to select
that bottom 6 bits; a hex value of 0x3f... So working backwards to
derive mc->shift...
mask = (1 << mc->shift) - 1 = 0011 1111b
1 << mc->shift = 0100 0000b
mc->shift = 6
Thus, the macro needs to specify xshift=6. As for the other settings...
my gain range is -6dB through to 29dB, so those specify the xmin and
xmax as being -6 and 29 respectively.
As for the mute; we define this using the SOC_DOUBLE_R macro; bit 6 is
the mute bit as we can see above. It's also inverted; turning it on,
turns the output driver off... and it's a single-bit value.
I therefore define the line output driver volume, and its corresponding
mute switch as follows:
/* Left Line Output Gain Setting Register */
#define AIC3204_LOLGAIN AIC3204_PGREG(1, 18)
/* Left Line Output Mute */
#define AIC3204_LOLGAIN_MUTE_SHIFT (6)
/* Left Line Output Gain Mask */
#define AIC3204_LOLGAIN_MASK (0x3f)
/* Right Line Output Gain Setting Register */
#define AIC3204_LORGAIN AIC3204_PGREG(1, 19)
/* Right Line Output Mute */
#define AIC3204_LORGAIN_MUTE_SHIFT (6)
/* Right Line Output Gain Mask */
#define AIC3204_LORGAIN_MASK (0x3f)
...
SOC_DOUBLE_R_SX_TLV("Line Output Playback Volume",
AIC3204_LOLGAIN, AIC3204_LORGAIN,
6, -6, 29, output_stage_tlv),
SOC_DOUBLE_R("Line Output Playback Switch", AIC3204_LOLGAIN,
AIC3204_LORGAIN, AIC3204_LOLGAIN_MUTE_SHIFT,
0x01, 1),
Now, that *mostly* works, except when the gain is set below 0dB, then
strange things occur. Did I miss something or is the bug elsewhere?
Regards,
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 5:11 ` Stuart Longland
@ 2010-06-15 5:23 ` Stuart Longland
2010-06-15 9:00 ` Mark Brown
2010-06-15 13:42 ` Peter Ujfalusi
2010-06-18 2:38 ` SOC_DOUBLE_R_SX_TLV (was Update on TLV320AIC3204 Driver) Stuart Longland
2 siblings, 1 reply; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 5:23 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 03:11:10PM +1000, Stuart Longland wrote:
> On Tue, Jun 15, 2010 at 10:27:34AM +1000, Stuart Longland wrote:
> > On Fri, Jun 11, 2010 at 11:18:04AM +0100, Mark Brown wrote:
> > > On Fri, Jun 11, 2010 at 03:55:12PM +1000, Stuart Longland wrote:
> > >
> > > > (1) When using the SOC_DOUBLE_R_SX_TLV controls, I notice there's an
> > > > odd interaction between the mute switch associated with the
> > > > control, and its corresponding gain setting.
> > >
> > > > Nothing changes in the actual registers (except the mute bit of
> > > > course) but the displayed gain shoots up to infinity if the mute
> > > > is toggled when the gain associated with that mute control is
> > > > set below 0dB. When the gain is at 0dB or above, toggling the
> > > > mute has no effect on the displayed gain setting.
> > >
> > > This is almost always due to having an overlap between the bitfield for
> > > the volume and the mute bit.
> >
> > I'll have a closer look, perhaps there's some misunderstanding on my
> > part as to how the macros work.
>
> Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
> are implemented. I couldn't find where in the git trees the control had
> been added, I wound up applying this patch myself in my tree... it
> apparently got applied in the official trees, but I cannot find it.
Okay... further digging, found it at
<http://git.kernel.org/?p=linux/kernel/git/lrg/asoc-2.6.git;a=patch;h=b6f4bb383d69cac46f17e2305720f9a3d426c5ed>
Looks like what I got is pretty much identical.
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 0:27 ` Stuart Longland
2010-06-15 5:11 ` Stuart Longland
@ 2010-06-15 7:21 ` Mark Brown
1 sibling, 0 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-15 7:21 UTC (permalink / raw)
To: Stuart Longland; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On 15 Jun 2010, at 01:27, Stuart Longland wrote:
> I've started documenting some of this stuff on the ALSA Wiki at
> <http://www.alsa-project.org/main/index.php/DAPM>. I'm hoping this
> might be a useful resource for others. Was about to do it on an
> intranet wiki here, but figured it'd be of benefit to others too.
If you're doing this it would be a bit nicer if you could contribute the
documentation to the kernel. The APIs do change over time and the
in tree documentation has a much better chance of being in sync with
what people are actually working with than online documentation
(which may be for a later kernel version with features they can't use),
though it's not been so great recently.
>> SND_SOC_DAPM_SUPPLY can help you here - you can have a conditional
>> supply.
> Is there an example of the supply doing something like this? I'm
> thinking a mux might be the way to represent it, but it's then a matter
> of getting DAPM to switch the MUX over to the other source when the
> current source gets shut down.
WM8994 does this for the AIF clocks. You can't use a mux since you're not
routing an audio path.
>> Provide a display_register() callback which filters out the undefined
>> registers or otherwise improve the standard functionality.
> I shall look into this... but a thought, is there any merrit in the
> two-file (register select" and "register data") interface to the CODEC
> registers? The current interface looks great if you want to quickly get
> a register dump, but the solution I have is easily used in scripts.
It's fairly easy to use the existing solution in scripts with a little bit of
filtering (even grep can do a good job) and I have to say I'm concerned
about the idea of production use of this stuff. It's supposed to give the
impression that you're not meant to be using it in production for anything
except pulling diagnostics out.
>>
>> If the registers are mostly clustered at the starts of pages then an
>> array of pointers to per-page arrays might do the trick.
>
> As in this?
>
> static u8 aic3204_reg_page0[128];
Yes.
> I wasn't sure how compatible that would be with the rest of ALSA, but I
> might give that a try shortly.
The rest of ALSA shouldn't be able to tell what you're doing, all the
operations which interact with the register cache are provided by your
driver. If it can see what's going on we've got a problem anyway.
>> Please post to the mailing list if you want review - the standard
>> workflow is to provide comments by replying to the patch.
> No problems, I'll post the code to the list in a moment. Last time I
> tried this it got held for moderation because the email was too big.
> Hence my posting it to a website and linking to it. I'll post the files
> up in my next email. When things are a little more mature, I'll post
> the patch for the driver.
This is one of the things that the requirement to CC the maintainers
and other relevant people helps with - it means that they get a copy
as soon as their mail server takes the message rather than having
to wait for the list.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 5:23 ` Stuart Longland
@ 2010-06-15 9:00 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-15 9:00 UTC (permalink / raw)
To: Stuart Longland; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 03:23:51PM +1000, Stuart Longland wrote:
> On Tue, Jun 15, 2010 at 03:11:10PM +1000, Stuart Longland wrote:
> > Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
> > are implemented. I couldn't find where in the git trees the control had
> > been added, I wound up applying this patch myself in my tree... it
> > apparently got applied in the official trees, but I cannot find it.
> Okay... further digging, found it at
> <http://git.kernel.org/?p=linux/kernel/git/lrg/asoc-2.6.git;a=patch;h=b6f4bb383d69cac46f17e2305720f9a3d426c5ed>
> Looks like what I got is pretty much identical.
As documented in MAINTAINERS ASoC git for ASoC is:
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
which gets fed into ALSA git:
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 5:11 ` Stuart Longland
2010-06-15 5:23 ` Stuart Longland
@ 2010-06-15 13:42 ` Peter Ujfalusi
2010-06-15 13:53 ` Mark Brown
2010-06-15 22:59 ` Stuart Longland
2010-06-18 2:38 ` SOC_DOUBLE_R_SX_TLV (was Update on TLV320AIC3204 Driver) Stuart Longland
2 siblings, 2 replies; 22+ messages in thread
From: Peter Ujfalusi @ 2010-06-15 13:42 UTC (permalink / raw)
To: alsa-devel, Eric B??nard; +Cc: Liam Girdwood, ext Stuart Longland, Mark Brown
Hi,
On Tuesday 15 June 2010 08:11:10 ext Stuart Longland wrote:
> Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
> are implemented. I couldn't find where in the git trees the control had
> been added, I wound up applying this patch myself in my tree... it
> apparently got applied in the official trees, but I cannot find it.
I'm not really sure what the SOC_DOUBLE_R_SX_TLV is for...
But would a simple SOC_DOUBLE_R_TLV enough for the aic3204 codec?
I have not looked at the datasheet (if it is available), but I'll try to find
it.
Well looking at the _2r_sx functions in soc-core.c does not really clears the
intention, but at least I can see several things in the snd_soc_put_volsw_2r_sx
function, which can cause problems:
- in error case it returns 0,
- instead of updating the bits, it rewrites the register, which I think will
erase bits, which it should not touch.
- it has the ret variable, but it is not really used, and when it got assigned,
than it gets 1 in the success case. But luckily the return at the end of the
function is not using the ret, but 0.
- I'm not really sure if the 0xff masking is needed at all in the _sx functions,
since the values are masked with the mask anyways.
So I do not really follow what these suppose to do... Can someone give an
example of HW register, and the SX combination with the tlv struct?
--
Péter
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 13:42 ` Peter Ujfalusi
@ 2010-06-15 13:53 ` Mark Brown
2010-06-16 5:33 ` Peter Ujfalusi
2010-06-15 22:59 ` Stuart Longland
1 sibling, 1 reply; 22+ messages in thread
From: Mark Brown @ 2010-06-15 13:53 UTC (permalink / raw)
To: Peter Ujfalusi
Cc: alsa-devel, ext Stuart Longland, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 04:42:32PM +0300, Peter Ujfalusi wrote:
> On Tuesday 15 June 2010 08:11:10 ext Stuart Longland wrote:
> > Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
> > are implemented. I couldn't find where in the git trees the control had
> > been added, I wound up applying this patch myself in my tree... it
> > apparently got applied in the official trees, but I cannot find it.
> I'm not really sure what the SOC_DOUBLE_R_SX_TLV is for...
It's for value ranges which have normal unsigned integer mapping but
where the zero point is not at the lowest value so you need to wrap
around. Which isn't actually what Stuart is looking for...
> But would a simple SOC_DOUBLE_R_TLV enough for the aic3204 codec?
> I have not looked at the datasheet (if it is available), but I'll try to find
> it.
> Well looking at the _2r_sx functions in soc-core.c does not really clears the
> intention, but at least I can see several things in the snd_soc_put_volsw_2r_sx
...and this won't work since Stuart is looking for something that does
signed values which I don't think we have already, the standard stuff
all wants unsigned quantities.
> function, which can cause problems:
> - in error case it returns 0,
Yup, will fix that just now.
> - instead of updating the bits, it rewrites the register, which I think will
> erase bits, which it should not touch.
It's just open coding the update bits so there's no actual problem here.
> - it has the ret variable, but it is not really used, and when it got assigned,
> than it gets 1 in the success case. But luckily the return at the end of the
> function is not using the ret, but 0.
Yeah, ret is a waste of time.
> - I'm not really sure if the 0xff masking is needed at all in the _sx functions,
> since the values are masked with the mask anyways.
It's not really needed due to mask, yes.
> So I do not really follow what these suppose to do... Can someone give an
> example of HW register, and the SX combination with the tlv struct?
The only user is cs42l451.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 1/3]
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
2010-06-15 0:33 ` Update on TLV320AIC3204 Driver [File 2/3] Stuart Longland
2010-06-15 0:34 ` Update on TLV320AIC3204 Driver [File 3/3] Stuart Longland
@ 2010-06-15 14:20 ` Mark Brown
2010-06-15 22:46 ` Stuart Longland
2 siblings, 1 reply; 22+ messages in thread
From: Mark Brown @ 2010-06-15 14:20 UTC (permalink / raw)
To: Stuart Longland; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 10:29:14AM +1000, Stuart Longland wrote:
> > Please post to the mailing list if you want review - the standard
> > workflow is to provide comments by replying to the patch.
> Attached :- sound/soc/codecs/tlv320aic3204.c
Please post patches in the normal fashion, it's really much easier.
This looks mostly good, but a few comments below.
> #ifndef DEBUG
> #define DEBUG
> #endif
Hrm.
> if ( reg ) {
> /* Refresh cache */
scripts/checkpatch.pl is your friend.
> #if 0
> dev_dbg( &codec->dev, "%s: pg %d reg %d[%04x] => %02x\n",
> __func__, reg >> 7, reg & 0xff, reg, value[0] );
> #endif
Can probably just leave dev_dbg() unconditionally, it won't display in
production builds.
> static inline int aic3204_mod( struct snd_soc_codec *codec, unsigned int reg,
> u8 and_mask, u8 or_mask )
snd_soc_update_bits().
> * This is a bit misleading; xshift refers to the bit one bit *past*
> * the most significant bit. Or at least that's what it appears to be
> * doing the math. Mask needs to select the last 6 bits; which is the
> * signed bitfield specifiying the gain in dB.
If that's the case it's buggy and should be fixed.
> static const struct snd_soc_dapm_route intercon[] = {
> /* Data Connections */
> {"Left Data Out", NULL, "Left ADC"},
> {"Right Data Out", NULL, "Right ADC"},
> {"Left DAC", NULL, "Left Data In"},
> {"Right DAC", NULL, "Right Data In"},
> /* Line Output */
Some blanks in this table might not hurt.
> #ifndef ENABLE_PLL
> /* If PLL is disabled; always bypass the PLL */
> bypass_pll = 1;
> #endif
If you've got the PLL working just drop the #defines, there shouldn't be
build time configuration for things like this. If configuration is
needed make it an API call or platform data.
> static int aic3204_set_bias_level(struct snd_soc_codec *codec,
> enum snd_soc_bias_level level)
> {
> #if 0
> switch (level) {
> case SND_SOC_BIAS_ON:
If there's really nothing needed then no need to supply the function.
> /* Reset the CODEC */
> aic3204_write(codec, AIC3204_RESET, AIC3204_RESET_SOFT);
>
> /* Read in the cache */
> for( reg=0; reg < AIC3204_CACHEREGNUM; reg++ ) {
> u8 temp;
> aic3204_read( codec, reg, &temp );
> }
You should be able to have a table of defaults.
> return aic3204_register(codec);
>
> err_enable:
> regulator_bulk_free(ARRAY_SIZE(aic3204->supplies), aic3204->supplies);
The split between the registration function and things like the
regulators is a bit odd here - probably as well to just have one
function unless you have both I2C and SPI options (in which case merge
the GPIO and regulator stuff into register()).
> #else
> static inline void aic3204_i2c_init(void) { }
> static inline void aic3204_i2c_exit(void) { }
> #endif
Not needed unless you have multiple buses.
> if (setup) {
> struct aic3204_priv *aic3204 = snd_soc_codec_get_drvdata(codec);
>
> /* Run setup script; if any */
> if ( setup->init_reg_list ) {
This shouldn't be needed - if it were needed it should be part of the
core.
> /* Copy over channel information */
> aic3204->channels = setup->channels;
Platform data?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 1/3]
2010-06-15 14:20 ` Update on TLV320AIC3204 Driver [File 1/3] Mark Brown
@ 2010-06-15 22:46 ` Stuart Longland
2010-06-16 9:35 ` Mark Brown
0 siblings, 1 reply; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 22:46 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 03:20:50PM +0100, Mark Brown wrote:
> On Tue, Jun 15, 2010 at 10:29:14AM +1000, Stuart Longland wrote:
>
> > > Please post to the mailing list if you want review - the standard
> > > workflow is to provide comments by replying to the patch.
>
> > Attached :- sound/soc/codecs/tlv320aic3204.c
>
> Please post patches in the normal fashion, it's really much easier.
>
> This looks mostly good, but a few comments below.
>
> > #ifndef DEBUG
> > #define DEBUG
> > #endif
>
> Hrm.
Yes, I know, it'll be gone in the patch.
> > if ( reg ) {
> > /* Refresh cache */
>
> scripts/checkpatch.pl is your friend.
I take it this checks for coding style? I'll have a look.
> > #if 0
> > dev_dbg( &codec->dev, "%s: pg %d reg %d[%04x] => %02x\n",
> > __func__, reg >> 7, reg & 0xff, reg, value[0] );
> > #endif
>
> Can probably just leave dev_dbg() unconditionally, it won't display in
> production builds.
Yep... at the moment I've got a couple of #ifdefs around that statement
now; basically so that certain types of debug message can be enabled.
In the initial phase when I was reading every register into cache,
having this uncommented lead to a _lot_ of data, but it was handy in the
beginning to check things were working as I expected.
I'll get rid of those now.
> > static inline int aic3204_mod( struct snd_soc_codec *codec, unsigned int reg,
> > u8 and_mask, u8 or_mask )
>
> snd_soc_update_bits().
Yep, I've done that in the latest revision.
> > * This is a bit misleading; xshift refers to the bit one bit *past*
> > * the most significant bit. Or at least that's what it appears to be
> > * doing the math. Mask needs to select the last 6 bits; which is the
> > * signed bitfield specifiying the gain in dB.
>
> If that's the case it's buggy and should be fixed.
By the sounds of the comments made elsewhere in this thread, it could be
a case of round pegs in square holes. My analysis of the functions
behaviour lead me to the above comment.
The dB scale comes out correct, but there is the funny overlap between
mute and volume level as per my earlier posts.
> > static const struct snd_soc_dapm_route intercon[] = {
> > /* Data Connections */
> > {"Left Data Out", NULL, "Left ADC"},
> > {"Right Data Out", NULL, "Right ADC"},
> > {"Left DAC", NULL, "Left Data In"},
> > {"Right DAC", NULL, "Right Data In"},
> > /* Line Output */
>
> Some blanks in this table might not hurt.
All noted.
> > #ifndef ENABLE_PLL
> > /* If PLL is disabled; always bypass the PLL */
> > bypass_pll = 1;
> > #endif
>
> If you've got the PLL working just drop the #defines, there shouldn't be
> build time configuration for things like this. If configuration is
> needed make it an API call or platform data.
Indeed... it was used before I got the PLL working; and the PLL has been
working well lately, so I may just ditch those #ifdefs and make it the
default.
> > static int aic3204_set_bias_level(struct snd_soc_codec *codec,
> > enum snd_soc_bias_level level)
> > {
> > #if 0
> > switch (level) {
> > case SND_SOC_BIAS_ON:
>
> If there's really nothing needed then no need to supply the function.
Yep, will get rid of it then. I wasn't quite sure what it was doing, or
what was needed. I'm undecided as to whether I should use this to power
the PLL down, or whether that'd be better done elsewhere.
I'm giving some thought to handling the bit and word clocks -- and think
since this is related, we can do something about the PLL in the same
place.
> > /* Reset the CODEC */
> > aic3204_write(codec, AIC3204_RESET, AIC3204_RESET_SOFT);
> >
> > /* Read in the cache */
> > for( reg=0; reg < AIC3204_CACHEREGNUM; reg++ ) {
> > u8 temp;
> > aic3204_read( codec, reg, &temp );
> > }
>
> You should be able to have a table of defaults.
Yep, I've done this in the latest version (although it's a big long
table).
Funny thing, one of the I²C devices on the same bus as the CODEC is a
PSoC-based sensor controller which had a polled-interrupt I²C slave
implementation that would stretch the clock when it was busy doing its
other tasks. This caused the machine to appear to "lock up" at this
point -- later calculations revealed that the effective bitrate would
have meant this step would have taken about 10 minutes. My test board
didn't have one of these devices onboard, hence the problem wasn't
noticed.
I've since used the above ifdef'd dev_dbg statement to capture the data
out of the registers after reset and did some vim voodoo to translate
that into C initialisation data in a static const array.
> > return aic3204_register(codec);
> >
> > err_enable:
> > regulator_bulk_free(ARRAY_SIZE(aic3204->supplies), aic3204->supplies);
>
> The split between the registration function and things like the
> regulators is a bit odd here - probably as well to just have one
> function unless you have both I2C and SPI options (in which case merge
> the GPIO and regulator stuff into register()).
>
> > #else
> > static inline void aic3204_i2c_init(void) { }
> > static inline void aic3204_i2c_exit(void) { }
> > #endif
>
> Not needed unless you have multiple buses.
The TLV320AIC3204 is a dual I²C/SPI device, if you tie one of its pins
to Vdd, it runs I²C, if you tie that pin to Vss, it runs SPI.
> > if (setup) {
> > struct aic3204_priv *aic3204 = snd_soc_codec_get_drvdata(codec);
> >
> > /* Run setup script; if any */
> > if ( setup->init_reg_list ) {
>
> This shouldn't be needed - if it were needed it should be part of the
> core.
> > /* Copy over channel information */
> > aic3204->channels = setup->channels;
>
> Platform data?
This has all now been replaced; in the early days I had it there as a
means of setting up the CODEC, and had thought it'd be a useful way to
set custom parameters like GPIO pin functions, interrupts and filter
settings... but I knew full well that I'd have a difficult job getting
an ugly hack like that accepted into the kernel.
I've now condensed this down to what I need to get things working for
now, and made this into a platform data struct. The channel
configuration is also obsolete now; this is set up through the mixer
interface (in the end it wasn't even being used).
Next time around there'll be a patch on its way, since it now looks as
if the driver is getting to the point where it's in viable shape to be
contributed to the Linux community proper. :-)
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 13:42 ` Peter Ujfalusi
2010-06-15 13:53 ` Mark Brown
@ 2010-06-15 22:59 ` Stuart Longland
1 sibling, 0 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-15 22:59 UTC (permalink / raw)
To: Peter Ujfalusi; +Cc: alsa-devel, Mark Brown, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 04:42:32PM +0300, Peter Ujfalusi wrote:
> Hi,
>
> On Tuesday 15 June 2010 08:11:10 ext Stuart Longland wrote:
> > Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV widgets
> > are implemented. I couldn't find where in the git trees the control had
> > been added, I wound up applying this patch myself in my tree... it
> > apparently got applied in the official trees, but I cannot find it.
>
> I'm not really sure what the SOC_DOUBLE_R_SX_TLV is for...
> But would a simple SOC_DOUBLE_R_TLV enough for the aic3204 codec?
> I have not looked at the datasheet (if it is available), but I'll try to find
> it.
http://focus.ti.com/docs/prod/folders/print/tlv320aic3204.html has the
datasheet.
As I mentioned; the bitfields are signed integers, 6 bits wide in the
case of the example given, there are some that are 7 bits.
In the examples that I gave (driver gain settings), they have a range of
-6dB to 29dB... which corresponds to bitfield values of 0x3a (for -6dB)
and 0x1d (for 29dB).
I did try using the regular TLV macro, and found that the volume
"wrapped" around in a very undesirable fashion. SOC_DOUBLE_R_SX_TLV was
the suggestion made at the time[1]. The way it is now, it works okay
... if you leave the mute untouched.
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
1. http://article.gmane.org/gmane.linux.alsa.devel/73640
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-15 13:53 ` Mark Brown
@ 2010-06-16 5:33 ` Peter Ujfalusi
2010-06-16 14:51 ` Mark Brown
0 siblings, 1 reply; 22+ messages in thread
From: Peter Ujfalusi @ 2010-06-16 5:33 UTC (permalink / raw)
To: alsa-devel
Cc: ext Stuart Longland, ext Mark Brown, Eric B??nard, Liam Girdwood
On Tuesday 15 June 2010 16:53:23 ext Mark Brown wrote:
> On Tue, Jun 15, 2010 at 04:42:32PM +0300, Peter Ujfalusi wrote:
> > On Tuesday 15 June 2010 08:11:10 ext Stuart Longland wrote:
> > > Okay, I've had a close inspection of how the SOC_DOUBLE_R_SX_TLV
> > > widgets are implemented. I couldn't find where in the git trees the
> > > control had been added, I wound up applying this patch myself in my
> > > tree... it apparently got applied in the official trees, but I cannot
> > > find it.
> >
> > I'm not really sure what the SOC_DOUBLE_R_SX_TLV is for...
>
> It's for value ranges which have normal unsigned integer mapping but
> where the zero point is not at the lowest value so you need to wrap
> around. Which isn't actually what Stuart is looking for...
I found the CS42L51 datasheet, and now it is clear what the _SX is doing.
> > But would a simple SOC_DOUBLE_R_TLV enough for the aic3204 codec?
> > I have not looked at the datasheet (if it is available), but I'll try to
> > find it.
> > Well looking at the _2r_sx functions in soc-core.c does not really clears
> > the intention, but at least I can see several things in the
> > snd_soc_put_volsw_2r_sx
>
> ...and this won't work since Stuart is looking for something that does
> signed values which I don't think we have already, the standard stuff
> all wants unsigned quantities.
Yeah, the aic3204 needs signed values.
There is this _s8 things, which does signed, but never used, nor really checked
it.
> > - instead of updating the bits, it rewrites the register, which I think
> > will erase bits, which it should not touch.
>
> It's just open coding the update bits so there's no actual problem here.
It might be so that I have overlooked something, but...
If the register has:
bit # Function
0 .. 6 : Gain
7 : Mute
Let say that the mute is set to one, and the user modifies the gain, than in the
function:
int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int mask = (1<<mc->shift)-1;
/*
* mc->shift is 7, whiche means seven bits for the gain bit array
* mask == 0x7f (b0111 1111): it only covers the gain area
*/
int min = mc->min;
int ret;
unsigned int val, valr, oval, ovalr;
val = ((ucontrol->value.integer.value[0]+min) & 0xff);
val &= mask;
valr = ((ucontrol->value.integer.value[1]+min) & 0xff);
valr &= mask;
/*
* Here we make sure that the coming new value has only bits set withing the
* gain area, so we have b0xxx xxxx for the val, and valr
*/
oval = snd_soc_read(codec, mc->reg) & mask;
ovalr = snd_soc_read(codec, mc->rreg) & mask;
/*
* We read the content of the register, and taking only the bits, which is
* within the gain area: b0xxx xxxx
*/
ret = 0;
if (oval != val) {
/* Only update, if the gain in the chip is different than the new one */
ret = snd_soc_write(codec, mc->reg, val);
/*
* Write the val to the chip.
* Now, if the mute was set before it is going to be cleared, since the code has
* masked it out, the val only contains the updated bits for the gain part, the
* rest is 0.
* So if the user changes the volume, the mute bit is going to be erased.
*/
if (ret < 0)
return 0;
ret = 1;
}
if (ovalr != valr) {
ret = snd_soc_write(codec, mc->rreg, valr);
if (ret < 0)
return 0;
ret = 1;
}
return 0;
}
Have I missed something?
--
Péter
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver [File 1/3]
2010-06-15 22:46 ` Stuart Longland
@ 2010-06-16 9:35 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-16 9:35 UTC (permalink / raw)
To: Stuart Longland; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Wed, Jun 16, 2010 at 08:46:23AM +1000, Stuart Longland wrote:
> On Tue, Jun 15, 2010 at 03:20:50PM +0100, Mark Brown wrote:
> > scripts/checkpatch.pl is your friend.
> I take it this checks for coding style? I'll have a look.
Coding style and other things - anything that's amenable to automatic
checking.
> Yep, will get rid of it then. I wasn't quite sure what it was doing, or
> what was needed. I'm undecided as to whether I should use this to power
> the PLL down, or whether that'd be better done elsewhere.
The PLL can probably be handled as a supply to other components.
> I'm giving some thought to handling the bit and word clocks -- and think
> since this is related, we can do something about the PLL in the same
> place.
If you need to explicitly control them either the hw_params or using a
supply for the DAC/ADC might work.
> > > #else
> > > static inline void aic3204_i2c_init(void) { }
> > > static inline void aic3204_i2c_exit(void) { }
> > > #endif
> > Not needed unless you have multiple buses.
> The TLV320AIC3204 is a dual I²C/SPI device, if you tie one of its pins
> to Vdd, it runs I²C, if you tie that pin to Vss, it runs SPI.
I'd be tempted to skip the ifdefs until SPI is actually implemented
since the driver is useless otherwise.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: Update on TLV320AIC3204 Driver
2010-06-16 5:33 ` Peter Ujfalusi
@ 2010-06-16 14:51 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-16 14:51 UTC (permalink / raw)
To: Peter Ujfalusi
Cc: alsa-devel, ext Stuart Longland, Eric B??nard, Liam Girdwood
On Wed, Jun 16, 2010 at 08:33:12AM +0300, Peter Ujfalusi wrote:
> Have I missed something?
No, it's failing to do the shifts. Looking at the cs42l51 driver it
looks like all the registers on that chip are fully occupied by the
volume controls so it's not an issue for the only current user.
^ permalink raw reply [flat|nested] 22+ messages in thread
* SOC_DOUBLE_R_SX_TLV (was Update on TLV320AIC3204 Driver)
2010-06-15 5:11 ` Stuart Longland
2010-06-15 5:23 ` Stuart Longland
2010-06-15 13:42 ` Peter Ujfalusi
@ 2010-06-18 2:38 ` Stuart Longland
2010-06-18 2:56 ` [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV Stuart Longland
2 siblings, 1 reply; 22+ messages in thread
From: Stuart Longland @ 2010-06-18 2:38 UTC (permalink / raw)
To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, Eric B??nard, Liam Girdwood
On Tue, Jun 15, 2010 at 03:11:10PM +1000, Stuart Longland wrote:
> When a read is performed, this structure points to this function as the
> means for reading the register (defined in sound/soc-core.c):
>
> /**
> * snd_soc_get_volsw_2r_sx - double with tlv and variable data size
> * mixer get callback
> * @kcontrol: mixer control
> * @uinfo: control element information
> *
> * Returns 0 for success.
> */
> int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
> struct snd_ctl_elem_value *ucontrol)
> {
> struct soc_mixer_control *mc =
> (struct soc_mixer_control *)kcontrol->private_value;
> struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> unsigned int mask = (1<<mc->shift)-1;
> int min = mc->min;
> int val = snd_soc_read(codec, mc->reg) & mask;
> int valr = snd_soc_read(codec, mc->rreg) & mask;
>
> ucontrol->value.integer.value[0] = ((val & 0xff)-min);
> ucontrol->value.integer.value[1] = ((valr & 0xff)-min);
> return 0;
> }
> EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
I've figured it out now... and there'll be a patch to fix it.
The mute <--> gain interaction was pure coincidence as it turns out.
There's no interaction between the two, just that when I toggle the
mute, the gain gets re-read, and hence it *appeared* to interact.
Notice the value calculated for the channel is:
output = ( regval & 0xff ) - minimum
In my case, my minimum is -6. Suppose I set my driver gain to -1dB, in
hexadecimal, 0x3f. Since no sign extension is done, (regval & 0xff) =
63. Adding 6 to this (double negation) you get 69... which is greater
than the maximum gain of 29. Hence the scale shoots up to maximum.
The fix, is to change the above so that they mask the result; thus
wrapping around the zero point like so:
output = ( ( regval & 0xff ) - minimum ) & mask
This fixes the problem that I observe, and shouldn't break the other
user of the control. A patch to fix this issue is coming.
--
Stuart Longland (aka Redhatter, VK4MSL) .'''.
Gentoo Linux/MIPS Cobalt and Docs Developer '.'` :
. . . . . . . . . . . . . . . . . . . . . . .'.'
http://dev.gentoo.org/~redhatter :.'
I haven't lost my mind...
...it's backed up on a tape somewhere.
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV
2010-06-18 2:38 ` SOC_DOUBLE_R_SX_TLV (was Update on TLV320AIC3204 Driver) Stuart Longland
@ 2010-06-18 2:56 ` Stuart Longland
2010-06-18 10:29 ` Liam Girdwood
2010-06-19 1:36 ` Mark Brown
0 siblings, 2 replies; 22+ messages in thread
From: Stuart Longland @ 2010-06-18 2:56 UTC (permalink / raw)
Cc: alsa-devel, Takashi Iwai, Mark Brown, Linux Kernel,
Linux ARM Kernel, Liam Girdwood
When SX_TLV widgets are read, if the gain is set to a value below 0dB,
the mixer control is erroniously read as being at maximum volume.
The value read out of the CODEC register is never sign-extended, and
when the minimum value is subtracted (read; added, since the minimum is
negative) the result is a number greater than the maximum allowed value
for the control, and hence it saturates.
Solution: Mask the result so that it "wraps around", emulating
sign-extension.
Signed-off-by: Stuart Longland <redhatter@gentoo.org>
---
sound/soc/soc-core.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index a82a797..0470288 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -2400,8 +2400,8 @@ int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
int val = snd_soc_read(codec, mc->reg) & mask;
int valr = snd_soc_read(codec, mc->rreg) & mask;
- ucontrol->value.integer.value[0] = ((val & 0xff)-min);
- ucontrol->value.integer.value[1] = ((valr & 0xff)-min);
+ ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask;
+ ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
--
1.6.4.4
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV
2010-06-18 2:56 ` [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV Stuart Longland
@ 2010-06-18 10:29 ` Liam Girdwood
2010-06-19 1:36 ` Mark Brown
1 sibling, 0 replies; 22+ messages in thread
From: Liam Girdwood @ 2010-06-18 10:29 UTC (permalink / raw)
To: Stuart Longland
Cc: Takashi Iwai, ALSA Development List, Mark Brown, Linux Kernel,
Linux ARM Kernel
On Fri, 2010-06-18 at 12:56 +1000, Stuart Longland wrote:
> When SX_TLV widgets are read, if the gain is set to a value below 0dB,
> the mixer control is erroniously read as being at maximum volume.
>
> The value read out of the CODEC register is never sign-extended, and
> when the minimum value is subtracted (read; added, since the minimum is
> negative) the result is a number greater than the maximum allowed value
> for the control, and hence it saturates.
>
> Solution: Mask the result so that it "wraps around", emulating
> sign-extension.
>
> Signed-off-by: Stuart Longland <redhatter@gentoo.org>
> ---
> sound/soc/soc-core.c | 4 ++--
> 1 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
> index a82a797..0470288 100644
> --- a/sound/soc/soc-core.c
> +++ b/sound/soc/soc-core.c
> @@ -2400,8 +2400,8 @@ int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
> int val = snd_soc_read(codec, mc->reg) & mask;
> int valr = snd_soc_read(codec, mc->rreg) & mask;
>
> - ucontrol->value.integer.value[0] = ((val & 0xff)-min);
> - ucontrol->value.integer.value[1] = ((valr & 0xff)-min);
> + ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask;
> + ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask;
> return 0;
> }
> EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
--
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV
2010-06-18 2:56 ` [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV Stuart Longland
2010-06-18 10:29 ` Liam Girdwood
@ 2010-06-19 1:36 ` Mark Brown
1 sibling, 0 replies; 22+ messages in thread
From: Mark Brown @ 2010-06-19 1:36 UTC (permalink / raw)
To: Stuart Longland
Cc: Takashi Iwai, ALSA Development List, Linux Kernel,
Linux ARM Kernel, Liam Girdwood
On Fri, Jun 18, 2010 at 12:56:10PM +1000, Stuart Longland wrote:
> When SX_TLV widgets are read, if the gain is set to a value below 0dB,
> the mixer control is erroniously read as being at maximum volume.
Applied, thanks.
BTW, you might want to have a look at the CCs - you probably don't need
to CCing the ARM list for ASoC patches that aren't ARM specific, for
example.
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2010-06-19 1:36 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-06-11 5:55 Update on TLV320AIC3204 Driver Stuart Longland
2010-06-11 10:18 ` Mark Brown
2010-06-15 0:27 ` Stuart Longland
2010-06-15 5:11 ` Stuart Longland
2010-06-15 5:23 ` Stuart Longland
2010-06-15 9:00 ` Mark Brown
2010-06-15 13:42 ` Peter Ujfalusi
2010-06-15 13:53 ` Mark Brown
2010-06-16 5:33 ` Peter Ujfalusi
2010-06-16 14:51 ` Mark Brown
2010-06-15 22:59 ` Stuart Longland
2010-06-18 2:38 ` SOC_DOUBLE_R_SX_TLV (was Update on TLV320AIC3204 Driver) Stuart Longland
2010-06-18 2:56 ` [PATCH] ASoC: Fix overflow bug in SOC_DOUBLE_R_SX_TLV Stuart Longland
2010-06-18 10:29 ` Liam Girdwood
2010-06-19 1:36 ` Mark Brown
2010-06-15 7:21 ` Update on TLV320AIC3204 Driver Mark Brown
2010-06-15 0:29 ` Update on TLV320AIC3204 Driver [File 1/3] Stuart Longland
2010-06-15 0:33 ` Update on TLV320AIC3204 Driver [File 2/3] Stuart Longland
2010-06-15 0:34 ` Update on TLV320AIC3204 Driver [File 3/3] Stuart Longland
2010-06-15 14:20 ` Update on TLV320AIC3204 Driver [File 1/3] Mark Brown
2010-06-15 22:46 ` Stuart Longland
2010-06-16 9:35 ` Mark Brown
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).