Alsa-devel
Threads by month
- ----- 2025 -----
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
September 2010
- 120 participants
- 253 discussions
Hi,
first of all I apologize in case you receive multiple copies of this message, but I forgot to specify a subject in the original mail.
I have a problem compiling the latest alsa driver snapshots. Specifically, it's a problem when patching pcm_native.c:
[..]
copying file alsa-kernel/core/info.c
patching file info.c
copying file alsa-kernel/core/pcm.c
patching file pcm.c
Hunk #3 succeeded at 739 (offset 2 lines).
Hunk #5 succeeded at 954 (offset 2 lines).
Hunk #7 succeeded at 1045 (offset 2 lines).
copying file alsa-kernel/core/pcm_native.c
patching file pcm_native.c
Hunk #4 succeeded at 1544 (offset -25 lines).
Hunk #5 succeeded at 2827 (offset 12 lines).
Hunk #6 succeeded at 2878 (offset 10 lines).
Hunk #7 FAILED at 2906.
Hunk #8 FAILED at 2922.
Hunk #9 succeeded at 2915 (offset -23 lines).
Hunk #10 succeeded at 3036 (offset 10 lines).
Hunk #11 succeeded at 3027 (offset -23 lines).
Hunk #12 succeeded at 3074 (offset 10 lines).
Hunk #13 succeeded at 3098 (offset -23 lines).
Hunk #14 succeeded at 3143 (offset 10 lines).
Hunk #15 succeeded at 3124 (offset -23 lines).
Hunk #16 succeeded at 3214 (offset 10 lines).
Hunk #17 succeeded at 3208 (offset -23 lines).
Hunk #18 succeeded at 3271 (offset 10 lines).
Hunk #19 succeeded at 3304 (offset -23 lines).
Hunk #20 succeeded at 3386 (offset 10 lines).
Hunk #21 succeeded at 3411 (offset -23 lines).
Hunk #22 succeeded at 3456 (offset 10 lines).
Hunk #23 succeeded at 3450 (offset -23 lines).
Hunk #24 succeeded at 3525 (offset 10 lines).
Hunk #25 succeeded at 3644 (offset -23 lines).
2 out of 25 hunks FAILED -- saving rejects to file pcm_native.c.rej
make[2]: *** [pcm_native.c] Error 1
It's
some days I get this, but I waited to report this since I thought it
could be a temporary problem. My OS is Fedora 11 x86_64. Previous
snapshots didn't give me this problem, but I can't say which one broke
the compilation, since I usually try updated snapshots only at kernel
updates.
I searched for this error and the only relevant report I could find is this one:
http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/016420.html
a report from last April.
What could the problem be?
Thanks
_________________________________________________________________
Invite your mail contacts to join your friends list with Windows Live Spaces. It's easy!
http://spaces.live.com/spacesapi.aspx?wx_action=create&wx_url=/friends.aspx…
3
10
Hi,
I sent this to alsa-user a week ago but didn't get any reply. Does
anybody know, what is responsible for the missing faders? Did the
driver change or is it some module loading mechanism in the distro
which prevents the faders from showing up in alsamixer? This is so
strange. Please help!
--
Orm
--------------------------------------------------------------------
Hi everybody,
after the last update of Ubuntu (intrepid to jaunty) on my Lenovo
Ideapad my microphone input doesn't work anymore (neither internal nor
external). I found out that alsamixer lacks quite a lot of faders in
the newer kernel/module compared to the older one. Some of the faders
now unavailable were crucial for Mic input to work on the older system
so I'd very much like to get them back ;-)
Some more infos:
Soundcard: Intel 82801G (ICH7 Family)
Chip: Realtek ALC269
Module: snd_hda_intel (in both kernels)
Older kernel: Ubuntu stock 2.6.27-14-generic
Newer kernel: Ubuntu stock 2.6.28-13-rt
Is there any way to make the missing controls available or did the
driver change and I have to compile the older driver for the new
kernel?
Yours,
Orm
2
3
I have a target that uses iMX27(L) + tlv320aic3x. The kernel tree is
HEAD of linux-arm from about a week ago.
Simple playback and recording (using aplay and arecord) work fine.
However, I've run into a strange problem when both playback and
capture streams are opened at the same time.
Consider the attached test program. The capture device is setup with a
period time of 10ms, and the program reads samples in a loop. The
expected result is that I get a sample every 10ms. This works fine if
the playback device is *not* opened (more specifically, if the
hw_params are not set). However if the playback device is opened (#if
1 code) then I get samples every 20ms, which seems very wrong.
I have tried using both DMA and FIQ ssi-pcm interfaces and both give
the same strange result. With the FIQ driver, if I instrument the
timer callback, the timer triggers every 10ms but there is only enough
data every 20ms.
The codec is being driven with a 13MHz MCLK (output from MX27) and the
BCLK and WCLK come from the codec. They seem to have the correct
rates.
Any ideas? Can anybody else with MX27 hardware verify if my test
program also fails on their target?
thanks,
randolph
/* Test program */
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
int main(int argc, char **argv)
{
int err = -1;
char buf[4096];
unsigned long period_size;
snd_pcm_t *in_handle = NULL, *out_handle;
snd_pcm_hw_params_t *hwparams;
if (snd_pcm_open(&in_handle, "hw:0,0", SND_PCM_STREAM_CAPTURE, 0) < 0) {
printf("Cannot open capture device\n");
return -1;
}
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(in_handle, hwparams);
snd_pcm_hw_params_set_access(in_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(in_handle, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(in_handle, hwparams, 1);
snd_pcm_hw_params_set_rate(in_handle, hwparams, 8000, 0);
snd_pcm_hw_params_set_period_time(in_handle, hwparams, 10000, 0);
snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0);
snd_pcm_hw_params(in_handle, hwparams);
snd_pcm_hw_params_free(hwparams);
if (snd_pcm_open(&out_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
printf("Cannot open playback device\n");
return -1;
}
#if 1
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(out_handle, hwparams);
snd_pcm_hw_params_set_access(out_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(out_handle, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(out_handle, hwparams, 1);
snd_pcm_hw_params_set_rate(out_handle, hwparams, 8000, 0);
snd_pcm_hw_params_set_period_time(out_handle, hwparams, 10000, 0);
snd_pcm_hw_params(out_handle, hwparams);
snd_pcm_hw_params_free(hwparams);
#endif
printf("period size = %u\n", period_size);
printf("Config done, start loop\n");
while (1) {
struct timeval tv;
int ret = snd_pcm_readi(in_handle, buf, period_size);
if (ret < 0)
snd_pcm_prepare(in_handle);
gettimeofday(&tv, NULL);
printf("[%u.%06u] snd_pcm_readi() return %d\n",
tv.tv_sec, tv.tv_usec, ret);
}
return 0;
}
5
11

24 Jun '11
Hello!
The background: I'm just designing an ATMEL AT91RM9200 based board which
got two TLV320AIC33 on board. It is either possible
to hook up the two TLV320AIC33 to a separate SSC (synchronous serial
control) channel each or use one SSC channel and configure it for TDM (time
division multiplex) mode. There is already a driver for
TLV320AIC14k/TLV320AIC2k in the development tree, so adding support for the
TLV320AIC33 should be feasible.
What I'm not sure about is the fact if ASOC is prepared for using channels,
especially SSC, in TDM mode. Is the core ASOC structure prepared to handle
that?
Would we be better off instantiate one drive twice each time with different
parameters for the SSC channel?
Thanks for any feedback.
--
MfG / Regards
Friedrich Lobenstock
_________________________________________________________
SCOTTY Group Austria GmbH
a SCOTTY Group plc company, http://www.scottygroup.com/
_________________________________________________________
4
3

Re: [alsa-devel] [PATCH] ASoC: Add support for Cirrus Logic CS42L52 Lowpower Stereo Codec
by Mark Brown 22 Jun '11
by Mark Brown 22 Jun '11
22 Jun '11
On Thu, Feb 25, 2010 at 06:07:35PM -0600, Brian wrote:
This is a quick first pass review only. It'd be best if you could
include a Signed-off-by line in submissions so that they can be merged
if they're OK, even if you expect problems you may be surprised!
Overall this looks fairly good - there's a lot of comments below but the
vast majority of them are stylistic issues or needing to update to
current APIs rather than major structural problems with the code. The
main thing I don't really follow is the register access code, there's
quite a bit of stuff going on in there.
> + * published by the Free Software Foundation.
> + * Revision history
> + * Nov 2007 Initial version.
> + * Oct 2008 Updated to 2.6.26
> + * Feb 2010 Updated to latest asoc git tree
Please remove the changelog, once things are merged git takes care of
that.
> + */
> +#define DEBUG
Not by default in mainline :)
> +#ifdef DEBUG
> +#define SOCDBG(fmt, arg...) printk(KERN_ERR "%s: %s() " fmt, SOC_CS42L52_NAME, __FUNCTION__, ##arg)
> +#else
> +#define SOCDBG(fmt, arg...)
> +#endif
> +#define SOCINF(fmt, args...) printk(KERN_INFO "%s: " fmt, SOC_CS42L52_NAME, ##args)
> +#define SOCERR(fmt, args...) printk(KERN_ERR "%s: " fmt, SOC_CS42L52_NAME, ##args)
Please convert the driver to use the standard dev_ and pr_ macros for
this (where possible the dev_ ones are better).
> +static int soc_cs42l52_set_bias_level(struct snd_soc_codec *codec,
> + enum snd_soc_bias_level level);
> +static int soc_cs42l52_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai);
> +static int soc_cs42l52_set_sysclk(struct snd_soc_dai *codec_dai,
> + int clk_id, u_int freq, int dir);
> +static int soc_cs42l52_digital_mute(struct snd_soc_dai *dai, int mute);
> +static int soc_cs42l52_set_fmt(struct snd_soc_dai *codec_dai,
> + u_int fmt);
> +
> +static unsigned int soc_cs42l52_read(struct snd_soc_codec *codec,
> + u_int reg);
Please use 'unsigned int' rather than 'u_int' for consistency with the
rest of ASoC. It's a bit surprising that you need the forward
declarations at all but they do no harm.
> +/**
> + * snd_soc_get_volsw - single mixer get callback
> + * @kcontrol: mixer control
> + * @uinfo: control element information
> + *
> + * Callback to get the value of a single mixer control.
> + *
> + * Returns 0 for success.
> + */
> +int snd_soc_cs42l5x_get_volsw(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> + int reg = kcontrol->private_value & 0xff;
The indentation looks wrong - should be tabs for indenting the code
blocks. scripts/checkpatch.pl is a pretty good check for this sort of
stuff, I'll not mention other issues.
> + int shift = (kcontrol->private_value >> 8) & 0x0f;
> + int rshift = (kcontrol->private_value >> 12) & 0x0f;
> + int max = (kcontrol->private_value >> 16) & 0xff;
> + int mask = (1 << fls(max)) - 1;
> + int min = (kcontrol->private_value >> 24) & 0xff;
> +
> + ucontrol->value.integer.value[0] =
> + ((snd_soc_read(codec, reg) >> shift) - min) & mask;
> + if (shift != rshift)
> + ucontrol->value.integer.value[1] =
> + ((snd_soc_read(codec, reg) >> rshift) - min) & mask;
This and most of the other custom control stuff looks an awful lot like
it should be done by adding a SND_SOC_DOUBLE_SIGNED() or something along
the lines of the existing _S8() but I didn't read entirely closely. I
was speaking to someone on IRC only the other day who needed some of
those types for a different device.
These should also be updated to use struct soc_mixer_control for the
private value rather than shifting everything into the private value, it
allows wider ranges and is less error prone.
> +static const u8 soc_cs42l52_reg_default[] = {
> + 0x00, 0xE0, 0x01, 0x07, 0x05, /*4*/
> + 0xa0, 0x00, 0x00, 0x81, /*8*/
Very nitpicky but you're using a mix of upper and lower case for the
letters in the hex digits.
> +static inline int soc_cs42l52_read_reg_cache(struct snd_soc_codec *codec,
> + u_int reg)
> +{
> + u8 *cache = codec->reg_cache;
> +
> + return reg > SOC_CS42L52_REG_NUM ? -EINVAL : cache[reg];
> +}
You should be able to replace all your register I/O code with use of
soc-cache.c with 8 bit data, 8 bit register. Except...
> + if(info->flags & SOC_CS42L52_ALL_IN_ONE)
> + {
> + for(i = 0; i < codec->num_dai; i++)
> + {
> + if(info->machine_handler)
> + info->machine_handler(i);
...I'm not sure what's going on here but it could really use an
explanation? I'm not sure what ALL_IN_ONE is supposed to do, or what
the machine_handler() function is for.
> + if(info->flags & SOC_CS42L52_CHIP_SWICTH)
> + {
> + num = info->flags & SOC_CS42L52_CHIP_MASK;
> + if(info->machine_handler)
> + info->machine_handler(num);
> + }
This too. There's a lot of unusual code using this machine handler
function. I suspect this can all use ASoC standard stuff.
> +static inline int soc_cs42l52_get_revison(struct snd_soc_codec *codec)
> +{
> + u8 data;
> + u8 addr;
> + int num, ret = 0;
> + struct soc_codec_cs42l52 *info = (struct soc_codec_cs42l52*)codec->private_data;
No need to cast away from void (quite a few examples of this in the
code).
> + return ret < 0 ? ret : data;
I'm not a big fan of the ternery operator...
> +static unsigned int soc_cs42l52_read(struct snd_soc_codec *codec,
> + u_int reg)
> +{
> + u8 data;
> + u8 addr;
> + int i, ret = 0;
> + struct soc_codec_cs42l52 *info = (struct soc_codec_cs42l52*)codec->private_data;
> +#ifndef CONFIG_CS42L52_DEBUG
> + if(reg == CODEC_CS42L52_SPK_STATUS)
> + {
> +#endif
This really shouldn't be ifdefed. If you need to handle registers that
report status from the device with the soc-cache stuff use the
volatile_register() callback in the CODEC, any registers that need a
CODEC read will call through to the hardware rather than using the
cache.
> +static const char *cs42l52_adc_mux[] = {"AIN1", "AIN2", "AIN3", "AIN4", "PGA"};
> +static const char *cs42l52_mic_mux[] = {"MIC1", "MIC2"};
> +static const char *cs42l52_stereo_mux[] = {"Mono", "Stereo"};
> +static const char *cs42l52_off[] = {"On", "Off"};
> +static const char *cs42l52_hpmux[] = {"Off", "On"};
> +
> +static const struct soc_enum soc_cs42l52_enum[] = {
> +SOC_ENUM_DOUBLE(CODEC_CS42L52_ANALOG_HPF_CTL, 4, 6, 2, cs42l52_hpf_freeze), /*0*/
> +SOC_ENUM_SINGLE(CODEC_CS42L52_ADC_HPF_FREQ, 0, 4, cs42l52_hpf_corner_freq),
Don't use an array of enums, define individual variables for them. Some
drivers use the arrays for historical reasons - they're quite painful to
work with if you ever want to change anything in the middle and we've
found a few off by one errors in the referencing into the arrays before
:/.
> +static const struct snd_kcontrol_new soc_cs42l52_controls[] = {
> +
> +SOC_ENUM("Mic VA Capture Switch", soc_cs42l52_enum[18]), /*0*/
enums shouldn't be called Switch - Switch has a specific meaning to ALSA
(see Documentation/sound/alsa/ControlNames.txt, there's quite a few
other controls could do with having the components of the name reordered
and various other things following that). Normally just omit the ' Switch'
from the name.
> +SOC_DOUBLE("Analog SR Capture Switch", CODEC_CS42L52_ANALOG_HPF_CTL, 1, 3, 1, 1),
> +SOC_DOUBLE("Analog ZC Capture Switch", CODEC_CS42L52_ANALOG_HPF_CTL, 0, 2, 1, 1),
This'd normally be 'Capture ZC Switch' if it's for zero cross.
> +SOC_ENUM("HPF corner freq Capture Switch", soc_cs42l52_enum[1]), /*5*/
> +
> +SOC_SINGLE("Ganged Ctl Capture Switch", CODEC_CS42L52_ADC_MISC_CTL, 7, 1, 1), /* should be enabled init */
What does the comment mean here?
> +SOC_SINGLE("HP Analog Gain Playback Volume", CODEC_CS42L52_PB_CTL1, 5, 7, 0),
Just "Headphone Volume"?
> +SOC_SINGLE("Playback B=A Volume Playback Switch", CODEC_CS42L52_PB_CTL1, 4, 1, 0), /*10*/ /*should be enabled init*/
If this is the mute for the headphone volume it should have the same
name but with Switch instead of Volume so applications like alsamixer
can display the two together.
> +static int soc_cs42l52_add_controls(struct snd_soc_codec *codec)
> +{
> + int i,ret = 0;
> +
> + for(i = 0; i < ARRAY_SIZE(soc_cs42l52_controls); i++)
> + {
> + ret = snd_ctl_add(codec->card,
> + snd_soc_cnew(&soc_cs42l52_controls[i], codec, NULL));
> + if(ret < 0)
> + {
> + SOCDBG("add cs42l52 controls failed\n");
> + break;
Replace this with snd_soc_add_controls().
> + /* Sum switches */
> + SND_SOC_DAPM_PGA("AIN1A Switch", CODEC_CS42L52_ADC_PGA_A, 0, 0, NULL, 0),
Without having checked the datasheet I *suspect* most of these switches
should be modelled as inputs to a mixer (possibly one with no power
control of its own)? The "Switch" in the name certainly looks very out
of place in the name of a DAPM control.
> + /* Output path */
> + SND_SOC_DAPM_MUX("Passthrough Left Playback Switch", SND_SOC_NOPM, 0, 0, &cs42l52_hpa_mux),
Normally "Bypass" instead of "Passthrough".
> + /* Mic Bias */
> + {"Mic Bias Capture Switch", "On", "PGA MICA"},
> + {"Mic Bias Capture Switch", "On", "PGA MICB"},
> + {"Mic-Bias", NULL, "Mic Bias Capture Switch"},
> + {"Mic-Bias", NULL, "Mic Bias Capture Switch"},
> + {"ADC Mux Left Capture Switch", "PGA", "Mic-Bias"},
> + {"ADC Mux Right Capture Switch", "PGA", "Mic-Bias"},
> + {"Passthrough Left Playback Switch", "On", "Mic-Bias"},
> + {"Passthrough Right Playback Switch", "On", "Mic-Bias"},
This looks odd - normally the microphone bias would be connected to the
microphone input externally and would not be hooked up as an input
wihtin the CODEC?
> + /* terminator */
> + {NULL, NULL, NULL},
Not needed any more (I'm vaugely surprised this doesn't generate an
error).
> +#define SOC_CS42L52_RATES ( SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
> + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
> + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
> + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
> + SNDRV_PCM_RATE_96000 ) /*refer to cs42l52 datasheet page35*/
This is equivalent to SNDRV_PCM_RATE_8000_96000
> +/* #define CONFIG_MANUAL_CLK */
This might use some comments (or should possibly be deleted)?
> + soc_cs42l52_set_bias_level(soc_codec, SND_SOC_BIAS_OFF);
> + soc_codec->bias_level = SND_SOC_BIAS_STANDBY;
> + schedule_delayed_work(&soc_codec->delayed_work, msecs_to_jiffies(1000));
> + soc_cs42l52_required_setup(soc_codec);
A comment about what the delayed work is up to wouldn't hurt?
> + /*set hp default volume*/
> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_HPA_VOL, DEFAULT_HP_VOL);
> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_HPB_VOL, DEFAULT_HP_VOL);
For pretty much all the configuration you're doing here just let
userspace set things up, there's no telling what's an appropriate
setting on any given system and presumably the chip designers made a
reasonable effort to ensure that the default state of the chip is at
least not harmful.
> +#ifdef CONFIG_MANUAL_CLK
> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_CLK_CTL,
> + (soc_cs42l52_read(soc_codec, CODEC_CS42L52_CLK_CTL)
> + & ~CLK_CTL_AUTODECT_ENABLE));
Pass this configuration in as platform data, this will allow multiple
boards to build from the same kernel.
> + ret = snd_soc_init_card(soc_dev);
> + if(ret)
> + {
> + SOCERR("add snd card failed\n");
> + goto card_err;
> + }
Please develop against
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git for-2.6.35
(or whatever the latest kernel version branch is at any given time.)
You should also be calling snd_soc_register_codec() and
snd_soc_register_dai() from this function.
> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
No need for the ifdefs if you only support I2C, the ifdefs are used by
drivers that have multiple control interfaces (typically I2C plus SPI).
> + }
> +
> +
> + return ret;
> +}
> +
> +static int cs42l52_i2c_remove(struct i2c_client *client)
> +{
> + struct snd_soc_codec *soc_codec;
> + if(client)
> + {
> + soc_codec = i2c_get_clientdata(client);
> + if(soc_codec)
> + kfree(soc_codec->reg_cache);
> + }
> + return 0;
> +}
This should be unregistering from ASoC as well, or refusing to remove if
not implemented.
> + case SND_SOC_DAIFMT_DSP_B:
> + SOCINF("unsupported format\n");
> + ret = -EINVAL;
> + goto done;
> + default:
> + SOCINF("invaild format\n");
> + ret = -EINVAL;
> + goto done;
Could just squash these together.
> + case SND_SOC_DAIFMT_IB_IF:
> + SOCDBG("codec dai fmt inversed sclk\n");
> + iface |= IFACE_CTL1_INV_SCLK;
> + break;
> + case SND_SOC_DAIFMT_IB_NF:
> + iface |= IFACE_CTL1_INV_SCLK;
> + break;
It looks like the hardware only supports the inversion of one of the
clocks so only two of the clock polarity combinations should be
supported?
> + info->format = iface;
I'd really expect to see interaction with the hardware here?
> + /* 96k */
> + {12288000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K, CLK_CTL_NOT_27M, CLK_CTL_RATIO_128, 0},
> + {18432000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K, CLK_CTL_NOT_27M, CLK_CTL_RATIO_128, 0},/*29*/
> + {12000000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K, CLK_CTL_NOT_27M, CLK_CTL_RATIO_125, 0},
> + {24000000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K, CLK_CTL_NOT_27M, CLK_CTL_RATIO_125, 1},
If you're feeling adventurous you should be able to use this data to set
up constraints for the DAI in the startup() callback so only sample
rates supported from the system clock can be used by userspace.
> +static int soc_cs42l52_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + int ret = 0;
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct snd_soc_device *soc_dev = rtd->socdev;
> + struct snd_soc_codec *soc_codec = soc_dev->card->codec;
> + struct soc_codec_cs42l52 *info = (struct soc_codec_cs42l52*)soc_codec->private_data;
> +
> + u32 clk = 0;
> + int index = soc_cs42l52_get_clk(info->sysclk, params_rate(params));
> +static int soc_cs42l52_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> + struct snd_soc_device *soc_dev = (struct snd_soc_device*)platform_get_drvdata(pdev);
> + struct snd_soc_codec *soc_codec = soc_dev->card->codec;
> +
> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_PWCTL1, PWCTL1_PDN_CODEC);
> + soc_cs42l52_set_bias_level(soc_codec, SND_SOC_BIAS_OFF);
I'd expect that set_bias_level() would do what the explict write does?
> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
> + if(soc_codec_data->i2c_addr)
> + {
> + ret = i2c_add_driver(&cs42l52_i2c_drv);
> +
> + if(ret)
> + {
> + SOCERR("register i2c driver failed\n");
> + goto out;
> +
> + }
> + SOCINF("Cirrus Logic ALSA SoC codec cs42l52 driver verison 1.2 2/2010\n");
> + return ret;
> + }
This should be changed to use standard device model registration - see
http://git.kernel.org/?p=linux/kernel/git/broonie/sound-2.6.git;a=commit;h=…
for an example of doing the conversion.
> +struct soc_codec_cs42l52_data{
> + u_short i2c_addr;
> +};
With the device model stuff this won't be needed any more.
> +/*
> + *CS42L52 internal registers
> + */
> +#define CODEC_CS42L52_CHIP 0x01
> +#define CHIP_ID 0xE0
> +#define CHIP_ID_MASK 0xF8
Many of the register names and other definitions for register fields and
values need to be namespace to avoid collisions.
4
5
Hello ALSA developers,
I am having problems with the microphone on the following hardware this is on
a Lenovo W500 laptop.
Card: HDA Intel
Chip: Conexant CX20561 (Hermosa)
Here is the problems im noticing:
1) If I use ALSA 'default' with the microphone I get noise interference on
output [high/low/high/low].
If I switch this to use the "ALSA Capture on HDA Intel [CONEXANT Analog] I get
interference except it is the standard 60Hz electrical noise [steady noise]
2) Using the internal/external microphones gives an inconsistent volume. It
also does not seem to work with the microphone cutting out so much. I need to
adjust Digital + another microphone but it still drops capturing.
Alsamixer shows: Digital/Docking Microphone/External Microphone/Internal
Microphone
Here is the module options I give to the sound driver:
options snd-intel-hda enable_msi=1 power_save=10 power_save_controller=true
model=laptop index=0
Assuming power saving/controller causes the microphone to cut out or not.
3) I'm now aware that this card has no pinouts for PCM capture
https://bugzilla.redhat.com/show_bug.cgi?id=502293
Thanks for any help,
Shawn.
2
12
Hi,
I experience frequent kernel crashes with 2.6.31 and CA0110. I know
this card doesn't work, and there is no problem with that; however, it
crashing non-stop is not very nice.
Also, card doesn't get any controls (and I believe it does get some
controls when it's the only card using HDA driver, that is when I
disable onboard one).
dmesg is here:
http://pastebin.com/m7c53562f
lspci:
02:00.0 PCI bridge: Creative Labs Device 7006 (prog-if 00 [Normal decode])
Flags: bus master, fast devsel, latency 0
Bus: primary=02, secondary=03, subordinate=03, sec-latency=64
Memory behind bridge: fe900000-fe9fffff
Capabilities: [50] Power Management version 3
Capabilities: [60] MSI: Enable- Count=1/16 Maskable- 64bit+
Capabilities: [80] Subsystem: Creative Labs Device 0010
Capabilities: [90] Express PCI/PCI-X Bridge, MSI 00
Capabilities: [100] Advanced Error Reporting
Kernel modules: shpchp
03:00.0 Audio device: Creative Labs [SB X-Fi Xtreme Audio] CA0110-IBG
Subsystem: Creative Labs Device 0018
Flags: bus master, medium devsel, latency 64, IRQ 16
Memory at fe9fc000 (32-bit, non-prefetchable) [size=16K]
Capabilities: [dc] Power Management version 3
Kernel driver in use: HDA Intel
Kernel modules: snd-hda-intel
Any ideas?
--
Vedran Miletić
P.S.
Please CC me, as I'm not subscribed to mailing list at the moment.
2
2
From: Cliff Cai <cliff.cai(a)analog.com>
Signed-off-by: Cliff Cai <cliff.cai(a)analog.com>
Signed-off-by: Mike Frysinger <vapier(a)gentoo.org>
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ssm2604.c | 643 ++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/ssm2604.h | 92 +++++++
4 files changed, 741 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/ssm2604.c
create mode 100644 sound/soc/codecs/ssm2604.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 5da30eb..5db82d2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_PCM3008
select SND_SOC_SPDIF
select SND_SOC_SSM2602 if I2C
+ select SND_SOC_SSM2604 if I2C
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
select SND_SOC_TLV320AIC23 if I2C
select SND_SOC_TLV320AIC26 if SPI_MASTER
@@ -150,6 +151,9 @@ config SND_SOC_SPDIF
config SND_SOC_SSM2602
tristate
+config SND_SOC_SSM2604
+ tristate
+
config SND_SOC_STAC9766
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 91429ea..7593eeb 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -16,6 +16,7 @@ snd-soc-l3-objs := l3.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-ssm2604-objs := ssm2604.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
@@ -81,6 +82,7 @@ obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_SSM2604) += snd-soc-ssm2604.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
diff --git a/sound/soc/codecs/ssm2604.c b/sound/soc/codecs/ssm2604.c
new file mode 100644
index 0000000..7522586
--- /dev/null
+++ b/sound/soc/codecs/ssm2604.c
@@ -0,0 +1,643 @@
+/*
+ * Driver for ssm2604 sound codec
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/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 "ssm2604.h"
+
+#define SSM2604_VERSION "0.1"
+
+struct snd_soc_codec_device soc_codec_dev_ssm2604;
+static struct snd_soc_codec *ssm2604_codec;
+/* codec private data */
+struct ssm2604_priv {
+ unsigned int sysclk;
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * ssm2604 register cache
+ * We can't read the ssm2604 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 ssm2604_reg[SSM2604_CACHEREGNUM] = {
+ 0x0017, 0x0017, 0x0000, 0x0000,
+ 0x0000, 0x000a, 0x0000, 0x0000
+};
+
+#define ssm2604_reset(c) snd_soc_write(c, SSM2604_RESET, 0)
+
+static const char *ssm2604_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum ssm2604_enum[] = {
+ SOC_ENUM_SINGLE(SSM2604_APDIGI, 1, 4, ssm2604_deemph),
+};
+
+static const struct snd_kcontrol_new ssm2604_snd_controls[] = {
+
+SOC_DOUBLE_R("Capture Volume", SSM2604_LINVOL, SSM2604_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", SSM2604_LINVOL, SSM2604_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", SSM2604_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", SSM2604_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Capture Source", ssm2604_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", ssm2604_enum[1]),
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new ssm2604_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", SSM2604_APANA, 3, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SSM2604_PWR, 4, 1,
+ &ssm2604_output_mixer_controls[0],
+ ARRAY_SIZE(ssm2604_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2604_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2604_PWR, 2, 1),
+SND_SOC_DAPM_PGA("Line Input", SSM2604_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route audio_conn[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+
+ /* outputs */
+ {"ROUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line", "Line Input"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+};
+
+static int ssm2604_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ssm2604_dapm_widgets,
+ ARRAY_SIZE(ssm2604_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn));
+
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return i;
+}
+
+static int ssm2604_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ u16 srate;
+ 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 ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = codec->control_data;
+ u16 iface = snd_soc_read(codec, SSM2604_IFACE) & 0xfff3;
+ int i = get_coeff(ssm2604->sysclk, params_rate(params));
+
+ if (substream == ssm2604->slave_substream) {
+ dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ /*no match is found*/
+ if (i == ARRAY_SIZE(coeff_div))
+ return -EINVAL;
+
+ srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ snd_soc_write(codec, SSM2604_ACTIVE, 0);
+ snd_soc_write(codec, SSM2604_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+ snd_soc_write(codec, SSM2604_IFACE, iface);
+ snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+ return 0;
+}
+
+static int ssm2604_startup(struct snd_pcm_substream *substream,
+ 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 ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = codec->control_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ * TODO: the ssm2604 allows pairs of non-matching PB/REC rates
+ */
+ if (ssm2604->master_substream) {
+ master_runtime = ssm2604->master_substream->runtime;
+ dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
+ master_runtime->sample_bits,
+ master_runtime->rate);
+
+ if (master_runtime->rate != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ if (master_runtime->sample_bits != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ ssm2604->slave_substream = substream;
+ } else
+ ssm2604->master_substream = substream;
+
+ return 0;
+}
+
+static int ssm2604_pcm_prepare(struct snd_pcm_substream *substream,
+ 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;
+ /* set active */
+ snd_soc_write(codec, SSM2604_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+
+ return 0;
+}
+
+static void ssm2604_shutdown(struct snd_pcm_substream *substream,
+ 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 ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec);
+
+ /* deactivate */
+ if (!codec->active)
+ snd_soc_write(codec, SSM2604_ACTIVE, 0);
+
+ if (ssm2604->master_substream == substream)
+ ssm2604->master_substream = ssm2604->slave_substream;
+
+ ssm2604->slave_substream = NULL;
+}
+
+static int ssm2604_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, SSM2604_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE;
+ if (mute)
+ snd_soc_write(codec, SSM2604_APDIGI,
+ mute_reg | APDIGI_ENABLE_DAC_MUTE);
+ else
+ snd_soc_write(codec, SSM2604_APDIGI, mute_reg);
+ return 0;
+}
+
+static int ssm2604_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 ssm2604_priv *ssm2604 = snd_soc_codec_get_drvdata(codec);
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ ssm2604->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ssm2604_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0013;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0003;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, SSM2604_IFACE, iface);
+ return 0;
+}
+
+static int ssm2604_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = snd_soc_read(codec, SSM2604_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ snd_soc_write(codec, SSM2604_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ snd_soc_write(codec, SSM2604_PWR, reg | PWR_CLK_OUT_PDN);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ snd_soc_write(codec, SSM2604_ACTIVE, 0);
+ snd_soc_write(codec, SSM2604_PWR, 0xffff);
+ break;
+
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define SSM2604_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define SSM2604_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops ssm2604_dai_ops = {
+ .startup = ssm2604_startup,
+ .prepare = ssm2604_pcm_prepare,
+ .hw_params = ssm2604_hw_params,
+ .shutdown = ssm2604_shutdown,
+ .digital_mute = ssm2604_mute,
+ .set_sysclk = ssm2604_set_dai_sysclk,
+ .set_fmt = ssm2604_set_dai_fmt,
+};
+
+struct snd_soc_dai ssm2604_dai = {
+ .name = "SSM2604",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2604_RATES,
+ .formats = SSM2604_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2604_RATES,
+ .formats = SSM2604_FORMATS,},
+ .ops = &ssm2604_dai_ops,
+};
+EXPORT_SYMBOL_GPL(ssm2604_dai);
+
+static int ssm2604_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;
+
+ ssm2604_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ssm2604_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];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(ssm2604_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ ssm2604_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ssm2604_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+static int ssm2604_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int reg, ret = 0;
+
+ socdev->card->codec = ssm2604_codec;
+ codec = ssm2604_codec;
+
+ ssm2604_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "ssm2604: failed to create pcms\n");
+ goto pcm_err;
+ }
+ /*power on device*/
+ snd_soc_write(codec, SSM2604_ACTIVE, 0);
+ /* set the update bits */
+ reg = snd_soc_read(codec, SSM2604_LINVOL);
+ snd_soc_write(codec, SSM2604_LINVOL, reg | LINVOL_LRIN_BOTH);
+ reg = snd_soc_read(codec, SSM2604_RINVOL);
+ snd_soc_write(codec, SSM2604_RINVOL, reg | RINVOL_RLIN_BOTH);
+
+ snd_soc_write(codec, SSM2604_APANA, APANA_SELECT_DAC);
+ snd_soc_write(codec, SSM2604_PWR, 0);
+
+ snd_soc_add_controls(codec, ssm2604_snd_controls,
+ ARRAY_SIZE(ssm2604_snd_controls));
+ ssm2604_add_widgets(codec);
+
+ return ret;
+
+pcm_err:
+ return ret;
+}
+
+/* remove everything here */
+static int ssm2604_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ssm2604 = {
+ .probe = ssm2604_probe,
+ .remove = ssm2604_remove,
+ .suspend = ssm2604_suspend,
+ .resume = ssm2604_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2604);
+
+static int ssm2604_register(struct ssm2604_priv *ssm2604, enum snd_soc_control_type control)
+{
+ struct snd_soc_codec *codec = &ssm2604->codec;
+ int ret = 0;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ codec->name = "SSM2604";
+ codec->owner = THIS_MODULE;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = ssm2604_set_bias_level;
+ codec->dai = &ssm2604_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(ssm2604_reg);
+ codec->reg_cache = kmemdup(ssm2604_reg, sizeof(ssm2604_reg),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_dai(&ssm2604_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ snd_soc_unregister_codec(codec);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void ssm2604_unregister(struct ssm2604_priv *ssm2604)
+{
+ struct snd_soc_codec *codec = &ssm2604->codec;
+
+ ssm2604_set_bias_level(&ssm2604->codec, SND_SOC_BIAS_OFF);
+ kfree(codec->reg_cache);
+ snd_soc_unregister_dai(&ssm2604_dai);
+ snd_soc_unregister_codec(&ssm2604->codec);
+ kfree(ssm2604);
+ ssm2604_codec = NULL;
+}
+
+static int ssm2604_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ssm2604_priv *ssm2604;
+ struct snd_soc_codec *codec;
+
+ ssm2604 = kzalloc(sizeof(struct ssm2604_priv), GFP_KERNEL);
+ if (ssm2604 == NULL)
+ return -ENOMEM;
+ codec = &ssm2604->codec;
+ snd_soc_codec_set_drvdata(codec, ssm2604);
+
+ i2c_set_clientdata(i2c, ssm2604);
+ codec->control_data = i2c;
+
+ codec->dev = &i2c->dev;
+ ssm2604_codec = codec;
+
+ return ssm2604_register(ssm2604, SND_SOC_I2C);
+}
+
+static __devexit int ssm2604_i2c_remove(struct i2c_client *client)
+{
+ struct ssm2604_priv *ssm2604 = i2c_get_clientdata(client);
+ ssm2604_unregister(ssm2604);
+ return 0;
+}
+
+static const struct i2c_device_id ssm2604_i2c_id[] = {
+ { "ssm2604", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2604_i2c_id);
+
+/* corgi i2c codec control layer */
+static struct i2c_driver ssm2604_i2c_driver = {
+ .driver = {
+ .name = "ssm2604",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2604_i2c_probe,
+ .remove = __devexit_p(ssm2604_i2c_remove),
+ .id_table = ssm2604_i2c_id,
+};
+
+static int __init ssm2604_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&ssm2604_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register ssm2604 I2C driver: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+module_init(ssm2604_modinit);
+
+static void __exit ssm2604_exit(void)
+{
+ i2c_del_driver(&ssm2604_i2c_driver);
+}
+module_exit(ssm2604_exit);
+
+MODULE_DESCRIPTION("ASoC SSM2604 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ssm2604.h b/sound/soc/codecs/ssm2604.h
new file mode 100644
index 0000000..3b24a08
--- /dev/null
+++ b/sound/soc/codecs/ssm2604.h
@@ -0,0 +1,92 @@
+/*
+ * Header for ssm2604 sound codec
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _SSM2604_H
+#define _SSM2604_H
+
+/* SSM2604 Codec Register definitions */
+
+#define SSM2604_LINVOL 0x00
+#define SSM2604_RINVOL 0x01
+#define SSM2604_APANA 0x04
+#define SSM2604_APDIGI 0x05
+#define SSM2604_PWR 0x06
+#define SSM2604_IFACE 0x07
+#define SSM2604_SRATE 0x08
+#define SSM2604_ACTIVE 0x09
+#define SSM2604_RESET 0x0f
+
+/* SSM2604 Codec Register Field definitions
+ * (Mask value to extract the corresponding Register field)
+ */
+
+/* Left ADC Volume Control (SSM2604_REG_LEFT_ADC_VOL) */
+#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */
+#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */
+#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */
+
+/* Right ADC Volume Control (SSM2604_REG_RIGHT_ADC_VOL) */
+#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */
+#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */
+#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */
+
+
+/* Analogue Audio Path Control (SSM2604_REG_ANALOGUE_PATH) */
+#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */
+#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */
+
+/* Digital Audio Path Control (SSM2604_REG_DIGITAL_PATH) */
+#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */
+#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */
+#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */
+#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */
+
+/* Power Down Control (SSM2604_REG_POWER)
+ * (1=Enable PowerDown, 0=Disable PowerDown)
+ */
+#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */
+#define PWR_ADC_PDN 0x004 /* ADC Power Down */
+#define PWR_DAC_PDN 0x008 /* DAC Power Down */
+#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */
+#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */
+#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */
+
+/* Digital Audio Interface Format (SSM2604_REG_DIGITAL_IFACE) */
+#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */
+#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */
+#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */
+#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */
+#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */
+#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */
+
+/* Sampling Control (SSM2604_REG_SAMPLING_CTRL) */
+#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */
+#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */
+#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */
+#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */
+#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */
+
+/* Active Control (SSM2604_REG_ACTIVE_CTRL) */
+#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */
+
+/*********************************************************************/
+
+#define SSM2604_CACHEREGNUM 9
+
+#define SSM2604_SYSCLK 0
+#define SSM2604_DAI 0
+
+struct ssm2604_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ssm2604_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ssm2604;
+
+#endif
--
1.7.2
3
6
On Tue, Sep 28, 2010 at 9:42 PM, Takashi Iwai <tiwai(a)suse.de> wrote:
> Enable all CONFIG_SND_HDA_*.
Done that. Still no mic.
On Tue, Sep 28, 2010 at 9:42 PM, Wu Fengguang <fengguang.wu(a)intel.com>
wrote:
> It's easier to enable all of them. They are small modules.
Tried just about everything, still no mic. Sounds works in all cases.
I found that with SND_HDA_CODEC_INTELHDMI selected, SND_DYNAMIC_MINORS
will be enabled by default which won't work in my case as I'm not using
udev. Here's a patch to take away the default so non-udev will still
works.
Do I really need udev to get mic to work?
Thanks,
Jeff
--- lx/sound/pci/hda/Kconfig.org 2010-09-14 16:34:52.000000000 +0800
+++ lx/sound/pci/hda/Kconfig 2010-09-14 16:35:09.000000000 +0800
@@ -145,7 +145,6 @@
config SND_HDA_CODEC_INTELHDMI
bool "Build INTEL HDMI HD-audio codec support"
- select SND_DYNAMIC_MINORS
default y
help
Say Y here to include INTEL HDMI HD-audio codec support in
3
16
Dear all:
I just install Ubuntu9.10 on my laptop and I try to update my sound
system and recompile the utility.
there are some questions I met:
1. when configure the utility
it says
checking panel.h presence... no
checking for panel.h... no
configure: error: required curses helper header not found
and config.log is
"conftest.c:10:42: error: CoreFoundation/CFPreferences.h: No such file
or directory
conftest.c: In function 'main':
conftest.c:14: error: 'NULL' undeclared (first use in this function)
conftest.c:14: error: (Each undeclared identifier is repor"
the above error seems I miss install some package before.
I google the name but I have no improvement.
"conftest.c:20:28: error: ac_nonexistent.h: No such file or directory
configure:7397: $? = 1"
the above error seems the header located where the conftest.c doesn't expect.
how could solve them all?
2. there are some configure file in share when I compile advance linux
sound architecture.
like alsa.conf.
have anyone know where I can find the naming rule?
appreciate your help,
miloody
4
7
Is the ADAT output of the IDT (sigmatel) STAC927x HDA codec supported in
alsa ?
If not could someone please try to add this incredibly nice feature in
the existing alsa driver ?
Thank you very much
4
13

31 Jan '11
Hello,
Recently I'm having some strange issues with my VIA VT1708S on-board
sound card. The description might be a bit long, so I'll give a brief
summary first.
I have ASUS P5QL/EPU motherboard with VT1708S 8-channel HDA Codec.
There are 3 OSs installed on this machine: openSUSE 11.1 (my main
system), openSUSE 11.3, Kubuntu 10.04.
Audio worked fine until some updates at the beginning of September.
Since then I have 2 different issues.
1).On 11.1 - Line In control seems inactive, instead Front Mic control
controls the level from Line In jack. Front Mic is constanly disabled.
alsaconf does not show hda-intel in the list of detected cards.
Latest version it happened with is git20100925.
When I compiled stable 1.0.23 sources, everything started working as
expected again.
I reported this issue on bugtrack:
https://bugtrack.alsa-project.org/alsa-bug/view.php?id=5133
2) On oS 11.3 I start experiencing problems recording audio either
from Mic or from Line.
After some experiments I found out that the Capture1 control is
somehow broken. Mute switch is reversed. When it is unmuted, there is
no audio recorded. When it is muted, recording works. The same
happened to at least one more user of openSUSE, see the thread "How to
investigate the problem with MIC" on alsa-user mailing list.
I've also added my comments to issue
https://bugtrack.alsa-project.org/alsa-bug/view.php?id=5130 but now I
think it is different issue.
Even when I have audio recorded normally when Capture1 is muted, I
still have very distorted voice from Mic in Skype. It worked fine
before the problem started.
More details:
On oS 11.1 I had my audio working fine after updating to 1.0.23
versions of alsa driver. I'm using opensuse/multimedia: repository
that provides binary rpm of alsa-driver git snapshots.
I have only one current kernel 2.6.27.48 on 11.1.
On oS 11.3 audio worked right out of the box, since it uses kernels
with alsa-driver 1.0.23. I have 2-3 kernels installed at the same time
- normal 2.6.34 from official repository and 2.6.35 from kernel build
service.
I'm not sure when exactly the problems started, but definitely at the
beginning of September.
I've initially saw that on oS 11.1 I can't hear the audio from my TV
card connected to Line In. Then I noticed that Mic does not work in
Skype on both 11.1 and 11.3, while it still works on Kubuntu. When I
unmuted Front Mic on 11.1 I've suddenly found that it actually enabled
Line In.
I think now that something went bad with card detecting. Probably on
11.1 with latest git versions my card is not properly detected and
some "defaults" are used.
I'm attaching here 2 outputs of alsa-info.sh -
11.1_stable captured with alsa-driver compiled from stable 1.0.23 sources.
11.1_git25 captured with driver compiled with git20100925 version
from opensuse/multimedia: repository.
For me the problem with 11.1 is resolved, but probably it's worth
checking why latest version from git behave this way.
I'll continue with 11.3 investigation later on.
Regards,
--
Mark Goldstein
2
21

13 Dec '10
Hi,
I need to set all mixer stuff to 100% and the output isn't ok.
Volume is too low.
When I switch to another input on my amp. it's about 500% louder.
Please have a look to attached ala-info output.
I tested with opensuse 11.2 and also with a newer kernel for the distro
2.6.34.
Can you please help?
Thx.
Halim
--
Halim Sahin
E-Mail:
halim.sahin (at) t-online.de
2
4
So I've discovered that my sound card has a "PCM Playback Volume"
control, but changing that control does not alter the volume. This is
not only confusing but also a problem for PulseAudio. (PA merges this
control into it's overall volume, and so when the user moves PA's volume
control, in some ranges nothing happens.)
Interestingly enough, this control does not come from the HDA parser, it
is added by alsactl at boot time...! (Through alsa-lib's
snd_ctl_elem_add_integer, which does an snd_ctl_ioctl.)
So the question is, why is alsactl creating volume controls, and why is
userspace allowed to add controls in the first place, if the kernel
can't use them for anything anyway? That doesn't make sense to me.
This a generic problem, but sound card info, if that matters, is
available here:
https://launchpad.net/bugs/625149
Codec: http://launchpadlibrarian.net/54478760/Card0.Codecs.codec.0.txt
--
David Henningsson, Canonical Ltd.
http://launchpad.net/~diwic
9
73
Hi guys.
Is it possible to somehow mandate the
use of a PCM plugin? For example, the
PC-Speaker.conf defines the use of softvol
plugin for "default" and "front" devices.
It is still possible for the software to bypass
it by the use of "hw", and some software
does exactly that.
Is it possible to somehow make some
plugin a mandatory?
6
23

19 Oct '10
Hello everybody.
This is my first "post" here, so please be "gentle".
I have created PAVC plugin for ALSA, it is based on COPY plugin, and
use SOFTVOL plugin for sound output. As far as I can tell from current
tests - It works perfectly, and it doesn't break compatibility with
any apps I use (and PulseAudio does). So I was wondering - is there
any chance this plugin would be added to the next official ALSA
release?
Here is the patch that enables it on current alsa-lib-1.0.23
(hopefully this mailing list is a good place to "post" this):
--- a/configure.in 2010-09-23 17:03:00.000000000 +0200
+++ b/configure.in 2010-09-23 17:09:11.029900400 +0200
@@ -445,7 +445,7 @@
pcm_plugins=""
fi
-PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul"
+PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul pavc"
build_pcm_plugin="no"
for t in $PCM_PLUGIN_LIST; do
@@ -516,6 +516,7 @@
AM_CONDITIONAL(BUILD_PCM_PLUGIN_EXTPLUG, test x$build_pcm_extplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_IOPLUG, test x$build_pcm_ioplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_MMAP_EMUL, test x$build_pcm_mmap_emul = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_PAVC, test x$build_pcm_pavc = xyes)
dnl Defines for plug plugin
if test "$build_pcm_rate" = "yes"; then
--- a/src/pcm/Makefile.am 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/Makefile.am 2010-09-23 17:20:10.601899695 +0200
@@ -102,6 +102,9 @@
if BUILD_PCM_PLUGIN_MMAP_EMUL
libpcm_la_SOURCES += pcm_mmap_emul.c
endif
+if BUILD_PCM_PLUGIN_PAVC
+libpcm_la_SOURCES += pcm_pavc.c
+endif
EXTRA_DIST = pcm_dmix_i386.c pcm_dmix_x86_64.c pcm_dmix_generic.c
--- a/src/pcm/pcm.c 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/pcm.c 2010-09-23 17:10:43.813900406 +0200
@@ -2047,7 +2047,7 @@
static const char *const build_in_pcms[] = {
"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
"linear", "meter", "mulaw", "multi", "null", "empty", "plug",
"rate", "route", "share",
- "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
+ "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul","pavc",
NULL
};
--- a/include/pcm.h 2010-04-16 13:11:05.000000000 +0200
+++ b/include/pcm.h 2010-09-23 17:11:58.389899158 +0200
@@ -376,7 +376,9 @@
SND_PCM_TYPE_EXTPLUG,
/** Mmap-emulation plugin */
SND_PCM_TYPE_MMAP_EMUL,
- SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
+ /** PAVC plugin */
+ SND_PCM_TYPE_PAVC,
+ SND_PCM_TYPE_LAST = SND_PCM_TYPE_PAVC
};
/** PCM type */
--- a/src/pcm/pcm_pavc.c
+++ b/src/pcm/pcm_pavc.c
@@ -0,0 +1,846 @@
+/*
+ * PCM - PAVC plugin
+ * Based on PCM - Copy conversion plugin by Abramo Bagnara
<abramo(a)alsa-project.org>
+ *
+ * Copyright (c) 2010 by Adrian Kobyliński <huk256(a)gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_pavc = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+ /* This field need to be the first */
+ snd_pcm_plugin_t plug;
+} snd_pcm_pavc_t;
+#endif
+
+static int snd_pcm_pavc_hw_refine_cprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+ int err;
+ snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+ err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+ &access_mask);
+ if (err < 0)
+ return err;
+ params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_sprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+ snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+ _snd_pcm_hw_params_any(sparams);
+ _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+ &saccess_mask);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_schange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(sparams, links, params);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_cchange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(params, links, sparams);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_refine_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cprepare,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_pavc_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_params_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_params);
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_write_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(slave_areas, slave_offset,
+ areas, offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_read_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(areas, offset,
+ slave_areas, slave_offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static void snd_pcm_pavc_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+ snd_pcm_pavc_t *pavc = pcm->private_data;
+ snd_output_printf(out, "Copy conversion PCM\n");
+ if (pcm->setup) {
+ snd_output_printf(out, "Its setup is:\n");
+ snd_pcm_dump_setup(pcm, out);
+ }
+ snd_output_printf(out, "Slave: ");
+ snd_pcm_dump(pavc->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_pavc_ops = {
+ .close = snd_pcm_generic_close,
+ .info = snd_pcm_generic_info,
+ .hw_refine = snd_pcm_pavc_hw_refine,
+ .hw_params = snd_pcm_pavc_hw_params,
+ .hw_free = snd_pcm_generic_hw_free,
+ .sw_params = snd_pcm_generic_sw_params,
+ .channel_info = snd_pcm_generic_channel_info,
+ .dump = snd_pcm_pavc_dump,
+ .nonblock = snd_pcm_generic_nonblock,
+ .async = snd_pcm_generic_async,
+ .mmap = snd_pcm_generic_mmap,
+ .munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ * of compatibility reasons. The prototype might be freely
+ * changed in future.
+ */
+int snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t
*slave, int close_slave)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_pavc_t *pavc;
+ int err;
+ assert(pcmp && slave);
+ pavc = calloc(1, sizeof(snd_pcm_pavc_t));
+ if (!pavc) {
+ return -ENOMEM;
+ }
+ snd_pcm_plugin_init(&pavc->plug);
+ pavc->plug.read = snd_pcm_pavc_read_areas;
+ pavc->plug.write = snd_pcm_pavc_write_areas;
+ pavc->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+ pavc->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+ pavc->plug.gen.slave = slave;
+ pavc->plug.gen.close_slave = close_slave;
+
+ err = snd_pcm_new(&pcm, SND_PCM_TYPE_PAVC, name, slave->stream,
slave->mode);
+ if (err < 0) {
+ free(pavc);
+ return err;
+ }
+ pcm->ops = &snd_pcm_pavc_ops;
+ pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+ pcm->private_data = pavc;
+ pcm->poll_fd = slave->poll_fd;
+ pcm->poll_events = slave->poll_events;
+ pcm->monotonic = slave->monotonic;
+ snd_pcm_set_hw_ptr(pcm, &pavc->plug.hw_ptr, -1, 0);
+ snd_pcm_set_appl_ptr(pcm, &pavc->plug.appl_ptr, -1, 0);
+ *pcmp = pcm;
+
+ return 0;
+}
+
+int countString(char * x,char x2,int count)
+{
+ int i;
+ int z=0;
+ for(i=0;i<count;++i)
+ {
+ if(x[i]==x2)
+ {
+ ++z;
+ }
+ }
+ return z;
+}
+
+char *deleteChar(char *x, char x2, int count)
+{
+ char *ret;
+ ret=(char*) malloc(count+1);
+ int i;
+ for(i=0;i<count;++i)
+ {
+ ret[i]=0;
+ }
+ for(i=0;i<count;++i)
+ {
+ if(x[i]!=x2)
+ {
+ ret[i]=x[i];
+ }
+ else
+ {
+ ret[i]=' ';
+ }
+ }
+ return ret;
+}
+
+char *getProceses()
+{
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ int i=0,j=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ char *buf=(char*)malloc(sizeof(char)*i);
+
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='p' && c!='\n')
+ {
+ buf[i]=c;
+ }
+ else if(c=='p')
+ {
+ buf[i]=' ';
+ }
+ else if(c=='\n')
+ {
+ buf[i]=',';
+ }
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ return buf;
+}
+
+char *loadPid(char *name)
+{
+ FILE *file;
+ int c,i=0,j=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ char *buf=malloc((sizeof(char))*i);
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ if(c!=EOF && c!='\n')
+ buf[i]=c;
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ return buf;
+ }
+ else
+ {
+ return NULL;
+ }
+
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void savePid(char *pid,char *file)
+{
+ int i;
+ char command3[256];
+
+ for(i=0;i<256;++i)
+ {
+ command3[i]=0;
+ }
+
+ strcat(command3,"echo ");
+ strcat(command3,pid);
+ strcat(command3," > ");
+ strcat(command3,file);
+
+ for(i=5;i<256;++i)
+ {
+ if(command3[i]!=EOF && command3[i]!='\n' && command3[i]!='*')
+ {
+ command3[i]=command3[i];
+ }
+ else
+ {
+ command3[i]=' ';
+ }
+ }
+
+ system(command3);
+}
+
+int checkDir(char *name)
+{
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,"ls -aF ");
+ strcat(command,getenv("HOME"));
+ strcat(command," | grep \\./$ | grep -w ");
+ strcat(command,name);
+
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen(command,"r")) )
+ {
+ perror("Problems with pipe");
+ return -1;
+ }
+
+ int i=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='\n')
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ if(i>0)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+int createDir(char *name)
+{
+ pid_t PID;
+ int status;
+
+ PID=fork();
+
+ if(PID>0)
+ {
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,".softvol");
+
+ execlp("mkdir","mkdir",command,NULL);
+ }
+ else
+ {
+ pid_t wpid = waitpid(PID, &status, WUNTRACED);
+ }
+
+ return WEXITSTATUS(status);
+}
+
+char *getDir(char *name)
+{
+ static char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,name);
+
+ return command;
+}
+
+char *getSoftvolNumber(int channelCount,int current)
+{
+ size_t i;
+ static char ret[4];
+ memset(ret,0,5);
+ char buf1[4],buf2[4];
+ sprintf(buf1,"%i",channelCount);
+ sprintf(buf2,"%i",current);
+
+ if(strlen(buf2)<strlen(buf1))
+ {
+ for(i=0;i<(strlen(buf1)-1);++i)
+ {
+ ret[i]='0';
+ }
+
+ strcat(ret,buf2);
+
+ return ret;
+ }
+ else if(strlen(buf2)==strlen(buf1))
+ {
+ strcat(ret,buf2);
+ return ret;
+ }
+ else
+ {
+ //this should never happen
+ return NULL;
+ }
+}
+
+int checkSoftvolProcess(int channelcount)
+{
+ int i=0,c=0,ret=channelcount-1,j=0;
+ char nameBuf[1024];
+ char pidBuf[32];
+ char numBuf[4];
+ char command[128];
+ char fileName[64];
+
+ FILE *fpipe;
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(nameBuf,0,1024);
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if current process is already conected to something
+ FILE *temp=fopen(fileName,"r");
+ if(temp!=NULL)
+ {
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+ }
+ else
+ {
+ memset(nameBuf,0,1024);
+ }
+
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the sound - so
we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //Current process is not connected to anything - so we connect it
+ //to the firs unused softvol output
+ memset(fileName,0,64);
+ }
+ }
+
+ if(fileName[0]!=0)
+ {
+ //Current process is already using some softvol output - so we
+ //reconnect it to this output
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+ FILE *temp=fopen(fileName,"r");
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+
+ char pidBuf[32];
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the
sound - so we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //It is some other process - so we connect it
+ //to the first unused softvol output
+ ret=i+1;
+ }
+ }
+ else
+ {
+ ret=i;
+ }
+ }
+ }
+ else
+ {
+ //Process isn't using any softvol output - so we connect it
+ //to the first unused on the list
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+
+ }
+ else
+ {
+ ret=i;
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_copy Plugin: copy
+
+This plugin copies samples from master copy PCM to given slave PCM.
+The channel count, format and rate must match for both of them.
+
+\code
+pcm.name {
+ type copy # Copy PCM
+ slave STR # Slave name
+ # or
+ slave { # Slave definition
+ pcm STR # Slave PCM name
+ # or
+ pcm { } # Slave PCM definition
+ }
+}
+\endcode
+
+\subsection pcm_plugins_copy_funcref Function reference
+
+<UL>
+ <LI>snd_pcm_copy_open()
+ <LI>_snd_pcm_copy_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ * of compatibility reasons. The prototype might be freely
+ * changed in future.
+ */
+int _snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name,
+ snd_config_t *root, snd_config_t *conf,
+ snd_pcm_stream_t stream, int mode)
+{
+ long channelcount=-1;
+ int ret=0;
+ char softvolName[64];
+ char pidBuf[32];
+ char fileName[64];
+ char helpBuf[32];
+
+ memset(softvolName,0,64);
+
+ snd_config_iterator_t i, next;
+ int err;
+ snd_pcm_t *spcm;
+ snd_config_t *slave = NULL, *sconf;
+
+ snd_config_t *tempConfig;
+ err=snd_config_search(conf,"channelcount",&tempConfig);
+ if(err<0)
+ {
+ SNDERR("channelcount field not found");
+ return -ENOENT;
+ }
+ err=snd_config_get_integer(tempConfig,&channelcount);
+ if(err<0)
+ {
+ SNDERR("channelcount vaule must be integer");
+ return -EINVAL;
+ }
+
+ snd_config_t *softvol[channelcount];
+
+ int counter=0;
+
+ sprintf(softvolName,"softvol%i",counter);
+
+ snd_config_for_each(i, next, conf)
+ {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (snd_pcm_conf_generic_id(id))
+ continue;
+ if (strcmp(id, "slave") == 0)
+ {
+ slave = n;
+ continue;
+ }
+ if (strcmp(id, "channelcount") == 0)
+ {
+ continue;
+ }
+ if((counter<channelcount) && strcmp(id,softvolName)==0)
+ {
+ softvol[counter]=n;
+ ++counter;
+ sprintf(softvolName,"softvol%i",counter);
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (!slave) {
+ SNDERR("slave is not defined");
+ return -EINVAL;
+ }
+
+ if(checkDir(".softvol"))
+ {
+ //Directory exists - we do nothing
+ }
+ else
+ {
+ //Directory doesn't exists we need to create it
+ if((err=createDir(".softvol"))!=0)
+ {
+ SNDERR("Unable to create ~/.softvol directory - unknown
error %i",err);
+ return err;
+ }
+ }
+
+ ret=checkSoftvolProcess(channelcount);
+
+ err = snd_pcm_slave_conf(root, softvol[ret], &sconf, 0);
+
+ if (err < 0)
+ return err;
+ err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+ snd_config_delete(sconf);
+ if (err < 0)
+ return err;
+ err = snd_pcm_pavc_open(pcmp, name, spcm, 1);
+ if (err < 0)
+ snd_pcm_close(spcm);
+
+ sprintf(pidBuf,"%d",getpid());
+ memset(fileName,0,64);
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+
+ strcat(fileName,getSoftvolNumber(channelcount,ret));
+ strcat(fileName,".pid");
+
+ savePid(pidBuf,fileName);
+
+ return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_pavc_open, SND_PCM_DLSYM_VERSION);
+#endif
To test PAVC, we have to rebuild alsa-lib, and create .asoundrc or
/etc/asound.conf that looks like this:
pcm.!default {
type plug
slave.pcm "asymed"
}
pcm.asymed
{
type asym
playback.pcm "pavcp"
capture.pcm "dsnooped"
}
pcm.dmixer {
type dmix
ipc_key 1025
slave {
pcm "hw:0"
period_time 0
period_size 256
#buffer_size 4096
periods 128
rate 44100
}
}
pcm.dsnooped {
type dsnoop
ipc_key 1026
slave
{
pcm "hw:0"
channels 2
period_size 256
#buffer_size 4096
rate 44100
periods 0
period_time 0
}
}
pcm.softvol00 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol00"
card 0
}
}
pcm.softvol01 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol01"
card 0
}
}
pcm.softvol02 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol02"
card 0
}
}
pcm.softvol03 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol03"
card 0
}
}
pcm.softvol04 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol04"
card 0
}
}
pcm.softvol05 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol05"
card 0
}
}
pcm.softvol06 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol06"
card 0
}
}
pcm.softvol07 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol07"
card 0
}
}
pcm.softvol08 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol08"
card 0
}
}
pcm.softvol09 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol09"
card 0
}
}
pcm.softvol10 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol10"
card 0
}
}
pcm.softvol11 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol11"
card 0
}
}
ctl.dmixer {
type hw
card 0
}
ctl.dsnooped {
type hw
card 0
}
pcm.pavcp
{
type pavc
slave.pcm "dmixer"
channelcount 12
softvol0.pcm "softvol00"
softvol1.pcm "softvol01"
softvol2.pcm "softvol02"
softvol3.pcm "softvol03"
softvol4.pcm "softvol04"
softvol5.pcm "softvol05"
softvol6.pcm "softvol06"
softvol7.pcm "softvol07"
softvol8.pcm "softvol08"
softvol9.pcm "softvol09"
softvol10.pcm "softvol10"
softvol11.pcm "softvol11"
}
With this, anything connected to "default" will be automatically
redirected to the first unused softvol channel. PIDs of apps that
currently use softvol channels are saved to ~/.softvol/SOFTVOLXX.pid,
so this can be checked at any time. If some application will try to
restart only its sound subsystem, PAVC plugin will check those files
to determine if it should be connected to the new softvol channel, or
the one it currently uses. I tried to mimic the way this works with
OSS4.
Current limitations:
-Since there is no "mute and slider at the same time" support in
SOFTVOL plugin - there is no mute switch for software channels (or
maybe there is a way I don't know about?)
-Software channels must be named SOFTVOL00,01,02 and so on
-SOFTVOL channels have both PLAYBACK and CAPTURE capabilities by
default - this makes some mixers go haywire, and display double
sliders - maybe there is a way around this I don't know about (like
setting PLAYBACK only capability)?
-Code of PAVC, could probably look nicer
Hopefully someone will take a look at this, of course I am open to
suggestions and critics.
Best regards.
P.S
Sorry for my English ;]
4
7
This patch adds the MAX98088 CODEC driver.
Signed-off-by: Peter Hsiang <peter.hsiang(a)maxim-ic.com>
---
include/sound/max98088.h | 54 +
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/max98088.c | 2355 +++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/max98088.h | 190 ++++
5 files changed, 2605 insertions(+), 0 deletions(-)
create mode 100644 include/sound/max98088.h
create mode 100644 sound/soc/codecs/max98088.c
create mode 100644 sound/soc/codecs/max98088.h
diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..30652be
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,54 @@
+/*
+ * Platform data for MAX98088
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+ const char *name;
+ unsigned int rate;
+ u16 band1[5];
+ u16 band2[5];
+ u16 band3[5];
+ u16 band4[5];
+ u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+ /* Equalizers for DAI1 and DAI2 */
+ struct max98088_eq_cfg *eq1_cfg;
+ struct max98088_eq_cfg *eq2_cfg;
+ unsigned int eq1_cfgcnt;
+ unsigned int eq2_cfgcnt;
+
+ /* Receiver output can be configured as power amplifier or LINE out */
+ /* Set receiver_mode to:
+ * 0 = amplifier output, or
+ * 1 = LINE level output
+ */
+ unsigned int receiver_mode:1;
+
+ /* Analog/digital microphone configuration:
+ * 0 = analog microphone input (normal setting)
+ * 1 = digital microphone input
+ */
+ unsigned int digmic_left_mode:1;
+ unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..4e6713c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CS4270 if I2C
select SND_SOC_DA7210 if I2C
select SND_SOC_JZ4740 if SOC_JZ4740
+ select SND_SOC_MAX98088 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_PCM3008
select SND_SOC_SPDIF
@@ -157,6 +158,9 @@ config SND_SOC_L3
config SND_SOC_DA7210
tristate
+config SND_SOC_MAX98088
+ tristate
+
config SND_SOC_PCM3008
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..7184611 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
@@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..52f57d5
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2355 @@
+/*
+ * max98088.c -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+/* Configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+ unsigned int rate;
+ unsigned int fmt;
+ int eq_textcnt;
+ const char *eq_texts[EQ_CFG_MAX];
+ int eq_sel;
+ struct soc_enum eq_enum;
+};
+
+/* Codec private data */
+struct max98088_priv {
+ u8 reg_cache[M98088_REG_CNT];
+ void *control_data;
+ struct max98088_pdata *pdata;
+
+ unsigned int sysclk;
+ struct max98088_cdata dai[2];
+ u8 power_state;
+ unsigned int ex_mode;
+ unsigned int digmic;
+ unsigned int mic1pre;
+ unsigned int mic2pre;
+ unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+ 0x00, /* 00 IRQ status */
+ 0x00, /* 01 MIC status */
+ 0x00, /* 02 jack status */
+ 0x00, /* 03 battery voltage */
+ 0x00, /* 04 */
+ 0x00, /* 05 */
+ 0x00, /* 06 */
+ 0x00, /* 07 */
+ 0x00, /* 08 */
+ 0x00, /* 09 */
+ 0x00, /* 0A */
+ 0x00, /* 0B */
+ 0x00, /* 0C */
+ 0x00, /* 0D */
+ 0x00, /* 0E */
+ 0x00, /* 0F interrupt enable */
+
+ 0x00, /* 10 master clock */
+ 0x00, /* 11 DAI1 clock mode */
+ 0x00, /* 12 DAI1 clock control */
+ 0x00, /* 13 DAI1 clock control */
+ 0x00, /* 14 DAI1 format */
+ 0x00, /* 15 DAI1 clock */
+ 0x00, /* 16 DAI1 config */
+ 0x00, /* 17 DAI1 TDM */
+ 0x00, /* 18 DAI1 filters */
+ 0x00, /* 19 DAI2 clock mode */
+ 0x00, /* 1A DAI2 clock control */
+ 0x00, /* 1B DAI2 clock control */
+ 0x00, /* 1C DAI2 format */
+ 0x00, /* 1D DAI2 clock */
+ 0x00, /* 1E DAI2 config */
+ 0x00, /* 1F DAI2 TDM */
+
+ 0x00, /* 20 DAI2 filters */
+ 0x00, /* 21 data config */
+ 0x00, /* 22 DAC mixer */
+ 0x00, /* 23 left ADC mixer */
+ 0x00, /* 24 right ADC mixer */
+ 0x00, /* 25 left HP mixer */
+ 0x00, /* 26 right HP mixer */
+ 0x00, /* 27 HP control */
+ 0x00, /* 28 left REC mixer */
+ 0x00, /* 29 right REC mixer */
+ 0x00, /* 2A REC control */
+ 0x00, /* 2B left SPK mixer */
+ 0x00, /* 2C right SPK mixer */
+ 0x00, /* 2D SPK control */
+ 0x00, /* 2E sidetone */
+ 0x00, /* 2F DAI1 playback level */
+
+ 0x00, /* 30 DAI1 playback level */
+ 0x00, /* 31 DAI2 playback level */
+ 0x00, /* 32 DAI2 playbakc level */
+ 0x00, /* 33 left ADC level */
+ 0x00, /* 34 right ADC level */
+ 0x00, /* 35 MIC1 level */
+ 0x00, /* 36 MIC2 level */
+ 0x00, /* 37 INA level */
+ 0x00, /* 38 INB level */
+ 0x00, /* 39 left HP volume */
+ 0x00, /* 3A right HP volume */
+ 0x00, /* 3B left REC volume */
+ 0x00, /* 3C right REC volume */
+ 0x00, /* 3D left SPK volume */
+ 0x00, /* 3E right SPK volume */
+ 0x00, /* 3F MIC config */
+
+ 0x00, /* 40 MIC threshold */
+ 0x00, /* 41 excursion limiter filter */
+ 0x00, /* 42 excursion limiter threshold */
+ 0x00, /* 43 ALC */
+ 0x00, /* 44 power limiter threshold */
+ 0x00, /* 45 power limiter config */
+ 0x00, /* 46 distortion limiter config */
+ 0x00, /* 47 audio input */
+ 0x00, /* 48 microphone */
+ 0x00, /* 49 level control */
+ 0x00, /* 4A bypass switches */
+ 0x00, /* 4B jack detect */
+ 0x00, /* 4C input enable */
+ 0x00, /* 4D output enable */
+ 0xF0, /* 4E bias control */
+ 0x00, /* 4F DAC power */
+
+ 0x0F, /* 50 DAC power */
+ 0x00, /* 51 system */
+ 0x00, /* 52 DAI1 EQ1 */
+ 0x00, /* 53 DAI1 EQ1 */
+ 0x00, /* 54 DAI1 EQ1 */
+ 0x00, /* 55 DAI1 EQ1 */
+ 0x00, /* 56 DAI1 EQ1 */
+ 0x00, /* 57 DAI1 EQ1 */
+ 0x00, /* 58 DAI1 EQ1 */
+ 0x00, /* 59 DAI1 EQ1 */
+ 0x00, /* 5A DAI1 EQ1 */
+ 0x00, /* 5B DAI1 EQ1 */
+ 0x00, /* 5C DAI1 EQ2 */
+ 0x00, /* 5D DAI1 EQ2 */
+ 0x00, /* 5E DAI1 EQ2 */
+ 0x00, /* 5F DAI1 EQ2 */
+
+ 0x00, /* 60 DAI1 EQ2 */
+ 0x00, /* 61 DAI1 EQ2 */
+ 0x00, /* 62 DAI1 EQ2 */
+ 0x00, /* 63 DAI1 EQ2 */
+ 0x00, /* 64 DAI1 EQ2 */
+ 0x00, /* 65 DAI1 EQ2 */
+ 0x00, /* 66 DAI1 EQ3 */
+ 0x00, /* 67 DAI1 EQ3 */
+ 0x00, /* 68 DAI1 EQ3 */
+ 0x00, /* 69 DAI1 EQ3 */
+ 0x00, /* 6A DAI1 EQ3 */
+ 0x00, /* 6B DAI1 EQ3 */
+ 0x00, /* 6C DAI1 EQ3 */
+ 0x00, /* 6D DAI1 EQ3 */
+ 0x00, /* 6E DAI1 EQ3 */
+ 0x00, /* 6F DAI1 EQ3 */
+
+ 0x00, /* 70 DAI1 EQ4 */
+ 0x00, /* 71 DAI1 EQ4 */
+ 0x00, /* 72 DAI1 EQ4 */
+ 0x00, /* 73 DAI1 EQ4 */
+ 0x00, /* 74 DAI1 EQ4 */
+ 0x00, /* 75 DAI1 EQ4 */
+ 0x00, /* 76 DAI1 EQ4 */
+ 0x00, /* 77 DAI1 EQ4 */
+ 0x00, /* 78 DAI1 EQ4 */
+ 0x00, /* 79 DAI1 EQ4 */
+ 0x00, /* 7A DAI1 EQ5 */
+ 0x00, /* 7B DAI1 EQ5 */
+ 0x00, /* 7C DAI1 EQ5 */
+ 0x00, /* 7D DAI1 EQ5 */
+ 0x00, /* 7E DAI1 EQ5 */
+ 0x00, /* 7F DAI1 EQ5 */
+
+ 0x00, /* 80 DAI1 EQ5 */
+ 0x00, /* 81 DAI1 EQ5 */
+ 0x00, /* 82 DAI1 EQ5 */
+ 0x00, /* 83 DAI1 EQ5 */
+ 0x00, /* 84 DAI2 EQ1 */
+ 0x00, /* 85 DAI2 EQ1 */
+ 0x00, /* 86 DAI2 EQ1 */
+ 0x00, /* 87 DAI2 EQ1 */
+ 0x00, /* 88 DAI2 EQ1 */
+ 0x00, /* 89 DAI2 EQ1 */
+ 0x00, /* 8A DAI2 EQ1 */
+ 0x00, /* 8B DAI2 EQ1 */
+ 0x00, /* 8C DAI2 EQ1 */
+ 0x00, /* 8D DAI2 EQ1 */
+ 0x00, /* 8E DAI2 EQ2 */
+ 0x00, /* 8F DAI2 EQ2 */
+
+ 0x00, /* 90 DAI2 EQ2 */
+ 0x00, /* 91 DAI2 EQ2 */
+ 0x00, /* 92 DAI2 EQ2 */
+ 0x00, /* 93 DAI2 EQ2 */
+ 0x00, /* 94 DAI2 EQ2 */
+ 0x00, /* 95 DAI2 EQ2 */
+ 0x00, /* 96 DAI2 EQ2 */
+ 0x00, /* 97 DAI2 EQ2 */
+ 0x00, /* 98 DAI2 EQ3 */
+ 0x00, /* 99 DAI2 EQ3 */
+ 0x00, /* 9A DAI2 EQ3 */
+ 0x00, /* 9B DAI2 EQ3 */
+ 0x00, /* 9C DAI2 EQ3 */
+ 0x00, /* 9D DAI2 EQ3 */
+ 0x00, /* 9E DAI2 EQ3 */
+ 0x00, /* 9F DAI2 EQ3 */
+
+ 0x00, /* A0 DAI2 EQ3 */
+ 0x00, /* A1 DAI2 EQ3 */
+ 0x00, /* A2 DAI2 EQ4 */
+ 0x00, /* A3 DAI2 EQ4 */
+ 0x00, /* A4 DAI2 EQ4 */
+ 0x00, /* A5 DAI2 EQ4 */
+ 0x00, /* A6 DAI2 EQ4 */
+ 0x00, /* A7 DAI2 EQ4 */
+ 0x00, /* A8 DAI2 EQ4 */
+ 0x00, /* A9 DAI2 EQ4 */
+ 0x00, /* AA DAI2 EQ4 */
+ 0x00, /* AB DAI2 EQ4 */
+ 0x00, /* AC DAI2 EQ5 */
+ 0x00, /* AD DAI2 EQ5 */
+ 0x00, /* AE DAI2 EQ5 */
+ 0x00, /* AF DAI2 EQ5 */
+
+ 0x00, /* B0 DAI2 EQ5 */
+ 0x00, /* B1 DAI2 EQ5 */
+ 0x00, /* B2 DAI2 EQ5 */
+ 0x00, /* B3 DAI2 EQ5 */
+ 0x00, /* B4 DAI2 EQ5 */
+ 0x00, /* B5 DAI2 EQ5 */
+ 0x00, /* B6 DAI1 biquad */
+ 0x00, /* B7 DAI1 biquad */
+ 0x00, /* B8 DAI1 biquad */
+ 0x00, /* B9 DAI1 biquad */
+ 0x00, /* BA DAI1 biquad */
+ 0x00, /* BB DAI1 biquad */
+ 0x00, /* BC DAI1 biquad */
+ 0x00, /* BD DAI1 biquad */
+ 0x00, /* BE DAI1 biquad */
+ 0x00, /* BF DAI1 biquad */
+
+ 0x00, /* C0 DAI2 biquad */
+ 0x00, /* C1 DAI2 biquad */
+ 0x00, /* C2 DAI2 biquad */
+ 0x00, /* C3 DAI2 biquad */
+ 0x00, /* C4 DAI2 biquad */
+ 0x00, /* C5 DAI2 biquad */
+ 0x00, /* C6 DAI2 biquad */
+ 0x00, /* C7 DAI2 biquad */
+ 0x00, /* C8 DAI2 biquad */
+ 0x00, /* C9 DAI2 biquad */
+ 0x00, /* CA */
+ 0x00, /* CB */
+ 0x00, /* CC */
+ 0x00, /* CD */
+ 0x00, /* CE */
+ 0x00, /* CF */
+
+ 0x00, /* D0 */
+ 0x00, /* D1 */
+ 0x00, /* D2 */
+ 0x00, /* D3 */
+ 0x00, /* D4 */
+ 0x00, /* D5 */
+ 0x00, /* D6 */
+ 0x00, /* D7 */
+ 0x00, /* D8 */
+ 0x00, /* D9 */
+ 0x00, /* DA */
+ 0x70, /* DB */
+ 0x00, /* DC */
+ 0x00, /* DD */
+ 0x00, /* DE */
+ 0x00, /* DF */
+
+ 0x00, /* E0 */
+ 0x00, /* E1 */
+ 0x00, /* E2 */
+ 0x00, /* E3 */
+ 0x00, /* E4 */
+ 0x00, /* E5 */
+ 0x00, /* E6 */
+ 0x00, /* E7 */
+ 0x00, /* E8 */
+ 0x00, /* E9 */
+ 0x00, /* EA */
+ 0x00, /* EB */
+ 0x00, /* EC */
+ 0x00, /* ED */
+ 0x00, /* EE */
+ 0x00, /* EF */
+
+ 0x00, /* F0 */
+ 0x00, /* F1 */
+ 0x00, /* F2 */
+ 0x00, /* F3 */
+ 0x00, /* F4 */
+ 0x00, /* F5 */
+ 0x00, /* F6 */
+ 0x00, /* F7 */
+ 0x00, /* F8 */
+ 0x00, /* F9 */
+ 0x00, /* FA */
+ 0x00, /* FB */
+ 0x00, /* FC */
+ 0x00, /* FD */
+ 0x00, /* FE */
+ 0x00, /* FF */
+};
+
+static struct {
+ int readable;
+ int writable;
+ int vol;
+} max98088_access[M98088_REG_CNT] = {
+ { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+ { 0xFF, 0x00, 1 }, /* 01 MIC status */
+ { 0xFF, 0x00, 1 }, /* 02 jack status */
+ { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+ { 0xFF, 0xFF, 0 }, /* 04 */
+ { 0xFF, 0xFF, 0 }, /* 05 */
+ { 0xFF, 0xFF, 0 }, /* 06 */
+ { 0xFF, 0xFF, 0 }, /* 07 */
+ { 0xFF, 0xFF, 0 }, /* 08 */
+ { 0xFF, 0xFF, 0 }, /* 09 */
+ { 0xFF, 0xFF, 0 }, /* 0A */
+ { 0xFF, 0xFF, 0 }, /* 0B */
+ { 0xFF, 0xFF, 0 }, /* 0C */
+ { 0xFF, 0xFF, 0 }, /* 0D */
+ { 0xFF, 0xFF, 0 }, /* 0E */
+ { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+ { 0xFF, 0xFF, 0 }, /* 10 master clock */
+ { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+ { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+ { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+ { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+ { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+ { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+ { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+ { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+ { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+ { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+ { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+ { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+ { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+ { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+ { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+ { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+ { 0xFF, 0xFF, 0 }, /* 21 data config */
+ { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+ { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+ { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+ { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+ { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+ { 0xFF, 0xFF, 0 }, /* 27 HP control */
+ { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+ { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+ { 0xFF, 0xFF, 0 }, /* 2A REC control */
+ { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+ { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+ { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+ { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+ { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+ { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+ { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+ { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+ { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+ { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+ { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+ { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+ { 0xFF, 0xFF, 0 }, /* 37 INA level */
+ { 0xFF, 0xFF, 0 }, /* 38 INB level */
+ { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+ { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+ { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+ { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+ { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+ { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+ { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+ { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+ { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+ { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+ { 0xFF, 0xFF, 0 }, /* 43 ALC */
+ { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+ { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+ { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+ { 0xFF, 0xFF, 0 }, /* 47 audio input */
+ { 0xFF, 0xFF, 0 }, /* 48 microphone */
+ { 0xFF, 0xFF, 0 }, /* 49 level control */
+ { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+ { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+ { 0xFF, 0xFF, 0 }, /* 4C input enable */
+ { 0xFF, 0xFF, 0 }, /* 4D output enable */
+ { 0xFF, 0xFF, 0 }, /* 4E bias control */
+ { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+ { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+ { 0xFF, 0xFF, 0 }, /* 51 system */
+ { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+ { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+ { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+ { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+ { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+ { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+ { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+ { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+ { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+ { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+ { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+ { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+ { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+ { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+ { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+ { 0x00, 0x00, 0 }, /* CA */
+ { 0x00, 0x00, 0 }, /* CB */
+ { 0x00, 0x00, 0 }, /* CC */
+ { 0x00, 0x00, 0 }, /* CD */
+ { 0x00, 0x00, 0 }, /* CE */
+ { 0x00, 0x00, 0 }, /* CF */
+
+ { 0x00, 0x00, 0 }, /* D0 */
+ { 0x00, 0x00, 0 }, /* D1 */
+ { 0x00, 0x00, 0 }, /* D2 */
+ { 0x00, 0x00, 0 }, /* D3 */
+ { 0x00, 0x00, 0 }, /* D4 */
+ { 0x00, 0x00, 0 }, /* D5 */
+ { 0x00, 0x00, 0 }, /* D6 */
+ { 0x00, 0x00, 0 }, /* D7 */
+ { 0x00, 0x00, 0 }, /* D8 */
+ { 0x00, 0x00, 0 }, /* D9 */
+ { 0x00, 0x00, 0 }, /* DA */
+ { 0x00, 0x00, 0 }, /* DB */
+ { 0x00, 0x00, 0 }, /* DC */
+ { 0x00, 0x00, 0 }, /* DD */
+ { 0x00, 0x00, 0 }, /* DE */
+ { 0x00, 0x00, 0 }, /* DF */
+
+ { 0x00, 0x00, 0 }, /* E0 */
+ { 0x00, 0x00, 0 }, /* E1 */
+ { 0x00, 0x00, 0 }, /* E2 */
+ { 0x00, 0x00, 0 }, /* E3 */
+ { 0x00, 0x00, 0 }, /* E4 */
+ { 0x00, 0x00, 0 }, /* E5 */
+ { 0x00, 0x00, 0 }, /* E6 */
+ { 0x00, 0x00, 0 }, /* E7 */
+ { 0x00, 0x00, 0 }, /* E8 */
+ { 0x00, 0x00, 0 }, /* E9 */
+ { 0x00, 0x00, 0 }, /* EA */
+ { 0x00, 0x00, 0 }, /* EB */
+ { 0x00, 0x00, 0 }, /* EC */
+ { 0x00, 0x00, 0 }, /* ED */
+ { 0x00, 0x00, 0 }, /* EE */
+ { 0x00, 0x00, 0 }, /* EF */
+
+ { 0x00, 0x00, 0 }, /* F0 */
+ { 0x00, 0x00, 0 }, /* F1 */
+ { 0x00, 0x00, 0 }, /* F2 */
+ { 0x00, 0x00, 0 }, /* F3 */
+ { 0x00, 0x00, 0 }, /* F4 */
+ { 0x00, 0x00, 0 }, /* F5 */
+ { 0x00, 0x00, 0 }, /* F6 */
+ { 0x00, 0x00, 0 }, /* F7 */
+ { 0x00, 0x00, 0 }, /* F8 */
+ { 0x00, 0x00, 0 }, /* F9 */
+ { 0x00, 0x00, 0 }, /* FA */
+ { 0x00, 0x00, 0 }, /* FB */
+ { 0x00, 0x00, 0 }, /* FC */
+ { 0x00, 0x00, 0 }, /* FD */
+ { 0x00, 0x00, 0 }, /* FE */
+ { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+ return max98088_access[reg].vol;
+}
+
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ data[0] = reg;
+ data[1] = value;
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+ unsigned long long int divisor)
+{
+ unsigned long long int quotient = 0;
+ int shift = 1;
+
+ BUG_ON(divisor == 0);
+
+ /* Result is 1.0 if divisor and dividend are equal */
+ if (divisor == dividend)
+ return 1;
+
+ /* Normalize divisor */
+ while (!(divisor & 0x8000000000000000ULL)) {
+ divisor <<= 1;
+ ++shift;
+ }
+
+ /* Shift and subtract */
+ while (shift--) {
+ quotient <<= 1;
+
+ if (divisor <= dividend) {
+ dividend -= divisor;
+ ++quotient;
+ }
+ divisor >>= 1;
+ }
+
+ /* Round up */
+ if (dividend > divisor)
+ ++quotient;
+
+ return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs share a power control signal.
+ * This function OR's the two power events to keep an unpowered INx
+ * from turning off it's counterpart.
+ * The control names are used to identify the PGA.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ u8 *state = &max98088->power_state;
+ unsigned int val;
+ unsigned int pga;
+ unsigned int mask;
+
+ BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+ if (strncmp(w->name, "INA1", 4) == 0) {
+ pga = INA1_PGA_BIT;
+ mask = INA1_PGA_BIT | INA2_PGA_BIT;
+ } else if (strncmp(w->name, "INA2", 4) == 0) {
+ pga = INA2_PGA_BIT;
+ mask = INA1_PGA_BIT | INA2_PGA_BIT;
+ } else if (strncmp(w->name, "INB1", 4) == 0) {
+ pga = INB1_PGA_BIT;
+ mask = INB1_PGA_BIT | INB2_PGA_BIT;
+ } else if (strncmp(w->name, "INB2", 4) == 0) {
+ pga = INB2_PGA_BIT;
+ mask = INB1_PGA_BIT | INB2_PGA_BIT;
+ } else {
+ return -EINVAL;
+ }
+
+ if (event == SND_SOC_DAPM_POST_PMU) {
+ /* ON */
+ *state |= pga;
+
+ /* Turn on, avoiding unnecessary writes */
+ val = snd_soc_read(codec, w->reg);
+ if (!(val & (1 << w->shift))) {
+ val |= (1 << w->shift);
+ snd_soc_write(codec, w->reg, val);
+ }
+ } else if (event == SND_SOC_DAPM_POST_PMD) {
+ /* OFF */
+ *state &= ~pga;
+
+ /* Turn off if both are off, avoiding unnecessary writes */
+ if (!(*state & mask)) {
+ val = snd_soc_read(codec, w->reg);
+ if (val & (1 << w->shift)) {
+ val &= ~(1 << w->shift);
+ snd_soc_write(codec, w->reg, val);
+ }
+ }
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int band, u16 *coefs)
+{
+ unsigned int eq_reg;
+ unsigned int i;
+
+ BUG_ON(band > 4);
+ BUG_ON(dai > 1);
+
+ /* Load the base register address */
+ eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+ /* Add the band address offset, note adjustment for word address */
+ eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+ /* Step through the registers and coefs */
+ for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+ snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+ snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+ }
+
+ return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+ "Off",
+ "100Hz",
+ "400Hz",
+ "600Hz",
+ "800Hz",
+ "1000Hz",
+ "200-400Hz",
+ "400-600Hz",
+ "400-800Hz",
+};
+
+static const unsigned int ex_mode_table[] = {
+ 0x00, /* disabled */
+ (0<<4)|3, /* 100Hz */
+ (1<<4)|0, /* 400Hz */
+ (2<<4)|0, /* 600Hz */
+ (3<<4)|0, /* 800Hz */
+ (4<<4)|0, /* 1000Hz */
+ (1<<4)|1, /* 200-400Hz */
+ (2<<4)|2, /* 400-600Hz */
+ (3<<4)|2, /* 400-800Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_ex_mode), max98088_ex_mode),
+};
+
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->ex_mode;
+ int sel = ucontrol->value.integer.value[0];
+
+ if (sel >= ARRAY_SIZE(ex_mode_table))
+ return -EINVAL;
+
+ *mode = ucontrol->value.integer.value[0];
+ snd_soc_write(codec, M98088_REG_41_SPKDHP,
+ ex_mode_table[*mode]);
+
+ return 0;
+}
+
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->ex_mode;
+
+ ucontrol->value.integer.value[0] = *mode;
+ return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+ "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+ max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+ "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+ "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+ SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_micpre[] = {
+ "0dB",
+ "20dB",
+ "30dB",
+};
+
+static const struct soc_enum max98088_micpre_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
+};
+
+static const char *max98088_extmic[] = {
+ "Off",
+ "MIC1",
+ "MIC2",
+};
+
+static const struct soc_enum max98088_extmic_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->mic1pre;
+ int sel = ucontrol->value.integer.value[0];
+
+ if (sel >= ARRAY_SIZE(max98088_micpre))
+ return -EINVAL;
+
+ *mode = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->mic1pre;
+
+ ucontrol->value.integer.value[0] = *mode;
+ return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->mic2pre;
+ int sel = ucontrol->value.integer.value[0];
+
+ if (sel >= ARRAY_SIZE(max98088_micpre))
+ return -EINVAL;
+
+ *mode = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->mic2pre;
+
+ ucontrol->value.integer.value[0] = *mode;
+ return 0;
+}
+
+static int max98088_extmic_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->extmic_mode;
+ int sel = ucontrol->value.integer.value[0];
+
+ if (sel >= ARRAY_SIZE(max98088_extmic))
+ return -EINVAL;
+
+ *mode = sel;
+ snd_soc_update_bits(codec, M98088_REG_48_CFG_MIC,
+ M98088_EXTMIC_MASK, sel);
+
+ return 0;
+}
+
+static int max98088_extmic_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ unsigned int *mode = &max98088->extmic_mode;
+
+ ucontrol->value.integer.value[0] = *mode;
+ return 0;
+}
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+ /* Analog outputs */
+
+ SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+ M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+ SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+ M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+ SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+ M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+ SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+ M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+ SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+ M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+ SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+ M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+ /* Analog inputs */
+
+ SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+ SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+ SOC_ENUM_EXT("MIC1 Boost Volume", max98088_micpre_enum,
+ max98088_mic1pre_get, max98088_mic1pre_set),
+
+ SOC_ENUM_EXT("MIC2 Boost Volume", max98088_micpre_enum,
+ max98088_mic2pre_get, max98088_mic2pre_set),
+
+ SOC_ENUM_EXT("Ext MIC Switch", max98088_extmic_enum,
+ max98088_extmic_get, max98088_extmic_set),
+
+ SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+ SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+ /* ADC input digital gains and volume controls */
+
+ SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+ SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+ SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+ SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+ /* Equalizer */
+
+ SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+ SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+ /* Excursion limiter */
+
+ SOC_ENUM_EXT("EX Limiter Mode", max98088_ex_mode_enum,
+ max98088_ex_mode_get, max98088_ex_mode_set),
+ SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+ /* Voice/music filters */
+
+ SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+ SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+ SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+ SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+ 0, 1, 0),
+
+ /* Automatic level control (for both DAI1/DAI2) */
+
+ SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+ SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+ SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+ SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+ /* Power limiter */
+
+ SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+ 4, 15, 0),
+ SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+ SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+ SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+ /* THD distortion limiter */
+
+ SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+ SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 status;
+
+ BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+ /* powering down headphone gracefully */
+ status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
+ if ((status & M98088_HPEN) == M98088_HPEN) {
+ max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+ (status & ~M98088_HPEN));
+ }
+ schedule_timeout(msecs_to_jiffies(20));
+
+ return 0;
+}
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (w->reg == M98088_REG_35_LVL_MIC1) {
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+ (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+ } else {
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+ (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+ SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+ SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+ SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+ M98088_REG_4D_PWR_EN_OUT, 1, 0),
+ SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+ M98088_REG_4D_PWR_EN_OUT, 0, 0),
+ SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+ M98088_REG_4D_PWR_EN_OUT, 1, 0),
+ SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+ M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+ SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 4, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+ 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+ 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_hp_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_hp_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_speaker_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_rec_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_rec_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_left_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+ &max98088_right_ADC_mixer_controls[0],
+ ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+ 5, 0, NULL, 0, max98088_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+ 5, 0, NULL, 0, max98088_mic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+ 7, 0, NULL, 0, max98088_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+ 7, 0, NULL, 0, max98088_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+ 6, 0, NULL, 0, max98088_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+ 6, 0, NULL, 0, max98088_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_OUTPUT("RECL"),
+ SND_SOC_DAPM_OUTPUT("RECR"),
+
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("INA1"),
+ SND_SOC_DAPM_INPUT("INA2"),
+ SND_SOC_DAPM_INPUT("INB1"),
+ SND_SOC_DAPM_INPUT("INB2"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Left headphone output mixer */
+ {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right headphone output mixer */
+ {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right HP Mixer", "Left DAC2 Switch", "DACL2" },
+ {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Left speaker output mixer */
+ {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right speaker output mixer */
+ {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Earpiece/Receiver output mixer */
+ {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Earpiece/Receiver output mixer */
+ {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+ {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+ {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+ {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+ {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+ {"HP Left Out", NULL, "Left HP Mixer"},
+ {"HP Right Out", NULL, "Right HP Mixer"},
+ {"SPK Left Out", NULL, "Left SPK Mixer"},
+ {"SPK Right Out", NULL, "Right SPK Mixer"},
+ {"REC Left Out", NULL, "Left REC Mixer"},
+ {"REC Right Out", NULL, "Right REC Mixer"},
+
+ {"HPL", NULL, "HP Left Out"},
+ {"HPR", NULL, "HP Right Out"},
+ {"SPKL", NULL, "SPK Left Out"},
+ {"SPKR", NULL, "SPK Right Out"},
+ {"RECL", NULL, "REC Left Out"},
+ {"RECR", NULL, "REC Right Out"},
+
+ /* Left ADC input mixer */
+ {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* Right ADC input mixer */
+ {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+ {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+ {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+ {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+ {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+ {"Right ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+ /* inputs */
+ {"ADCL", NULL, "Left ADC Mixer"},
+ {"ADCR", NULL, "Right ADC Mixer"},
+ {"INA1 Input", NULL, "INA1"},
+ {"INA2 Input", NULL, "INA2"},
+ {"INB1 Input", NULL, "INB1"},
+ {"INB2 Input", NULL, "INB2"},
+ {"MIC1 Input", NULL, "MIC1"},
+ {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+ ARRAY_SIZE(max98088_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_add_controls(codec, max98088_snd_controls,
+ ARRAY_SIZE(max98088_snd_controls));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+ u32 rate;
+ u8 sr;
+} rate_table[] = {
+ {8000, 0x10},
+ {11025, 0x20},
+ {16000, 0x30},
+ {22050, 0x40},
+ {24000, 0x50},
+ {32000, 0x60},
+ {44100, 0x70},
+ {48000, 0x80},
+ {88200, 0x90},
+ {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+ if (rate_table[i].rate >= rate) {
+ *value = rate_table[i].sr;
+ return 0;
+ }
+ }
+ *value = rate_table[0].sr;
+ return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ unsigned int rate;
+ u8 regval;
+ u16 ni;
+
+ cdata = &max98088->dai[0];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ M98088_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ M98088_DAI_WS, M98088_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+ if (rate != cdata->rate) {
+ /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+ if (rate_value(rate, ®val))
+ return -EINVAL;
+
+ snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
+ cdata->rate = rate;
+ }
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+ & M98088_DAI_MAS) {
+ if (max98088->sysclk == 0)
+ return -EINVAL;
+ ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate,
+ (unsigned long long int)max98088->sysclk);
+ snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+ (ni >> 8) & 0x7f);
+ snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+ ni & 0xff);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+ M98088_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+ M98088_DAI_DHF, M98088_DAI_DHF);
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+ return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ unsigned int rate;
+ u8 regval;
+ u16 ni;
+
+ cdata = &max98088->dai[1];
+
+ rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ M98088_DAI_WS, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ M98088_DAI_WS, M98088_DAI_WS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+ if (rate != cdata->rate) {
+ /* set DAI2 SR2 value for the DSP */
+ if (rate_value(rate, ®val))
+ return -EINVAL;
+
+ snd_soc_write(codec, M98088_REG_19_DAI2_CLKMODE, regval);
+ cdata->rate = rate;
+ }
+
+ /* Configure NI when operating as master */
+ if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+ & M98088_DAI_MAS) {
+ if (max98088->sysclk == 0)
+ return -EINVAL;
+ ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+ * (unsigned long long int)rate,
+ (unsigned long long int)max98088->sysclk);
+ snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+ (ni >> 8) & 0x7f);
+ snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+ ni & 0xff);
+ }
+
+ /* Update sample rate mode */
+ if (rate < 50000)
+ snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+ M98088_DAI_DHF, 0);
+ else
+ snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+ M98088_DAI_DHF, M98088_DAI_DHF);
+
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+ return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+ /* requested clock frequency is already setup */
+ if (freq == max98088->sysclk)
+ return 0;
+
+ max98088->sysclk = freq; /* remember current sysclk */
+
+ /* setup clocks for slave mode, and using the PLL
+ * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+ * 0x02 (when master clk is 20MHz to 30MHz)..
+ */
+ if ((freq >= 10000000) && (freq < 20000000)) {
+ snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+ } else if ((freq >= 20000000) && (freq < 30000000)) {
+ snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+ } else {
+ dev_err(codec->dev, "Invalid master clock frequency\n");
+ return -EINVAL;
+ }
+
+ if (snd_soc_read(codec, M98088_REG_51_PWR_SYS) & M98088_SHDNRUN) {
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+ M98088_SHDNRUN, 0);
+ snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+ 0, M98088_SHDNRUN);
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ max98088->sysclk = freq;
+ return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ u8 reg15val;
+ u8 reg14msk = 0;
+ u8 reg14val = 0;
+
+ cdata = &max98088->dai[0];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* mask MAS to select slave mode */
+ reg14msk |= M98088_DAI_MAS;
+ /* slave mode PLL */
+ snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* set to master mode */
+ reg14val |= M98088_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg14val |= M98088_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg14msk |= M98088_DAI_DLY;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ reg14msk |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg14msk |= M98088_DAI_BCI;
+ reg14val |= M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg14msk |= M98088_DAI_WCI;
+ reg14val |= M98088_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+ reg14msk, reg14val);
+
+ reg15val = M98088_DAI_BSEL64;
+ if (max98088->digmic)
+ reg15val |= M98088_DAI_OSR64;
+ snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+ }
+
+ return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ u8 reg1Cmsk = 0;
+ u8 reg1Cval = 0;
+
+ cdata = &max98088->dai[1];
+
+ if (fmt != cdata->fmt) {
+ cdata->fmt = fmt;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* mask MAS to select slave mode */
+ reg1Cmsk |= M98088_DAI_MAS;
+ /* slave mode PLL */
+ snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+ 0x80);
+ snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+ 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* set to master mode */
+ reg1Cval |= M98088_DAI_MAS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg1Cval |= M98088_DAI_DLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg1Cmsk |= M98088_DAI_DLY;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ reg1Cmsk |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg1Cmsk |= M98088_DAI_BCI;
+ reg1Cval |= M98088_DAI_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg1Cmsk |= M98088_DAI_WCI;
+ reg1Cval |= M98088_DAI_BCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+ reg1Cmsk, reg1Cval);
+
+ snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+ M98088_DAI_BSEL64);
+ }
+
+ return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+
+ /* write back cached values if they're writeable and
+ * different from the hardware default.
+ */
+ for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+ if (!max98088_access[i].writable)
+ continue;
+
+ if (max98088->reg_cache[i] == max98088_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, max98088->reg_cache[i]);
+ }
+
+ codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ max98088_sync_cache(codec);
+ snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+ M98088_MBEN, M98088_MBEN);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+ M98088_MBEN, 0);
+#ifdef CONFIG_REGULATOR
+ codec->cache_sync = 1;
+#endif
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+ .set_sysclk = max98088_dai_set_sysclk,
+ .set_fmt = max98088_dai1_set_fmt,
+ .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+ .set_sysclk = max98088_dai_set_sysclk,
+ .set_fmt = max98088_dai2_set_fmt,
+ .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+ .name = "HiFi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .ops = &max98088_dai1_ops,
+},
+{
+ .name = "Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98088_RATES,
+ .formats = MAX98088_FORMATS,
+ },
+ .ops = &max98088_dai2_ops,
+}
+};
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_eq_cfg *coef_set;
+ int best, best_val, save, i, sel, fs;
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[0];
+
+ if (!pdata || !cdata->eq_textcnt)
+ return;
+
+ /* Find the selected configuration with nearest sample rate */
+ fs = cdata->rate;
+ sel = cdata->eq_sel;
+
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+ if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+ abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->eq1_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->eq1_cfg[best].name,
+ pdata->eq1_cfg[best].rate, fs);
+
+ /* Disable EQ while configuring, and save current on/off state */
+ save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+ coef_set = &pdata->eq1_cfg[sel];
+
+ m98088_eq_band(codec, 0, 0, coef_set->band1);
+ m98088_eq_band(codec, 0, 1, coef_set->band2);
+ m98088_eq_band(codec, 0, 2, coef_set->band3);
+ m98088_eq_band(codec, 0, 3, coef_set->band4);
+ m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+ /* restore original on/off state */
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_eq_cfg *coef_set;
+ int best, best_val, save, i, sel, fs;
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[1];
+
+ if (!pdata || !cdata->eq_textcnt)
+ return;
+
+ /* Find the selected configuration with nearest sample rate */
+ fs = cdata->rate;
+
+ sel = cdata->eq_sel;
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+ if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+ abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->eq2_cfg[i].rate - fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+ pdata->eq2_cfg[best].name,
+ pdata->eq2_cfg[best].rate, fs);
+
+ /* Disable EQ while configuring, and save current on/off state */
+ save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+ coef_set = &pdata->eq2_cfg[sel];
+
+ m98088_eq_band(codec, 1, 0, coef_set->band1);
+ m98088_eq_band(codec, 1, 1, coef_set->band2);
+ m98088_eq_band(codec, 1, 2, coef_set->band3);
+ m98088_eq_band(codec, 1, 3, coef_set->band4);
+ m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+ /* restore original on/off state */
+ snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+ save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_cdata *cdata;
+ int sel = ucontrol->value.integer.value[0];
+
+ cdata = &max98088->dai[0];
+
+ if (sel >= pdata->eq1_cfgcnt)
+ return -EINVAL;
+
+ cdata->eq_sel = sel;
+ max98088_setup_eq1(codec);
+ return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_cdata *cdata;
+ int sel = ucontrol->value.integer.value[0];
+
+ cdata = &max98088->dai[1];
+
+ if (sel >= pdata->eq2_cfgcnt)
+ return -EINVAL;
+
+ cdata->eq_sel = sel;
+ max98088_setup_eq2(codec);
+ return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[0];
+
+ ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+ return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+
+ cdata = &max98088->dai[1];
+
+ ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+ return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_cdata *cdata;
+ int ret, i, j;
+
+ struct snd_kcontrol_new eq1control =
+ SOC_ENUM_EXT("EQ1 Mode",
+ max98088->dai[0].eq_enum,
+ max98088_get_eq1_enum,
+ max98088_put_eq1_enum);
+
+ cdata = &max98088->dai[0];
+
+ /* Build an array of texts for the enum API. The number
+ * of texts is likely fewer than the number of configurations
+ * due to multiple sample rates for the same text name. */
+ cdata->eq_textcnt = 0;
+ for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+ for (j = 0; j < cdata->eq_textcnt; j++) {
+ if (strcmp(pdata->eq1_cfg[i].name,
+ cdata->eq_texts[j]) == 0) {
+ break;
+ }
+ }
+
+ if (j != cdata->eq_textcnt)
+ continue;
+
+ /* ...and remember the new version. */
+ cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+ cdata->eq_textcnt++;
+
+ if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+ dev_err(codec->dev, "Too many EQ config entries\n");
+ cdata->eq_textcnt--;
+ break;
+ }
+ }
+
+ dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+ cdata->eq_textcnt);
+
+ /* now point the soc_enum to .texts array items */
+ cdata->eq_enum.texts = cdata->eq_texts;
+ cdata->eq_enum.max = cdata->eq_textcnt;
+
+ ret = snd_soc_add_controls(codec, &eq1control, 1);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ struct max98088_cdata *cdata;
+ int ret, i, j;
+
+ struct snd_kcontrol_new eq2control =
+ SOC_ENUM_EXT("EQ2 Mode",
+ max98088->dai[1].eq_enum,
+ max98088_get_eq2_enum,
+ max98088_put_eq2_enum);
+
+ cdata = &max98088->dai[1];
+
+ /* Build an array of texts for the enum API. The number
+ * of texts is likely fewer than the number of configurations
+ * due to multiple sample rates for the same text name. */
+ cdata->eq_textcnt = 0;
+ for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+ for (j = 0; j < cdata->eq_textcnt; j++) {
+ if (strcmp(pdata->eq2_cfg[i].name,
+ cdata->eq_texts[j]) == 0) {
+ break;
+ }
+ }
+
+ if (j != cdata->eq_textcnt)
+ continue;
+
+ cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+ cdata->eq_textcnt++;
+
+ if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+ dev_err(codec->dev, "Too many EQ config entries\n");
+ cdata->eq_textcnt--;
+ break;
+ }
+ }
+
+ dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+ cdata->eq_textcnt);
+
+ /* now point the soc_enum to .texts array items */
+ cdata->eq_enum.texts = cdata->eq_texts;
+ cdata->eq_enum.max = cdata->eq_textcnt;
+
+ ret = snd_soc_add_controls(codec, &eq2control, 1);
+ if (ret != 0)
+ printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_pdata *pdata = max98088->pdata;
+ u8 regval = 0;
+
+ if (!pdata) {
+ dev_dbg(codec->dev, "No platform data\n");
+ return;
+ }
+
+ /* configure mic for analog/digital mic mode */
+ if (pdata->digmic_left_mode)
+ regval |= M98088_DIGMIC_L;
+
+ if (pdata->digmic_right_mode)
+ regval |= M98088_DIGMIC_R;
+
+ max98088->digmic = (regval ? 1 : 0);
+
+ snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+ /* configure receiver output */
+ regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+ snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+ M98088_REC_LINEMODE_MASK, regval);
+
+ /* configure equalizers */
+ if (pdata->eq1_cfgcnt)
+ max98088_handle_eq1_pdata(codec);
+
+ if (pdata->eq2_cfgcnt)
+ max98088_handle_eq2_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 *cache = codec->reg_cache;
+
+ max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < M98088_REG_CNT; i++) {
+ if (i == M98088_REG_51_PWR_SYS)
+ continue;
+
+ if (!max98088_access[i].writable)
+ continue;
+
+ max98088_hw_write(codec, i, cache[i]);
+ }
+
+ /* now enter into the resume mode bias level */
+ max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+ struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+ struct max98088_cdata *cdata;
+ int ret = 0;
+
+ codec->cache_sync = 1;
+ memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* initalize private data */
+
+ max98088->sysclk = (unsigned)-1;
+
+ cdata = &max98088->dai[0];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_textcnt = 0;
+ cdata->eq_sel = 0;
+
+ cdata = &max98088->dai[1];
+ cdata->rate = (unsigned)-1;
+ cdata->fmt = (unsigned)-1;
+ cdata->eq_textcnt = 0;
+ cdata->eq_sel = 0;
+
+ max98088->power_state = 0; /* INA INB power enable state */
+ max98088->ex_mode = 0; /* excursion limiter mode */
+ max98088->digmic = 0; /* 0=analog, 1=digital */
+ max98088->mic1pre = 0;
+ max98088->mic2pre = 0;
+
+ ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_access;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+ /* initialize registers cache to hardware default */
+ max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+ snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+ M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+ M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+ snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+ snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+ snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+ M98088_S1NORMAL|M98088_SDATA);
+
+ snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+ M98088_S2NORMAL|M98088_SDATA);
+
+ max98088_handle_pdata(codec);
+
+ max98088_add_widgets(codec);
+
+err_access:
+ return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+ if (codec->control_data)
+ max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+ .probe = max98088_probe,
+ .remove = max98088_remove,
+ .suspend = max98088_suspend,
+ .resume = max98088_resume,
+ .set_bias_level = max98088_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(max98088_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = max98088_reg,
+ .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max98088_priv *max98088;
+ int ret;
+
+ max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+ if (max98088 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max98088);
+ max98088->control_data = i2c;
+ max98088->pdata = i2c->dev.platform_data;
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_max98088, &max98088_dai[0], 2);
+ if (ret < 0)
+ kfree(max98088);
+ return ret;
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+ { "max98088", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+ .driver = {
+ .name = "max98088-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = max98088_i2c_probe,
+ .remove = __devexit_p(max98088_i2c_remove),
+ .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&max98088_i2c_driver);
+ if (ret)
+ pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+ return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+ i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..9e9d8da
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,190 @@
+/*
+ * max98088.h -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS 0x00
+#define M98088_REG_01_MIC_STATUS 0x01
+#define M98088_REG_02_JACK_STAUS 0x02
+#define M98088_REG_03_BATTERY_VOLTAGE 0x03
+#define M98088_REG_0F_IRQ_ENABLE 0x0F
+#define M98088_REG_10_SYS_CLK 0x10
+#define M98088_REG_11_DAI1_CLKMODE 0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI 0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO 0x13
+#define M98088_REG_14_DAI1_FORMAT 0x14
+#define M98088_REG_15_DAI1_CLOCK 0x15
+#define M98088_REG_16_DAI1_IOCFG 0x16
+#define M98088_REG_17_DAI1_TDM 0x17
+#define M98088_REG_18_DAI1_FILTERS 0x18
+#define M98088_REG_19_DAI2_CLKMODE 0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI 0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO 0x1B
+#define M98088_REG_1C_DAI2_FORMAT 0x1C
+#define M98088_REG_1D_DAI2_CLOCK 0x1D
+#define M98088_REG_1E_DAI2_IOCFG 0x1E
+#define M98088_REG_1F_DAI2_TDM 0x1F
+#define M98088_REG_20_DAI2_FILTERS 0x20
+#define M98088_REG_21_SRC 0x21
+#define M98088_REG_22_MIX_DAC 0x22
+#define M98088_REG_23_MIX_ADC_LEFT 0x23
+#define M98088_REG_24_MIX_ADC_RIGHT 0x24
+#define M98088_REG_25_MIX_HP_LEFT 0x25
+#define M98088_REG_26_MIX_HP_RIGHT 0x26
+#define M98088_REG_27_MIX_HP_CNTL 0x27
+#define M98088_REG_28_MIX_REC_LEFT 0x28
+#define M98088_REG_29_MIX_REC_RIGHT 0x29
+#define M98088_REG_2A_MIC_REC_CNTL 0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT 0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT 0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL 0x2D
+#define M98088_REG_2E_LVL_SIDETONE 0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY 0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ 0x30
+#define M98088_REG_31_LVL_DAI2_PLAY 0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ 0x32
+#define M98088_REG_33_LVL_ADC_L 0x33
+#define M98088_REG_34_LVL_ADC_R 0x34
+#define M98088_REG_35_LVL_MIC1 0x35
+#define M98088_REG_36_LVL_MIC2 0x36
+#define M98088_REG_37_LVL_INA 0x37
+#define M98088_REG_38_LVL_INB 0x38
+#define M98088_REG_39_LVL_HP_L 0x39
+#define M98088_REG_3A_LVL_HP_R 0x3A
+#define M98088_REG_3B_LVL_REC_L 0x3B
+#define M98088_REG_3C_LVL_REC_R 0x3C
+#define M98088_REG_3D_LVL_SPK_L 0x3D
+#define M98088_REG_3E_LVL_SPK_R 0x3E
+#define M98088_REG_3F_MICAGC_CFG 0x3F
+#define M98088_REG_40_MICAGC_THRESH 0x40
+#define M98088_REG_41_SPKDHP 0x41
+#define M98088_REG_42_SPKDHP_THRESH 0x42
+#define M98088_REG_43_SPKALC_COMP 0x43
+#define M98088_REG_44_PWRLMT_CFG 0x44
+#define M98088_REG_45_PWRLMT_TIME 0x45
+#define M98088_REG_46_THDLMT_CFG 0x46
+#define M98088_REG_47_CFG_AUDIO_IN 0x47
+#define M98088_REG_48_CFG_MIC 0x48
+#define M98088_REG_49_CFG_LEVEL 0x49
+#define M98088_REG_4A_CFG_BYPASS 0x4A
+#define M98088_REG_4B_CFG_JACKDET 0x4B
+#define M98088_REG_4C_PWR_EN_IN 0x4C
+#define M98088_REG_4D_PWR_EN_OUT 0x4D
+#define M98088_REG_4E_BIAS_CNTL 0x4E
+#define M98088_REG_4F_DAC_BIAS1 0x4F
+#define M98088_REG_50_DAC_BIAS2 0x50
+#define M98088_REG_51_PWR_SYS 0x51
+#define M98088_REG_52_DAI1_EQ_BASE 0x52
+#define M98088_REG_84_DAI2_EQ_BASE 0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE 0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE 0xC0
+#define M98088_REG_FF_REV_ID 0xFF
+
+#define M98088_REG_CNT (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+ #define M98088_DAI_MAS (1<<7)
+ #define M98088_DAI_WCI (1<<6)
+ #define M98088_DAI_BCI (1<<5)
+ #define M98088_DAI_DLY (1<<4)
+ #define M98088_DAI_TDM (1<<2)
+ #define M98088_DAI_FSW (1<<1)
+ #define M98088_DAI_WS (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+ #define M98088_DAI_BSEL64 (1<<0)
+ #define M98088_DAI_OSR64 (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+ #define M98088_S1NORMAL (1<<6)
+ #define M98088_S2NORMAL (2<<6)
+ #define M98088_SDATA (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+ #define M98088_DAI_DHF (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+ #define M98088_DAI1L_TO_DACL (1<<7)
+ #define M98088_DAI1R_TO_DACL (1<<6)
+ #define M98088_DAI2L_TO_DACL (1<<5)
+ #define M98088_DAI2R_TO_DACL (1<<4)
+ #define M98088_DAI1L_TO_DACR (1<<3)
+ #define M98088_DAI1R_TO_DACR (1<<2)
+ #define M98088_DAI2L_TO_DACR (1<<1)
+ #define M98088_DAI2R_TO_DACR (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+ #define M98088_REC_LINEMODE (1<<7)
+ #define M98088_REC_LINEMODE_MASK (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+ #define M98088_MICPRE_MASK (3<<5)
+ #define M98088_MICPRE_SHIFT 5
+
+/* M98088_REG_3A_LVL_HP_R */
+ #define M98088_HP_MUTE (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+ #define M98088_REC_MUTE (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+ #define M98088_SP_MUTE (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+ #define M98088_EXTMIC_MASK (3<<0)
+ #define M98088_DIGMIC_L (1<<5)
+ #define M98088_DIGMIC_R (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+ #define M98088_VSEN (1<<6)
+ #define M98088_ZDEN (1<<5)
+ #define M98088_EQ2EN (1<<1)
+ #define M98088_EQ1EN (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+ #define M98088_INAEN (1<<7)
+ #define M98088_INBEN (1<<6)
+ #define M98088_MBEN (1<<3)
+ #define M98088_ADLEN (1<<1)
+ #define M98088_ADREN (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+ #define M98088_HPLEN (1<<7)
+ #define M98088_HPREN (1<<6)
+ #define M98088_HPEN ((1<<7)|(1<<6))
+ #define M98088_SPLEN (1<<5)
+ #define M98088_SPREN (1<<4)
+ #define M98088_RECEN (1<<3)
+ #define M98088_DALEN (1<<1)
+ #define M98088_DAREN (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+ #define M98088_SHDNRUN (1<<7)
+ #define M98088_PERFMODE (1<<3)
+ #define M98088_HPPLYBACK (1<<2)
+ #define M98088_PWRSV8K (1<<1)
+ #define M98088_PWRSV (1<<0)
+
+#define M98088_COEFS_PER_BAND 5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+ unsigned short i2c_address;
+};
+
+#endif
--
1.6.3.3
6
43

[alsa-devel] Need expert's advice - Fast Track Ultra (8R) dropping samples
by Felix Homann 15 Oct '10
by Felix Homann 15 Oct '10
15 Oct '10
Hi,
in that past months I've been trying get the Fast Track Ultra devices
working properly in Alsa. We've had lots of progress, most of the code
has moved to Alsa git and today I've even posted a patch for getting
mixer support for these devices.
Now, I need to get some expert's advice: The devices seem to drop
samples or frames. Here's a report I've got today on the M-Audio forum:
"I've got a subtle problem to report: I think audio playback is dropping
sample frames. To hear the problem, open Audacity at 48 kHz and play
a 10-kHz. sine wave. When I do that I hear a regular clicking sound, about
four clicks a second. I've tried recording the output and if I'm seeing
correctly,
exactly one sample frame in every 13312 (13x1024) is being dropped on
output.
I don't see anything similar on input. When either jack or Pd has both
the input and
the output open, the delay from input to output gradually decreases
until it forces
occasional sync errors. (I haven't tried this with audacity though.)"
(see
http://forums.m-audio.com/showthread.php?714-Not-a-problem.-FastTrack-on-li…)
I could reproduce it on my machines, even at 44.1 kHz. The clicking
sound is very subtle, it goes unnoticed when not listening to pure sines
without attention to clicks.
How can this be sorted out. Any ideas?
Kind regards,
Felix
4
23
This patch depends on gpiolib support for h1940 latch access,
here's link to patch in ml archive:
http://www.spinics.net/lists/arm-kernel/msg98042.html
I hope Ben will find some time to merge it into his tree
before 2.6.37 merge window :)
Signed-off-by: Vasily Khoruzhick <anarsoul(a)gmail.com>
Tested-by: Arnaud Patard <arnaud.patard(a)rtp-net.org>
---
sound/soc/s3c24xx/Kconfig | 8 +
sound/soc/s3c24xx/Makefile | 2 +
sound/soc/s3c24xx/h1940_uda1380.c | 296 +++++++++++++++++++++++++++++++++++++
3 files changed, 306 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/s3c24xx/h1940_uda1380.c
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig
index 7d8235d..6b50509 100644
--- a/sound/soc/s3c24xx/Kconfig
+++ b/sound/soc/s3c24xx/Kconfig
@@ -118,6 +118,14 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES
select SND_SOC_TLV320AIC3X
select SND_S3C24XX_SOC_SIMTEC
+config SND_S3C24XX_SOC_H1940_UDA1380
+ tristate "Audio support for the HP iPAQ H1940"
+ depends on SND_S3C24XX_SOC && ARCH_H1940
+ select SND_S3C24XX_SOC_I2S
+ select SND_SOC_UDA1380
+ help
+ This driver provides audio support for HP iPAQ h1940 PDA.
+
config SND_S3C24XX_SOC_RX1950_UDA1380
tristate "Audio support for the HP iPAQ RX1950"
depends on SND_S3C24XX_SOC && MACH_RX1950
diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile
index dd412a9..33a7c68 100644
--- a/sound/soc/s3c24xx/Makefile
+++ b/sound/soc/s3c24xx/Makefile
@@ -28,6 +28,7 @@ snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
+snd-soc-h1940-uda1380-objs := h1940_uda1380.o
snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o
snd-soc-smdk-wm9713-objs := smdk_wm9713.o
snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
@@ -44,6 +45,7 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
obj-$(CONFIG_SND_S3C24XX_SOC_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
+obj-$(CONFIG_SND_S3C24XX_SOC_H1940_UDA1380) += snd-soc-h1940-uda1380.o
obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o
obj-$(CONFIG_SND_S3C64XX_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
diff --git a/sound/soc/s3c24xx/h1940_uda1380.c b/sound/soc/s3c24xx/h1940_uda1380.c
new file mode 100644
index 0000000..77550fa
--- /dev/null
+++ b/sound/soc/s3c24xx/h1940_uda1380.c
@@ -0,0 +1,296 @@
+/*
+ * h1940-uda1380.c -- ALSA Soc Audio Layer
+ *
+ * Copyright (c) 2010 Arnaud Patard <arnaud.patard(a)rtp-net.org>
+ * Copyright (c) 2010 Vasily Khoruzhick <anarsoul(a)gmail.com>
+ *
+ * Based on version from Arnaud Patard <arnaud.patard(a)rtp-net.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/uda1380.h>
+#include <sound/jack.h>
+
+#include <plat/regs-iis.h>
+
+#include <mach/h1940-latch.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "../codecs/uda1380.h"
+
+static unsigned int rates[] = {
+ 11025,
+ 22050,
+ 44100,
+};
+
+static struct snd_pcm_hw_constraint_list hw_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+ {
+ .gpio = S3C2410_GPG(4),
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static int h1940_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.rate_min = hw_rates.list[0];
+ runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1];
+ runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_rates);
+}
+
+static int h1940_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int div;
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ switch (rate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ div = s3c24xx_i2s_get_clockrate() / (384 * rate);
+ if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate))
+ div++;
+ break;
+ default:
+ printk(KERN_ERR "%s: rate %d is not supported\n",
+ __func__, rate);
+ return -EINVAL;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* select clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_384FS);
+ if (ret < 0)
+ return ret;
+
+ /* set BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops h1940_ops = {
+ .startup = h1940_startup,
+ .hw_params = h1940_hw_params,
+};
+
+static int h1940_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(H1940_LATCH_AUDIO_POWER, 1);
+ else
+ gpio_set_value(H1940_LATCH_AUDIO_POWER, 0);
+
+ return 0;
+}
+
+/* h1940 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", h1940_spk_power),
+};
+
+/* h1940 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to VOUTLHP, VOUTRHP */
+ {"Headphone Jack", NULL, "VOUTLHP"},
+ {"Headphone Jack", NULL, "VOUTRHP"},
+
+ /* ext speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* mic is connected to VINM */
+ {"VINM", NULL, "Mic Jack"},
+};
+
+static struct platform_device *s3c24xx_snd_device;
+
+static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int err;
+
+ /* Add h1940 specific widgets */
+ err = snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+ if (err)
+ return err;
+
+ /* Set up h1940 specific audio path audio_mapnects */
+ err = snd_soc_dapm_add_routes(codec, audio_map,
+ ARRAY_SIZE(audio_map));
+ if (err)
+ return err;
+
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Speaker");
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+ snd_soc_dapm_sync(codec);
+
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+ &hp_jack);
+
+ snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+ hp_jack_pins);
+
+ snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+
+ return 0;
+}
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link h1940_uda1380_dai[] = {
+ {
+ .name = "uda1380",
+ .stream_name = "UDA1380 Duplex",
+ .cpu_dai_name = "s3c24xx-iis",
+ .codec_dai_name = "uda1380-hifi",
+ .init = h1940_uda1380_init,
+ .platform_name = "s3c24xx-pcm-audio",
+ .codec_name = "uda1380-codec.0-001a",
+ .ops = &h1940_ops,
+ },
+};
+
+static struct snd_soc_card h1940_asoc = {
+ .name = "h1940",
+ .dai_link = h1940_uda1380_dai,
+ .num_links = ARRAY_SIZE(h1940_uda1380_dai),
+};
+
+static int __init h1940_init(void)
+{
+ int ret;
+
+ if (!machine_is_h1940())
+ return -ENODEV;
+
+ /* configure some gpios */
+ ret = gpio_request(H1940_LATCH_AUDIO_POWER, "speaker-power");
+ if (ret)
+ goto err_out;
+
+ ret = gpio_direction_output(H1940_LATCH_AUDIO_POWER, 0);
+ if (ret)
+ goto err_gpio;
+
+ s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!s3c24xx_snd_device) {
+ ret = -ENOMEM;
+ goto err_gpio;
+ }
+
+ platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc);
+ ret = platform_device_add(s3c24xx_snd_device);
+
+ if (ret)
+ goto err_plat;
+
+ return 0;
+
+err_plat:
+ platform_device_put(s3c24xx_snd_device);
+err_gpio:
+ gpio_free(H1940_LATCH_AUDIO_POWER);
+
+err_out:
+ return ret;
+}
+
+static void __exit h1940_exit(void)
+{
+ platform_device_unregister(s3c24xx_snd_device);
+ snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+ gpio_free(H1940_LATCH_AUDIO_POWER);
+}
+
+module_init(h1940_init);
+module_exit(h1940_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC H1940");
+MODULE_LICENSE("GPL");
--
1.7.2.2
4
20
This patch fixes up the au1x audio platform after the multi-component
merge:
- compile fixes and updates to get DB1200 platform audio working again,
- removal of global variables in AC97/I2S/DMA(PCM) modules.
The AC97 part is limited to one instance only for now due to issues
with getting at driver data in the soc_ac97_ops.
Signed-off-by: Manuel Lauss <manuel.lauss(a)googlemail.com>
---
v5: smaller patch
v4: fixed a bug in the previous bugfix, and added DAI drvdata accessors.
v3: fixed a bug which caused cat /proc/iomem to loop endlessly.
v2: prepare PCM,I2S for multiple card operation, use dev_name() for DAI name.
Against Liam's asoc/for-2.6.37 branch.
Tested on DB1200 and DB1300 (here both I2S and AC97 operate as
independent cards), please fold this into the other Au1x multi-component
patches.
Issues I observed with AC97:
* AC97 is limited to a single instance since I cannot get at the driver
data in the AC97 callbacks at all time (or did I miss anything?):
when the AC97 codec calls snd_ac97_mixer(), it calls into the soc_ac97_ops
callbacks; however ac97->bus->card->private_data (suggested by Mark)
is _always_ NULL, so no way to get at the dai and ultimately driver data.
* generic AC97 codec use spits out this kobject warning, which is caused by the
"device_register()" in soc-core.c::soc_ac97_dev_register():
asoc: ac97-hifi <-> au1xpsc-ac97.1 mapping ok
kobject (8fdc59b0): tried to init an initialized object, something is
seriously wrong.
Call Trace:
[<804959c4>] dump_stack+0x8/0x34
[<802a30e4>] kobject_init+0x50/0xcc
[<802ec27c>] device_initialize+0x2c/0x70
[<802ecb64>] device_register+0x14/0x28
[<8039e058>] snd_soc_instantiate_cards+0xa00/0xb10
[<8039e27c>] soc_probe+0x114/0x154
[<802ef0fc>] driver_probe_device+0xe4/0x1a0
[<802ee404>] bus_for_each_drv+0x60/0xb0
[<802ef370>] device_attach+0x74/0xa8
[<802ee1e8>] bus_probe_device+0x30/0x54
[<802ec9a0>] device_add+0x384/0x534
[<802f09a4>] platform_device_add+0x15c/0x1c8
[<805ccb14>] db1200_audio_load+0x70/0x9c
[<801004fc>] do_one_initcall+0xfc/0x1e0
[<805ba32c>] kernel_init+0xc8/0x168
[<80105904>] kernel_thread_helper+0x10/0x18
arch/mips/alchemy/devboards/db1200/platform.c | 6 ++
sound/soc/au1x/db1200.c | 16 +++---
sound/soc/au1x/dbdma2.c | 82 ++++++++-----------------
sound/soc/au1x/psc-ac97.c | 59 +++++++++++-------
sound/soc/au1x/psc-i2s.c | 42 ++++---------
sound/soc/au1x/psc.h | 7 +--
6 files changed, 91 insertions(+), 121 deletions(-)
diff --git a/arch/mips/alchemy/devboards/db1200/platform.c b/arch/mips/alchemy/devboards/db1200/platform.c
index 3fa34c3..fbb5593 100644
--- a/arch/mips/alchemy/devboards/db1200/platform.c
+++ b/arch/mips/alchemy/devboards/db1200/platform.c
@@ -429,6 +429,11 @@ static struct platform_device db1200_audio_dev = {
.resource = au1200_psc1_res,
};
+static struct platform_device db1200_stac_dev = {
+ .name = "ac97-codec",
+ .id = 1, /* on PSC1 */
+};
+
static struct platform_device *db1200_devs[] __initdata = {
NULL, /* PSC0, selected by S6.8 */
&db1200_ide_dev,
@@ -436,6 +441,7 @@ static struct platform_device *db1200_devs[] __initdata = {
&db1200_rtc_dev,
&db1200_nand_dev,
&db1200_audio_dev,
+ &db1200_stac_dev,
};
static int __init db1200_dev_init(void)
diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c
index d8dc822..b62fcd3 100644
--- a/sound/soc/au1x/db1200.c
+++ b/sound/soc/au1x/db1200.c
@@ -27,10 +27,10 @@
static struct snd_soc_dai_link db1200_ac97_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
- .cpu_dai_name = "au1xpsc-ac97",
.codec_dai_name = "ac97-hifi",
- .platform_name = "au1xpsc-pcm-audio",
- .codec_name = "ac97-codec",
+ .cpu_dai_name = "au1xpsc_ac97.1",
+ .platform_name = "au1xpsc-pcm.1",
+ .codec_name = "ac97-codec.1",
};
static struct snd_soc_card db1200_ac97_machine = {
@@ -75,10 +75,10 @@ static struct snd_soc_ops db1200_i2s_wm8731_ops = {
static struct snd_soc_dai_link db1200_i2s_dai = {
.name = "WM8731",
.stream_name = "WM8731 PCM",
- .cpu_dai_name = "au1xpsc",
- .codec_dai_name = "wm8731-hifi"
- .platform_name = "au1xpsc-pcm-audio",
- .codec_name = "wm8731-codec.0-001a",
+ .codec_dai_name = "wm8731-hifi",
+ .cpu_dai_name = "au1xpsc_i2s.1",
+ .platform_name = "au1xpsc-pcm.1",
+ .codec_name = "wm8731-codec.0-001b",
.ops = &db1200_i2s_wm8731_ops,
};
@@ -97,7 +97,7 @@ static int __init db1200_audio_load(void)
int ret;
ret = -ENOMEM;
- db1200_asoc_dev = platform_device_alloc("soc-audio", -1);
+ db1200_asoc_dev = platform_device_alloc("soc-audio", 1); /* PSC1 */
if (!db1200_asoc_dev)
goto out;
diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c
index 00fdb9c..10fdd28 100644
--- a/sound/soc/au1x/dbdma2.c
+++ b/sound/soc/au1x/dbdma2.c
@@ -10,9 +10,6 @@
*
* DMA glue for Au1x-PSC audio.
*
- * NOTE: all of these drivers can only work with a SINGLE instance
- * of a PSC. Multiple independent audio devices are impossible
- * with ASoC v1.
*/
@@ -61,9 +58,6 @@ struct au1xpsc_audio_dmadata {
int msbits;
};
-/* instance data. There can be only one, MacLeod!!!! */
-static struct au1xpsc_audio_dmadata *au1xpsc_audio_pcmdma[2];
-
/*
* These settings are somewhat okay, at least on my machine audio plays
* almost skip-free. Especially the 64kB buffer seems to help a LOT.
@@ -199,6 +193,14 @@ out:
return 0;
}
+static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream *ss)
+{
+ struct snd_soc_pcm_runtime *rtd = ss->private_data;
+ struct au1xpsc_audio_dmadata *pcd =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ return &pcd[SUBSTREAM_TYPE(ss)];
+}
+
static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@@ -211,7 +213,7 @@ static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
goto out;
stype = SUBSTREAM_TYPE(substream);
- pcd = au1xpsc_audio_pcmdma[stype];
+ pcd = to_dmadata(substream);
DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
"runtime->min_align %d\n",
@@ -249,8 +251,7 @@ static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
{
- struct au1xpsc_audio_dmadata *pcd =
- au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)];
+ struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream);
au1xxx_dbdma_reset(pcd->ddma_chan);
@@ -267,7 +268,7 @@ static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
- u32 c = au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->ddma_chan;
+ u32 c = to_dmadata(substream)->ddma_chan;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
@@ -287,8 +288,7 @@ static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
static snd_pcm_uframes_t
au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
{
- return bytes_to_frames(substream->runtime,
- au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->pos);
+ return bytes_to_frames(substream->runtime, to_dmadata(substream)->pos);
}
static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
@@ -299,7 +299,7 @@ static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
{
- au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]);
+ au1x_pcm_dbdma_free(to_dmadata(substream));
return 0;
}
@@ -329,35 +329,21 @@ static int au1xpsc_pcm_new(struct snd_card *card,
return 0;
}
-static int au1xpsc_pcm_probe(struct snd_soc_platform *platform)
-{
- if (!au1xpsc_audio_pcmdma[PCM_TX] || !au1xpsc_audio_pcmdma[PCM_RX])
- return -ENODEV;
-
- return 0;
-}
-
/* au1xpsc audio platform */
struct snd_soc_platform_driver au1xpsc_soc_platform = {
- .probe = au1xpsc_pcm_probe,
.ops = &au1xpsc_pcm_ops,
.pcm_new = au1xpsc_pcm_new,
.pcm_free = au1xpsc_pcm_free_dma_buffers,
};
-EXPORT_SYMBOL_GPL(au1xpsc_soc_platform);
static int __devinit au1xpsc_pcm_drvprobe(struct platform_device *pdev)
{
+ struct au1xpsc_audio_dmadata *dmadata;
struct resource *r;
int ret;
- if (au1xpsc_audio_pcmdma[PCM_TX] || au1xpsc_audio_pcmdma[PCM_RX])
- return -EBUSY;
-
- /* TX DMA */
- au1xpsc_audio_pcmdma[PCM_TX]
- = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
- if (!au1xpsc_audio_pcmdma[PCM_TX])
+ dmadata = kzalloc(2 * sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
+ if (!dmadata)
return -ENOMEM;
r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
@@ -365,54 +351,40 @@ static int __devinit au1xpsc_pcm_drvprobe(struct platform_device *pdev)
ret = -ENODEV;
goto out1;
}
- (au1xpsc_audio_pcmdma[PCM_TX])->ddma_id = r->start;
+ dmadata[PCM_TX].ddma_id = r->start;
/* RX DMA */
- au1xpsc_audio_pcmdma[PCM_RX]
- = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
- if (!au1xpsc_audio_pcmdma[PCM_RX])
- return -ENOMEM;
-
r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!r) {
ret = -ENODEV;
- goto out2;
+ goto out1;
}
- (au1xpsc_audio_pcmdma[PCM_RX])->ddma_id = r->start;
+ dmadata[PCM_RX].ddma_id = r->start;
+
+ platform_set_drvdata(pdev, dmadata);
ret = snd_soc_register_platform(&pdev->dev, &au1xpsc_soc_platform);
if (!ret)
return ret;
-out2:
- kfree(au1xpsc_audio_pcmdma[PCM_RX]);
- au1xpsc_audio_pcmdma[PCM_RX] = NULL;
out1:
- kfree(au1xpsc_audio_pcmdma[PCM_TX]);
- au1xpsc_audio_pcmdma[PCM_TX] = NULL;
+ kfree(dmadata);
return ret;
}
static int __devexit au1xpsc_pcm_drvremove(struct platform_device *pdev)
{
- int i;
+ struct au1xpsc_audio_dmadata *dmadata = platform_get_drvdata(pdev);
snd_soc_unregister_platform(&pdev->dev);
-
- for (i = 0; i < 2; i++) {
- if (au1xpsc_audio_pcmdma[i]) {
- au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[i]);
- kfree(au1xpsc_audio_pcmdma[i]);
- au1xpsc_audio_pcmdma[i] = NULL;
- }
- }
+ kfree(dmadata);
return 0;
}
static struct platform_driver au1xpsc_pcm_driver = {
.driver = {
- .name = "au1xpsc-pcm-audio",
+ .name = "au1xpsc-pcm",
.owner = THIS_MODULE,
},
.probe = au1xpsc_pcm_drvprobe,
@@ -421,8 +393,6 @@ static struct platform_driver au1xpsc_pcm_driver = {
static int __init au1xpsc_audio_dbdma_load(void)
{
- au1xpsc_audio_pcmdma[PCM_TX] = NULL;
- au1xpsc_audio_pcmdma[PCM_RX] = NULL;
return platform_driver_register(&au1xpsc_pcm_driver);
}
@@ -460,7 +430,7 @@ struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev)
res[1].start = res[1].end = id[1];
res[0].flags = res[1].flags = IORESOURCE_DMA;
- pd = platform_device_alloc("au1xpsc-pcm", -1);
+ pd = platform_device_alloc("au1xpsc-pcm", pdev->id);
if (!pd)
goto out;
diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c
index 6a9516c..d0db66f 100644
--- a/sound/soc/au1x/psc-ac97.c
+++ b/sound/soc/au1x/psc-ac97.c
@@ -10,9 +10,6 @@
*
* Au1xxx-PSC AC97 glue.
*
- * NOTE: all of these drivers can only work with a SINGLE instance
- * of a PSC. Multiple independent audio devices are impossible
- * with ASoC v1.
*/
#include <linux/init.h>
@@ -56,12 +53,29 @@
/* instance data. There can be only one, MacLeod!!!! */
static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
+#if 0
+
+/* this could theoretically work, but ac97->bus->card->private_data can be NULL
+ * when snd_ac97_mixer() is called; I don't know if the rest further down the
+ * chain are always valid either.
+ */
+static inline struct au1xpsc_audio_data *ac97_to_pscdata(struct snd_ac97 *x)
+{
+ struct snd_soc_card *c = x->bus->card->private_data;
+ return snd_soc_dai_get_drvdata(c->rtd->cpu_dai);
+}
+
+#else
+
+#define ac97_to_pscdata(x) au1xpsc_ac97_workdata
+
+#endif
+
/* AC97 controller reads codec register */
static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
unsigned short retry, tmo;
unsigned long data;
@@ -102,8 +116,7 @@ static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
unsigned int tmo, retry;
au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
@@ -134,8 +147,7 @@ static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
/* AC97 controller asserts a warm reset */
static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata));
au_sync();
@@ -146,8 +158,7 @@ static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
int i;
/* disable PSC during cold reset */
@@ -202,8 +213,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
unsigned long r, ro, stat;
int chans, t, stype = SUBSTREAM_TYPE(substream);
@@ -283,8 +293,7 @@ out:
static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
- /* FIXME */
- struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int ret, stype = SUBSTREAM_TYPE(substream);
ret = 0;
@@ -325,7 +334,7 @@ static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = {
.hw_params = au1xpsc_ac97_hw_params,
};
-struct snd_soc_dai_driver au1xpsc_ac97_dai = {
+static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = {
.ac97_control = 1,
.probe = au1xpsc_ac97_probe,
.playback = {
@@ -342,7 +351,6 @@ struct snd_soc_dai_driver au1xpsc_ac97_dai = {
},
.ops = &au1xpsc_ac97_dai_ops,
};
-EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai);
static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
{
@@ -351,9 +359,6 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
unsigned long sel;
struct au1xpsc_audio_data *wd;
- if (au1xpsc_ac97_workdata)
- return -EBUSY;
-
wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
if (!wd)
return -ENOMEM;
@@ -387,14 +392,20 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(wd));
au_sync();
- ret = snd_soc_register_dai(&pdev->dev, &au1xpsc_ac97_dai);
+ /* name the DAI like this device instance ("au1xpsc-ac97.PSCINDEX") */
+ memcpy(&wd->dai_drv, &au1xpsc_ac97_dai_template,
+ sizeof(struct snd_soc_dai_driver));
+ wd->dai_drv.name = dev_name(&pdev->dev);
+
+ platform_set_drvdata(pdev, wd);
+
+ ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
if (ret)
goto out1;
wd->dmapd = au1xpsc_pcm_add(pdev);
if (wd->dmapd) {
- platform_set_drvdata(pdev, wd);
- au1xpsc_ac97_workdata = wd; /* MDEV */
+ au1xpsc_ac97_workdata = wd;
return 0;
}
@@ -477,7 +488,7 @@ static struct dev_pm_ops au1xpscac97_pmops = {
static struct platform_driver au1xpsc_ac97_driver = {
.driver = {
- .name = "au1xpsc-ac97",
+ .name = "au1xpsc_ac97",
.owner = THIS_MODULE,
.pm = AU1XPSCAC97_PMOPS,
},
diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c
index 94e560a..fca0912 100644
--- a/sound/soc/au1x/psc-i2s.c
+++ b/sound/soc/au1x/psc-i2s.c
@@ -10,9 +10,6 @@
*
* Au1xxx-PSC I2S glue.
*
- * NOTE: all of these drivers can only work with a SINGLE instance
- * of a PSC. Multiple independent audio devices are impossible
- * with ASoC v1.
* NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
*/
@@ -54,13 +51,10 @@
((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
-/* instance data. There can be only one, MacLeod!!!! */
-static struct au1xpsc_audio_data *au1xpsc_i2s_workdata;
-
static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
- struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);
unsigned long ct;
int ret;
@@ -120,7 +114,7 @@ static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
- struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int cfgbits;
unsigned long stat;
@@ -245,7 +239,7 @@ static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
- struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+ struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
int ret, stype = SUBSTREAM_TYPE(substream);
switch (cmd) {
@@ -263,19 +257,13 @@ static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
return ret;
}
-static int au1xpsc_i2s_probe(struct snd_soc_dai *dai)
-{
- return au1xpsc_i2s_workdata ? 0 : -ENODEV;
-}
-
static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
.trigger = au1xpsc_i2s_trigger,
.hw_params = au1xpsc_i2s_hw_params,
.set_fmt = au1xpsc_i2s_set_fmt,
};
-static struct snd_soc_dai_driver au1xpsc_i2s_dai = {
- .probe = au1xpsc_i2s_probe,
+static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {
.playback = {
.rates = AU1XPSC_I2S_RATES,
.formats = AU1XPSC_I2S_FMTS,
@@ -298,9 +286,6 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
int ret;
struct au1xpsc_audio_data *wd;
- if (au1xpsc_i2s_workdata)
- return -EBUSY;
-
wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
if (!wd)
return -ENOMEM;
@@ -337,17 +322,21 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
* time out.
*/
- ret = snd_soc_register_dai(&pdev->dev, &au1xpsc_i2s_dai);
+ /* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */
+ memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,
+ sizeof(struct snd_soc_dai_driver));
+ wd->dai_drv.name = dev_name(&pdev->dev);
+
+ platform_set_drvdata(pdev, wd);
+
+ ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
if (ret)
goto out1;
/* finally add the DMA device for this PSC */
wd->dmapd = au1xpsc_pcm_add(pdev);
- if (wd->dmapd) {
- platform_set_drvdata(pdev, wd);
- au1xpsc_i2s_workdata = wd;
+ if (wd->dmapd)
return 0;
- }
snd_soc_unregister_dai(&pdev->dev);
out1:
@@ -376,8 +365,6 @@ static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)
release_mem_region(r->start, resource_size(r));
kfree(wd);
- au1xpsc_i2s_workdata = NULL; /* MDEV */
-
return 0;
}
@@ -427,7 +414,7 @@ static struct dev_pm_ops au1xpsci2s_pmops = {
static struct platform_driver au1xpsc_i2s_driver = {
.driver = {
- .name = "au1xpsc",
+ .name = "au1xpsc_i2s",
.owner = THIS_MODULE,
.pm = AU1XPSCI2S_PMOPS,
},
@@ -437,7 +424,6 @@ static struct platform_driver au1xpsc_i2s_driver = {
static int __init au1xpsc_i2s_load(void)
{
- au1xpsc_i2s_workdata = NULL;
return platform_driver_register(&au1xpsc_i2s_driver);
}
diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
index f281443..b30eadd 100644
--- a/sound/soc/au1x/psc.h
+++ b/sound/soc/au1x/psc.h
@@ -8,16 +8,11 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
- * NOTE: all of these drivers can only work with a SINGLE instance
- * of a PSC. Multiple independent audio devices are impossible
- * with ASoC v1.
*/
#ifndef _AU1X_PCM_H
#define _AU1X_PCM_H
-extern struct snd_ac97_bus_ops soc_ac97_ops;
-
/* DBDMA helpers */
extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
@@ -28,6 +23,8 @@ struct au1xpsc_audio_data {
unsigned long cfg;
unsigned long rate;
+ struct snd_soc_dai_driver dai_drv;
+
unsigned long pm[2];
struct mutex lock;
struct platform_device *dmapd;
--
1.7.2
3
10
Hi all,
I'm struggling with getting the correct names for controls, so that they
show up correctly in the Playback or Capture modes of alsamixer
E.g. "PCM 3 Capture Meter" appears in the playback controls?
How is capture vs playback display determined?
or to put it another way: What are the rules for creating control names.
Here is a reduced schematic of one of our cards (in reality more
channels e.g. 4 physical stereo ins and outs, 12 players, 8 recorders)
All players and inputs are mixed to outputs.
All inputs and players multiplexed to each capture.
How should all these controls be named?
(1)-[vol]--+
(2)-[vol]--+
[play0]--[vol]--[meter]--[mode]--,-[vol]--+----[vol]-[meter]--[level]--[lineout0]
`-[vol]--|-.
| |
.-[vol]--' |
[play1]--[vol]--[meter]--[mode]--'-[vol]----+--[vol]-[meter]--[level]--[lineout1]
(1)-[vol]----+
(2)-[vol]----+
[linein0]-[level]-\ \
mux>-[meter]-(1)-,-------|\
[digitalin0]------/ | mux >--[mode]--[meter]--[cap0]
| ,../
| |
`--|--\
[linein1]-[level]-\ | >--[mode]--[meter]--[cap1]
mux>-[meter]-(2)-----'--/
[digitalin1]------/
notes
[level] means calibrated reference level setting in dBu
[mode] stereo channel modifier
+ summing junction
--|-- wires cross, no join
(1) connect from input to matrix mix output
thanks and regards
--
Eliot Blennerhassett
AudioScience Inc.
8
21
From: Vinod Koul <vinod.koul(a)intel.com>
This is the Intel SST audio driver.
As compared to the previous versions it has all the printks and other stuff
noted cleaned up and more hardware support. The Aava support is disabled in
this patch (is_aava resolves to 0) because the Aava board detection logic
is not yet upstream.
The driver itself is a combination of a traditional ALSA driver and a
hardware assisted offload driver which can play audio while the processor
is asleep but which can't do all the more interactive stuff.
In the general case most software would use the ALSA interface, but the
other interface is needed for certain classes of use such as music playback
on highly power consumption sensitive devices.
This is going to staging primarily because it depends upon the staging memrar
driver.
Signed-off-by: Vinod Koul <vinod.koul(a)intel.com>
Signed-off-by: Harsha Priya <priya.harsha(a)intel.com>
[Merged together and tweaked for -next]
Signed-off-by: Alan Cox <alan(a)linux.intel.com>
---
drivers/staging/Kconfig | 2
drivers/staging/Makefile | 1
drivers/staging/intel_sst/TODO | 12
drivers/staging/intel_sst/intel_sst.c | 512 ++++++++
drivers/staging/intel_sst/intel_sst.h | 131 ++
.../staging/intel_sst/intel_sst_app_interface.c | 1234 +++++++++++++++++++
drivers/staging/intel_sst/intel_sst_common.h | 618 ++++++++++
.../staging/intel_sst/intel_sst_drv_interface.c | 492 ++++++++
drivers/staging/intel_sst/intel_sst_dsp.c | 486 ++++++++
drivers/staging/intel_sst/intel_sst_fw_ipc.h | 393 ++++++
drivers/staging/intel_sst/intel_sst_ioctl.h | 435 +++++++
drivers/staging/intel_sst/intel_sst_ipc.c | 656 ++++++++++
drivers/staging/intel_sst/intel_sst_pvt.c | 311 +++++
drivers/staging/intel_sst/intel_sst_stream.c | 575 +++++++++
.../staging/intel_sst/intel_sst_stream_encoded.c | 1275 ++++++++++++++++++++
drivers/staging/intel_sst/intelmid.c | 1233 +++++++++++++++++++
drivers/staging/intel_sst/intelmid.h | 186 +++
drivers/staging/intel_sst/intelmid_ctrl.c | 629 ++++++++++
drivers/staging/intel_sst/intelmid_msic_control.c | 410 ++++++
drivers/staging/intel_sst/intelmid_pvt.c | 174 +++
drivers/staging/intel_sst/intelmid_snd_control.h | 114 ++
drivers/staging/intel_sst/intelmid_v0_control.c | 771 ++++++++++++
drivers/staging/intel_sst/intelmid_v1_control.c | 1072 +++++++++++++++++
drivers/staging/intel_sst/intelmid_v2_control.c | 1001 ++++++++++++++++
drivers/staging/intel_sst/jack.h | 10
25 files changed, 12733 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/intel_sst/TODO
create mode 100644 drivers/staging/intel_sst/intel_sst.c
create mode 100644 drivers/staging/intel_sst/intel_sst.h
create mode 100644 drivers/staging/intel_sst/intel_sst_app_interface.c
create mode 100644 drivers/staging/intel_sst/intel_sst_common.h
create mode 100644 drivers/staging/intel_sst/intel_sst_drv_interface.c
create mode 100644 drivers/staging/intel_sst/intel_sst_dsp.c
create mode 100644 drivers/staging/intel_sst/intel_sst_fw_ipc.h
create mode 100644 drivers/staging/intel_sst/intel_sst_ioctl.h
create mode 100644 drivers/staging/intel_sst/intel_sst_ipc.c
create mode 100644 drivers/staging/intel_sst/intel_sst_pvt.c
create mode 100644 drivers/staging/intel_sst/intel_sst_stream.c
create mode 100644 drivers/staging/intel_sst/intel_sst_stream_encoded.c
create mode 100644 drivers/staging/intel_sst/intelmid.c
create mode 100644 drivers/staging/intel_sst/intelmid.h
create mode 100644 drivers/staging/intel_sst/intelmid_ctrl.c
create mode 100644 drivers/staging/intel_sst/intelmid_msic_control.c
create mode 100644 drivers/staging/intel_sst/intelmid_pvt.c
create mode 100644 drivers/staging/intel_sst/intelmid_snd_control.h
create mode 100644 drivers/staging/intel_sst/intelmid_v0_control.c
create mode 100644 drivers/staging/intel_sst/intelmid_v1_control.c
create mode 100644 drivers/staging/intel_sst/intelmid_v2_control.c
create mode 100644 drivers/staging/intel_sst/jack.h
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index e00ef92..bb069cf 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -165,5 +165,7 @@ source "drivers/staging/bcm/Kconfig"
source "drivers/staging/ft1000/Kconfig"
+source "drivers/staging/intel_sst/Kconfig"
+
endif # !STAGING_EXCLUDE_BUILD
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 35eacc9..9457e07 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -63,3 +63,4 @@ obj-$(CONFIG_ATH6K_LEGACY) += ath6kl/
obj-$(CONFIG_USB_ENESTORAGE) += keucr/
obj-$(CONFIG_BCM_WIMAX) += bcm/
obj-$(CONFIG_FT1000) += ft1000/
+obj-$(CONFIG_SND_INTEL_SST) += intel_sst/
diff --git a/drivers/staging/intel_sst/TODO b/drivers/staging/intel_sst/TODO
new file mode 100644
index 0000000..58d6d2d
--- /dev/null
+++ b/drivers/staging/intel_sst/TODO
@@ -0,0 +1,12 @@
+TODO
+----
+
+Get the memrar driver cleaned up and upstream (dependancy blocking SST)
+Get the jack header entries accepted
+Review the printks and kill off any left over ST_ERR: messages
+Review the misc device ioctls for 32/64bit safety and sanity
+Review the misc device ioctls for size safety depending on config and decide
+ if space/unused areas should be left
+
+Anything the sound folks turn up on full review
+
diff --git a/drivers/staging/intel_sst/intel_sst.c b/drivers/staging/intel_sst/intel_sst.c
new file mode 100644
index 0000000..24d3928
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst.c
@@ -0,0 +1,512 @@
+/*
+ * intel_sst.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ *
+ * This file contains all init functions
+ */
+
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/miscdevice.h>
+#include <asm/mrst.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+
+MODULE_AUTHOR("Vinod Koul <vinod.koul(a)intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha(a)intel.com>");
+MODULE_AUTHOR("Dharageswari R <dharageswari.r(a)intel.com>");
+MODULE_AUTHOR("KP Jeeja <jeeja.kp(a)intel.com>");
+MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(SST_DRIVER_VERSION);
+
+struct intel_sst_drv *sst_drv_ctx;
+static struct mutex drv_ctx_lock;
+struct class *sst_class;
+
+/* fops Routines */
+static const struct file_operations intel_sst_fops = {
+ .owner = THIS_MODULE,
+ .open = intel_sst_open,
+ .release = intel_sst_release,
+ .read = intel_sst_read,
+ .write = intel_sst_write,
+ .unlocked_ioctl = intel_sst_ioctl,
+ .mmap = intel_sst_mmap,
+ .aio_read = intel_sst_aio_read,
+ .aio_write = intel_sst_aio_write,
+};
+static const struct file_operations intel_sst_fops_cntrl = {
+ .owner = THIS_MODULE,
+ .open = intel_sst_open_cntrl,
+ .release = intel_sst_release_cntrl,
+ .unlocked_ioctl = intel_sst_ioctl,
+};
+
+static struct miscdevice lpe_dev = {
+ .minor = MISC_DYNAMIC_MINOR,/* dynamic allocation */
+ .name = "intel_sst",/* /dev/intel_sst */
+ .fops = &intel_sst_fops
+};
+
+
+static struct miscdevice lpe_ctrl = {
+ .minor = MISC_DYNAMIC_MINOR,/* dynamic allocation */
+ .name = "intel_sst_ctrl",/* /dev/intel_sst_ctrl */
+ .fops = &intel_sst_fops_cntrl
+};
+
+/**
+* intel_sst_interrupt - Interrupt service routine for SST
+*
+* @irq: irq number of interrupt
+* @context: pointer to device structre
+*
+* This function is called by OS when SST device raises
+* an interrupt. This will be result of write in IPC register
+* Source can be busy or done interrupt
+*/
+static irqreturn_t intel_sst_interrupt(int irq, void *context)
+{
+ union interrupt_reg isr;
+ union ipc_header header;
+ union interrupt_reg imr;
+ struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
+ unsigned int size = 0, str_id;
+ struct stream_info *stream ;
+
+ /* Interrupt arrived, check src */
+ isr.full = sst_shim_read(drv->shim, SST_ISRX);
+
+ if (isr.part.busy_interrupt) {
+ header.full = sst_shim_read(drv->shim, SST_IPCD);
+ if (header.part.msg_id == IPC_SST_PERIOD_ELAPSED) {
+ sst_clear_interrupt();
+ str_id = header.part.str_id;
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->period_elapsed)
+ stream->period_elapsed(stream->pcm_substream);
+ return IRQ_HANDLED;
+ }
+ if (header.part.large)
+ size = header.part.data;
+ if (header.part.msg_id & REPLY_MSG) {
+ sst_drv_ctx->ipc_process_msg.header = header;
+ memcpy_fromio(sst_drv_ctx->ipc_process_msg.mailbox,
+ drv->mailbox + SST_MAILBOX_RCV, size);
+ queue_work(sst_drv_ctx->process_msg_wq,
+ &sst_drv_ctx->ipc_process_msg.wq);
+ } else {
+ sst_drv_ctx->ipc_process_reply.header = header;
+ memcpy_fromio(sst_drv_ctx->ipc_process_reply.mailbox,
+ drv->mailbox + SST_MAILBOX_RCV, size);
+ queue_work(sst_drv_ctx->process_reply_wq,
+ &sst_drv_ctx->ipc_process_reply.wq);
+ }
+ /* mask busy inetrrupt */
+ imr.full = sst_shim_read(drv->shim, SST_IMRX);
+ imr.part.busy_interrupt = 1;
+ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full);
+ return IRQ_HANDLED;
+ } else if (isr.part.done_interrupt) {
+ /* Clear done bit */
+ header.full = sst_shim_read(drv->shim, SST_IPCX);
+ header.part.done = 0;
+ sst_shim_write(sst_drv_ctx->shim, SST_IPCX, header.full);
+ /* write 1 to clear status register */;
+ isr.part.done_interrupt = 1;
+ /* dummy register for shim workaround */
+ sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full);
+ queue_work(sst_drv_ctx->post_msg_wq,
+ &sst_drv_ctx->ipc_post_msg.wq);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+
+}
+
+
+/*
+* intel_sst_probe - PCI probe function
+*
+* @pci: PCI device structure
+* @pci_id: PCI device ID structure
+*
+* This function is called by OS when a device is found
+* This enables the device, interrupt etc
+*/
+static int __devinit intel_sst_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ int i, ret = 0;
+
+ pr_debug("sst: Probe for DID %x\n", pci->device);
+ mutex_lock(&drv_ctx_lock);
+ if (sst_drv_ctx) {
+ pr_err("sst: Only one sst handle is supported\n");
+ mutex_unlock(&drv_ctx_lock);
+ return -EBUSY;
+ }
+
+ sst_drv_ctx = kzalloc(sizeof(*sst_drv_ctx), GFP_KERNEL);
+ if (!sst_drv_ctx) {
+ pr_err("sst: intel_sst malloc fail\n");
+ mutex_unlock(&drv_ctx_lock);
+ return -ENOMEM;
+ }
+ mutex_unlock(&drv_ctx_lock);
+
+ sst_drv_ctx->pci_id = pci->device;
+
+ mutex_init(&sst_drv_ctx->stream_lock);
+ mutex_init(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
+
+ sst_drv_ctx->stream_cnt = 0;
+ sst_drv_ctx->encoded_cnt = 0;
+ sst_drv_ctx->am_cnt = 0;
+ sst_drv_ctx->pb_streams = 0;
+ sst_drv_ctx->cp_streams = 0;
+ sst_drv_ctx->unique_id = 0;
+ sst_drv_ctx->pmic_port_instance = SST_DEFAULT_PMIC_PORT;
+
+ INIT_LIST_HEAD(&sst_drv_ctx->ipc_dispatch_list);
+ INIT_WORK(&sst_drv_ctx->ipc_post_msg.wq, sst_post_message);
+ INIT_WORK(&sst_drv_ctx->ipc_process_msg.wq, sst_process_message);
+ INIT_WORK(&sst_drv_ctx->ipc_process_reply.wq, sst_process_reply);
+ INIT_WORK(&sst_drv_ctx->mad_ops.wq, sst_process_mad_ops);
+ init_waitqueue_head(&sst_drv_ctx->wait_queue);
+
+ sst_drv_ctx->mad_wq = create_workqueue("sst_mad_wq");
+ if (!sst_drv_ctx->mad_wq)
+ goto do_free_drv_ctx;
+ sst_drv_ctx->post_msg_wq = create_workqueue("sst_post_msg_wq");
+ if (!sst_drv_ctx->post_msg_wq)
+ goto free_mad_wq;
+ sst_drv_ctx->process_msg_wq = create_workqueue("sst_process_msg_wqq");
+ if (!sst_drv_ctx->process_msg_wq)
+ goto free_post_msg_wq;
+ sst_drv_ctx->process_reply_wq = create_workqueue("sst_proces_reply_wq");
+ if (!sst_drv_ctx->process_reply_wq)
+ goto free_process_msg_wq;
+
+ for (i = 0; i < MAX_ACTIVE_STREAM; i++) {
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ sst_drv_ctx->alloc_block[i].ops_block.condition = false;
+ }
+ spin_lock_init(&sst_drv_ctx->list_spin_lock);
+
+ sst_drv_ctx->max_streams = pci_id->driver_data;
+ pr_debug("sst: Got drv data max stream %d\n",
+ sst_drv_ctx->max_streams);
+ for (i = 1; i <= sst_drv_ctx->max_streams; i++) {
+ struct stream_info *stream = &sst_drv_ctx->streams[i];
+ INIT_LIST_HEAD(&stream->bufs);
+ mutex_init(&stream->lock);
+ spin_lock_init(&stream->pcm_lock);
+ }
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ sst_drv_ctx->mmap_mem = NULL;
+ sst_drv_ctx->mmap_len = SST_MMAP_PAGES * PAGE_SIZE;
+ while (sst_drv_ctx->mmap_len > 0) {
+ sst_drv_ctx->mmap_mem =
+ kzalloc(sst_drv_ctx->mmap_len, GFP_KERNEL);
+ if (sst_drv_ctx->mmap_mem) {
+ pr_debug("sst: Got memory %p size 0x%x\n",
+ sst_drv_ctx->mmap_mem,
+ sst_drv_ctx->mmap_len);
+ break;
+ }
+ if (sst_drv_ctx->mmap_len < (SST_MMAP_STEP*PAGE_SIZE)) {
+ pr_err("sst: mem alloc fail...abort!!\n");
+ ret = -ENOMEM;
+ goto free_process_reply_wq;
+ }
+ sst_drv_ctx->mmap_len -= (SST_MMAP_STEP * PAGE_SIZE);
+ pr_debug("sst:mem alloc failed...trying %d\n",
+ sst_drv_ctx->mmap_len);
+ }
+ }
+
+ /* Init the device */
+ ret = pci_enable_device(pci);
+ if (ret) {
+ pr_err("sst: device cant be enabled\n");
+ goto do_free_mem;
+ }
+ sst_drv_ctx->pci = pci_dev_get(pci);
+ ret = pci_request_regions(pci, SST_DRV_NAME);
+ if (ret)
+ goto do_disable_device;
+ /* map registers */
+ /* SST Shim */
+ sst_drv_ctx->shim_phy_add = pci_resource_start(pci, 1);
+ sst_drv_ctx->shim = pci_ioremap_bar(pci, 1);
+ if (!sst_drv_ctx->shim)
+ goto do_release_regions;
+ pr_debug("sst: SST Shim Ptr %p\n", sst_drv_ctx->shim);
+
+ /* Shared SRAM */
+ sst_drv_ctx->mailbox = pci_ioremap_bar(pci, 2);
+ if (!sst_drv_ctx->mailbox)
+ goto do_unmap_shim;
+ pr_debug("sst: SRAM Ptr %p\n", sst_drv_ctx->mailbox);
+
+ /* IRAM */
+ sst_drv_ctx->iram = pci_ioremap_bar(pci, 3);
+ if (!sst_drv_ctx->iram)
+ goto do_unmap_sram;
+ pr_debug("sst:IRAM Ptr %p\n", sst_drv_ctx->iram);
+
+ /* DRAM */
+ sst_drv_ctx->dram = pci_ioremap_bar(pci, 4);
+ if (!sst_drv_ctx->dram)
+ goto do_unmap_iram;
+ pr_debug("sst: DRAM Ptr %p\n", sst_drv_ctx->dram);
+
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ /* Register the ISR */
+ ret = request_irq(pci->irq, intel_sst_interrupt,
+ IRQF_SHARED, SST_DRV_NAME, sst_drv_ctx);
+ if (ret)
+ goto do_unmap_dram;
+ pr_debug("sst: Registered IRQ 0x%x\n", pci->irq);
+
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ ret = misc_register(&lpe_dev);
+ if (ret) {
+ pr_err("sst: couldn't register LPE device\n");
+ goto do_free_irq;
+ }
+
+ /*Register LPE Control as misc driver*/
+ ret = misc_register(&lpe_ctrl);
+ if (ret) {
+ pr_err("sst: couldn't register misc driver\n");
+ goto do_free_irq;
+ }
+ }
+ sst_drv_ctx->lpe_stalled = 0;
+ pr_debug("sst: ...successfully done!!!\n");
+ return ret;
+
+do_free_irq:
+ free_irq(pci->irq, sst_drv_ctx);
+do_unmap_dram:
+ iounmap(sst_drv_ctx->dram);
+do_unmap_iram:
+ iounmap(sst_drv_ctx->iram);
+do_unmap_sram:
+ iounmap(sst_drv_ctx->mailbox);
+do_unmap_shim:
+ iounmap(sst_drv_ctx->shim);
+do_release_regions:
+ pci_release_regions(pci);
+do_disable_device:
+ pci_disable_device(pci);
+do_free_mem:
+ kfree(sst_drv_ctx->mmap_mem);
+free_process_reply_wq:
+ destroy_workqueue(sst_drv_ctx->process_reply_wq);
+free_process_msg_wq:
+ destroy_workqueue(sst_drv_ctx->process_msg_wq);
+free_post_msg_wq:
+ destroy_workqueue(sst_drv_ctx->post_msg_wq);
+free_mad_wq:
+ destroy_workqueue(sst_drv_ctx->mad_wq);
+do_free_drv_ctx:
+ kfree(sst_drv_ctx);
+ pr_err("sst: Probe failed with 0x%x\n", ret);
+ return ret;
+}
+
+/**
+* intel_sst_remove - PCI remove function
+*
+* @pci: PCI device structure
+*
+* This function is called by OS when a device is unloaded
+* This frees the interrupt etc
+*/
+static void __devexit intel_sst_remove(struct pci_dev *pci)
+{
+ pci_dev_put(sst_drv_ctx->pci);
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ misc_deregister(&lpe_dev);
+ misc_deregister(&lpe_ctrl);
+ }
+ free_irq(pci->irq, sst_drv_ctx);
+ iounmap(sst_drv_ctx->dram);
+ iounmap(sst_drv_ctx->iram);
+ iounmap(sst_drv_ctx->mailbox);
+ iounmap(sst_drv_ctx->shim);
+ sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ kfree(sst_drv_ctx->mmap_mem);
+ flush_scheduled_work();
+ destroy_workqueue(sst_drv_ctx->process_reply_wq);
+ destroy_workqueue(sst_drv_ctx->process_msg_wq);
+ destroy_workqueue(sst_drv_ctx->post_msg_wq);
+ destroy_workqueue(sst_drv_ctx->mad_wq);
+ kfree(sst_drv_ctx);
+ pci_release_region(pci, 1);
+ pci_release_region(pci, 2);
+ pci_release_region(pci, 3);
+ pci_release_region(pci, 4);
+ pci_release_region(pci, 5);
+ pci_set_drvdata(pci, NULL);
+}
+
+/* Power Management */
+/*
+* intel_sst_suspend - PCI suspend function
+*
+* @pci: PCI device structure
+* @state: PM message
+*
+* This function is called by OS when a power event occurs
+*/
+int intel_sst_suspend(struct pci_dev *pci, pm_message_t state)
+{
+ union config_status_reg csr;
+
+ pr_debug("sst: intel_sst_suspend called\n");
+
+ if (sst_drv_ctx->pb_streams != 0 || sst_drv_ctx->cp_streams != 0)
+ return -EPERM;
+ /*Assert RESET on LPE Processor*/
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.full = csr.full | 0x2;
+ /* Move the SST state to Suspended */
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_SUSPENDED;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ pci_set_drvdata(pci, sst_drv_ctx);
+ pci_save_state(pci);
+ pci_disable_device(pci);
+ pci_set_power_state(pci, PCI_D3hot);
+ return 0;
+}
+
+/**
+* intel_sst_resume - PCI resume function
+*
+* @pci: PCI device structure
+*
+* This function is called by OS when a power event occurs
+*/
+int intel_sst_resume(struct pci_dev *pci)
+{
+ int ret = 0;
+
+ pr_debug("sst: intel_sst_resume called\n");
+ if (sst_drv_ctx->sst_state != SST_SUSPENDED) {
+ pr_err("sst: SST is not in suspended state\n");
+ return -EPERM;
+ }
+ sst_drv_ctx = pci_get_drvdata(pci);
+ pci_set_power_state(pci, PCI_D0);
+ pci_restore_state(pci);
+ ret = pci_enable_device(pci);
+ if (ret)
+ pr_err("sst: device cant be enabled\n");
+
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ return 0;
+}
+
+/* PCI Routines */
+static struct pci_device_id intel_sst_ids[] = {
+ { PCI_VDEVICE(INTEL, SST_MRST_PCI_ID), 3},
+ { PCI_VDEVICE(INTEL, SST_MFLD_PCI_ID), 6},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, intel_sst_ids);
+
+static struct pci_driver driver = {
+ .name = SST_DRV_NAME,
+ .id_table = intel_sst_ids,
+ .probe = intel_sst_probe,
+ .remove = __devexit_p(intel_sst_remove),
+#ifdef CONFIG_PM
+ .suspend = intel_sst_suspend,
+ .resume = intel_sst_resume,
+#endif
+};
+
+/**
+* intel_sst_init - Module init function
+*
+* Registers with PCI
+* Registers with /dev
+* Init all data strutures
+*/
+static int __init intel_sst_init(void)
+{
+ /* Init all variables, data structure etc....*/
+ int ret = 0;
+ pr_debug("sst: INFO: ******** SST DRIVER loading.. Ver: %s\n",
+ SST_DRIVER_VERSION);
+
+ mutex_init(&drv_ctx_lock);
+ /* Register with PCI */
+ ret = pci_register_driver(&driver);
+ if (ret)
+ pr_err("sst: PCI register failed\n");
+ return ret;
+}
+
+/**
+* intel_sst_exit - Module exit function
+*
+* Unregisters with PCI
+* Unregisters with /dev
+* Frees all data strutures
+*/
+static void __exit intel_sst_exit(void)
+{
+ pci_unregister_driver(&driver);
+
+ pr_debug("sst: driver unloaded\n");
+ return;
+}
+
+module_init(intel_sst_init);
+module_exit(intel_sst_exit);
diff --git a/drivers/staging/intel_sst/intel_sst.h b/drivers/staging/intel_sst/intel_sst.h
new file mode 100644
index 0000000..1f19f0d
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst.h
@@ -0,0 +1,131 @@
+#ifndef __INTEL_SST_H__
+#define __INTEL_SST_H__
+/*
+ * intel_sst.h - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ * This file is shared between the SST and MAD drivers
+ */
+
+#define SST_CARD_NAMES "intel_mid_card"
+
+/* control list Pmic & Lpe */
+/* Input controls */
+enum port_status {
+ ACTIVATE = 1,
+ DEACTIVATE,
+};
+
+/* Card states */
+enum sst_card_states {
+ SND_CARD_UN_INIT = 0,
+ SND_CARD_INIT_DONE,
+};
+
+enum sst_controls {
+ SST_SND_ALLOC = 0x1000,
+ SST_SND_PAUSE = 0x1001,
+ SST_SND_RESUME = 0x1002,
+ SST_SND_DROP = 0x1003,
+ SST_SND_FREE = 0x1004,
+ SST_SND_BUFFER_POINTER = 0x1005,
+ SST_SND_STREAM_INIT = 0x1006,
+ SST_SND_START = 0x1007,
+ SST_SND_STREAM_PROCESS = 0x1008,
+ SST_MAX_CONTROLS = 0x1008,
+ SST_CONTROL_BASE = 0x1000,
+ SST_ENABLE_RX_TIME_SLOT = 0x1009,
+};
+
+enum SND_CARDS {
+ SND_FS = 0,
+ SND_MX,
+ SND_NC,
+ SND_MSIC
+};
+
+struct pcm_stream_info {
+ int str_id;
+ void *mad_substream;
+ void (*period_elapsed) (void *mad_substream);
+ unsigned long long buffer_ptr;
+ int sfreq;
+};
+
+struct snd_pmic_ops {
+ int card_status;
+ int master_mute;
+ int num_channel;
+ int input_dev_id;
+ int mute_status;
+ int pb_on;
+ int cap_on;
+ int output_dev_id;
+ int (*set_input_dev) (u8 value);
+ int (*set_output_dev) (u8 value);
+
+ int (*set_mute) (int dev_id, u8 value);
+ int (*get_mute) (int dev_id, u8 *value);
+
+ int (*set_vol) (int dev_id, int value);
+ int (*get_vol) (int dev_id, int *value);
+
+ int (*init_card) (void);
+ int (*set_pcm_audio_params)
+ (int sfreq, int word_size , int num_channel);
+ int (*set_pcm_voice_params) (void);
+ int (*set_voice_port) (int status);
+ int (*set_audio_port) (int status);
+
+ int (*power_up_pmic_pb) (unsigned int port);
+ int (*power_up_pmic_cp) (unsigned int port);
+ int (*power_down_pmic_pb) (void);
+ int (*power_down_pmic_cp) (void);
+ int (*power_down_pmic) (void);
+};
+
+struct intel_sst_card_ops {
+ char *module_name;
+ unsigned int vendor_id;
+ int (*control_set) (int control_element, void *value);
+ struct snd_pmic_ops *scard_ops;
+};
+
+/* modified for generic access */
+struct sc_reg_access {
+ u16 reg_addr;
+ u8 value;
+ u8 mask;
+};
+enum sc_reg_access_type {
+ PMIC_READ = 0,
+ PMIC_WRITE,
+ PMIC_READ_MODIFY,
+};
+
+int register_sst_card(struct intel_sst_card_ops *card);
+void unregister_sst_card(struct intel_sst_card_ops *card);
+#endif /* __INTEL_SST_H__ */
diff --git a/drivers/staging/intel_sst/intel_sst_app_interface.c b/drivers/staging/intel_sst/intel_sst_app_interface.c
new file mode 100644
index 0000000..9f31dc5
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_app_interface.c
@@ -0,0 +1,1234 @@
+/*
+ * intel_sst_interface.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * Jeeja KP <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ * Upper layer interfaces (MAD driver, MMF) to SST driver
+ */
+
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/uio.h>
+#include <linux/aio.h>
+#include <linux/uaccess.h>
+#include <linux/firmware.h>
+#include <linux/ioctl.h>
+#include <linux/smp_lock.h>
+#ifdef CONFIG_MRST_RAR_HANDLER
+#include <linux/rar_register.h>
+#include "../../../drivers/staging/memrar/memrar.h"
+#endif
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+#define AM_MODULE 1
+#define STREAM_MODULE 0
+
+
+/**
+* intel_sst_check_device - checks SST device
+*
+* This utility function checks the state of SST device and downlaods FW if
+* not done, or resumes the device if suspended
+*/
+
+static int intel_sst_check_device(void)
+{
+ int retval = 0;
+ if (sst_drv_ctx->pmic_state != SND_MAD_INIT_DONE) {
+ pr_warn("sst: Sound card not availble\n ");
+ return -EIO;
+ }
+ if (sst_drv_ctx->sst_state == SST_SUSPENDED) {
+ pr_debug("sst: Resuming from Suspended state\n");
+ retval = intel_sst_resume(sst_drv_ctx->pci);
+ if (retval) {
+ pr_debug("sst: Resume Failed= %#x,abort\n", retval);
+ return retval;
+ }
+ }
+
+ if (sst_drv_ctx->sst_state == SST_UN_INIT) {
+ /* FW is not downloaded */
+ retval = sst_download_fw();
+ if (retval)
+ return -ENODEV;
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ retval = sst_drv_ctx->rx_time_slot_status;
+ if (retval != RX_TIMESLOT_UNINIT
+ && sst_drv_ctx->pmic_vendor != SND_NC)
+ sst_enable_rx_timeslot(retval);
+ }
+ }
+ return 0;
+}
+
+/**
+ * intel_sst_open - opens a handle to driver
+ *
+ * @i_node: inode structure
+ * @file_ptr:pointer to file
+ *
+ * This function is called by OS when a user space component
+ * tries to get a driver handle. Only one handle at a time
+ * will be allowed
+ */
+int intel_sst_open(struct inode *i_node, struct file *file_ptr)
+{
+ unsigned int retval = intel_sst_check_device();
+ if (retval)
+ return retval;
+
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ if (sst_drv_ctx->encoded_cnt < MAX_ENC_STREAM) {
+ struct ioctl_pvt_data *data =
+ kzalloc(sizeof(struct ioctl_pvt_data), GFP_KERNEL);
+ if (!data) {
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ return -ENOMEM;
+ }
+
+ sst_drv_ctx->encoded_cnt++;
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ data->pvt_id = sst_assign_pvt_id(sst_drv_ctx);
+ data->str_id = 0;
+ file_ptr->private_data = (void *)data;
+ pr_debug("sst: pvt_id handle = %d!\n", data->pvt_id);
+ } else {
+ retval = -EUSERS;
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ }
+ return retval;
+}
+
+/**
+ * intel_sst_open_cntrl - opens a handle to driver
+ *
+ * @i_node: inode structure
+ * @file_ptr:pointer to file
+ *
+ * This function is called by OS when a user space component
+ * tries to get a driver handle to /dev/intel_sst_control.
+ * Only one handle at a time will be allowed
+ * This is for control operations only
+ */
+int intel_sst_open_cntrl(struct inode *i_node, struct file *file_ptr)
+{
+ unsigned int retval = intel_sst_check_device();
+ if (retval)
+ return retval;
+
+ /* audio manager open */
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ if (sst_drv_ctx->am_cnt < MAX_AM_HANDLES) {
+ sst_drv_ctx->am_cnt++;
+ pr_debug("sst: AM handle opened...\n");
+ file_ptr->private_data = NULL;
+ } else
+ retval = -EACCES;
+
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ return retval;
+}
+
+/**
+ * intel_sst_release - releases a handle to driver
+ *
+ * @i_node: inode structure
+ * @file_ptr: pointer to file
+ *
+ * This function is called by OS when a user space component
+ * tries to release a driver handle.
+ */
+int intel_sst_release(struct inode *i_node, struct file *file_ptr)
+{
+ struct ioctl_pvt_data *data = file_ptr->private_data;
+
+ pr_debug("sst: Release called, closing app handle\n");
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_drv_ctx->encoded_cnt--;
+ sst_drv_ctx->stream_cnt--;
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ free_stream_context(data->str_id);
+ kfree(data);
+ return 0;
+}
+
+int intel_sst_release_cntrl(struct inode *i_node, struct file *file_ptr)
+{
+ /* audio manager close */
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_drv_ctx->am_cnt--;
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ pr_debug("sst: AM handle closed\n");
+ return 0;
+}
+
+/**
+* intel_sst_mmap - mmaps a kernel buffer to user space for copying data
+*
+* @vma: vm area structure instance
+* @file_ptr: pointer to file
+*
+* This function is called by OS when a user space component
+* tries to get mmap memory from driver
+*/
+int intel_sst_mmap(struct file *file_ptr, struct vm_area_struct *vma)
+{
+ int retval, length;
+ struct ioctl_pvt_data *data =
+ (struct ioctl_pvt_data *)file_ptr->private_data;
+ int str_id = data->str_id;
+ void *mem_area;
+
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return -EINVAL;
+
+ length = vma->vm_end - vma->vm_start;
+ pr_debug("sst: called for stream %d length 0x%x\n", str_id, length);
+
+ if (length > sst_drv_ctx->mmap_len)
+ return -ENOMEM;
+ if (!sst_drv_ctx->mmap_mem)
+ return -EIO;
+
+ /* round it up to the page bondary */
+ /*mem_area = (void *)((((unsigned long)sst_drv_ctx->mmap_mem)
+ + PAGE_SIZE - 1) & PAGE_MASK);*/
+ mem_area = (void *) PAGE_ALIGN((unsigned int) sst_drv_ctx->mmap_mem);
+
+ /* map the whole physically contiguous area in one piece */
+ retval = remap_pfn_range(vma,
+ vma->vm_start,
+ virt_to_phys((void *)mem_area) >> PAGE_SHIFT,
+ length,
+ vma->vm_page_prot);
+ if (retval)
+ sst_drv_ctx->streams[str_id].mmapped = false;
+ else
+ sst_drv_ctx->streams[str_id].mmapped = true;
+
+ pr_debug("sst: mmap ret 0x%x\n", retval);
+ return retval;
+}
+
+/* sets mmap data buffers to play/capture*/
+static int intel_sst_mmap_play_capture(u32 str_id,
+ struct snd_sst_mmap_buffs *mmap_buf)
+{
+ struct sst_stream_bufs *bufs;
+ int retval, i;
+ struct stream_info *stream;
+ struct snd_sst_mmap_buff_entry *buf_entry;
+
+ pr_debug("sst:called for str_id %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return -EINVAL;
+ BUG_ON(!mmap_buf);
+
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->mmapped != true)
+ return -EIO;
+
+ if (stream->status == STREAM_UN_INIT ||
+ stream->status == STREAM_DECODE) {
+ return -EBADRQC;
+ }
+ stream->curr_bytes = 0;
+ stream->cumm_bytes = 0;
+
+ pr_debug("sst:new buffers count %d status %d\n",
+ mmap_buf->entries, stream->status);
+ buf_entry = mmap_buf->buff;
+ for (i = 0; i < mmap_buf->entries; i++) {
+ BUG_ON(!buf_entry);
+ bufs = kzalloc(sizeof(*bufs), GFP_KERNEL);
+ if (!bufs)
+ return -ENOMEM;
+ bufs->size = buf_entry->size;
+ bufs->offset = buf_entry->offset;
+ bufs->addr = sst_drv_ctx->mmap_mem;
+ bufs->in_use = false;
+ buf_entry++;
+ /* locking here */
+ mutex_lock(&stream->lock);
+ list_add_tail(&bufs->node, &stream->bufs);
+ mutex_unlock(&stream->lock);
+ }
+
+ mutex_lock(&stream->lock);
+ stream->data_blk.condition = false;
+ stream->data_blk.ret_code = 0;
+ if (stream->status == STREAM_INIT &&
+ stream->prev != STREAM_UN_INIT &&
+ stream->need_draining != true) {
+ stream->prev = stream->status;
+ stream->status = STREAM_RUNNING;
+ if (stream->ops == STREAM_OPS_PLAYBACK) {
+ if (sst_play_frame(str_id) < 0) {
+ pr_warn("sst: play frames fail\n");
+ mutex_unlock(&stream->lock);
+ return -EIO;
+ }
+ } else if (stream->ops == STREAM_OPS_CAPTURE) {
+ if (sst_capture_frame(str_id) < 0) {
+ pr_warn("sst: capture frame fail\n");
+ mutex_unlock(&stream->lock);
+ return -EIO;
+ }
+ }
+ }
+ mutex_unlock(&stream->lock);
+ /* Block the call for reply */
+ if (!list_empty(&stream->bufs)) {
+ stream->data_blk.on = true;
+ retval = sst_wait_interruptible(sst_drv_ctx,
+ &stream->data_blk);
+ }
+
+ if (retval >= 0)
+ retval = stream->cumm_bytes;
+ pr_debug("sst:end of play/rec ioctl bytes = %d!!\n", retval);
+ return retval;
+}
+
+/*sets user data buffers to play/capture*/
+static int intel_sst_play_capture(struct stream_info *stream, int str_id)
+{
+ int retval;
+
+ stream->data_blk.ret_code = 0;
+ stream->data_blk.on = true;
+ stream->data_blk.condition = false;
+
+ mutex_lock(&stream->lock);
+ if (stream->status == STREAM_INIT && stream->prev != STREAM_UN_INIT) {
+ /* stream is started */
+ stream->prev = stream->status;
+ stream->status = STREAM_RUNNING;
+ }
+
+ if (stream->status == STREAM_INIT && stream->prev == STREAM_UN_INIT) {
+ /* stream is not started yet */
+ pr_debug("sst: Stream isn't in started state %d, prev %d\n",
+ stream->status, stream->prev);
+ } else if ((stream->status == STREAM_RUNNING ||
+ stream->status == STREAM_PAUSED) &&
+ stream->need_draining != true) {
+ /* stream is started */
+ if (stream->ops == STREAM_OPS_PLAYBACK ||
+ stream->ops == STREAM_OPS_PLAYBACK_DRM) {
+ if (sst_play_frame(str_id) < 0) {
+ pr_warn("sst: play frames failed\n");
+ mutex_unlock(&stream->lock);
+ return -EIO;
+ }
+ } else if (stream->ops == STREAM_OPS_CAPTURE) {
+ if (sst_capture_frame(str_id) < 0) {
+ pr_warn("sst: capture frames failed\n ");
+ mutex_unlock(&stream->lock);
+ return -EIO;
+ }
+ }
+ } else {
+ mutex_unlock(&stream->lock);
+ return -EIO;
+ }
+ mutex_unlock(&stream->lock);
+ /* Block the call for reply */
+
+ retval = sst_wait_interruptible(sst_drv_ctx, &stream->data_blk);
+ if (retval) {
+ stream->status = STREAM_INIT;
+ pr_debug("sst: wait returned error...\n");
+ }
+ return retval;
+}
+
+/* fills kernel list with buffer addresses for SST DSP driver to process*/
+static int snd_sst_fill_kernel_list(struct stream_info *stream,
+ const struct iovec *iovec, unsigned long nr_segs,
+ struct list_head *copy_to_list)
+{
+ struct sst_stream_bufs *stream_bufs;
+ unsigned long index, data_not_copied, mmap_len;
+ unsigned char *bufp;
+ unsigned long size, copied_size;
+ int retval = 0, add_to_list = 0;
+ static int sent_offset;
+ static unsigned long sent_index;
+
+ stream_bufs = kzalloc(sizeof(*stream_bufs), GFP_KERNEL);
+ if (!stream_bufs)
+ return -ENOMEM;
+ stream_bufs->addr = sst_drv_ctx->mmap_mem;
+#ifdef CONFIG_MRST_RAR_HANDLER
+ if (stream->ops == STREAM_OPS_PLAYBACK_DRM) {
+ for (index = stream->sg_index; index < nr_segs; index++) {
+ __u32 rar_handle;
+ struct sst_stream_bufs *stream_bufs =
+ kzalloc(sizeof(*stream_bufs), GFP_KERNEL);
+
+ stream->sg_index = index;
+ if (!stream_bufs)
+ return -ENOMEM;
+ retval = copy_from_user((void *) &rar_handle,
+ iovec[index].iov_base,
+ sizeof(__u32));
+ if (retval != 0)
+ return -EFAULT;
+ stream_bufs->addr = (char *)rar_handle;
+ stream_bufs->in_use = false;
+ stream_bufs->size = iovec[0].iov_len;
+ /* locking here */
+ mutex_lock(&stream->lock);
+ list_add_tail(&stream_bufs->node, &stream->bufs);
+ mutex_unlock(&stream->lock);
+ }
+ stream->sg_index = index;
+ return retval;
+ }
+#endif
+ mmap_len = sst_drv_ctx->mmap_len;
+ stream_bufs->addr = sst_drv_ctx->mmap_mem;
+ bufp = stream->cur_ptr;
+
+ copied_size = 0;
+
+ if (!stream->sg_index)
+ sent_index = sent_offset = 0;
+
+ for (index = stream->sg_index; index < nr_segs; index++) {
+ stream->sg_index = index;
+ if (!stream->cur_ptr)
+ bufp = iovec[index].iov_base;
+
+ size = ((unsigned long)iovec[index].iov_base
+ + iovec[index].iov_len) - (unsigned long) bufp;
+
+ if ((copied_size + size) > mmap_len)
+ size = mmap_len - copied_size;
+
+
+ if (stream->ops == STREAM_OPS_PLAYBACK) {
+ data_not_copied = copy_from_user(
+ (void *)(stream_bufs->addr + copied_size),
+ bufp, size);
+ if (data_not_copied > 0) {
+ /* Clean up the list and return error code */
+ retval = -EFAULT;
+ break;
+ }
+ } else if (stream->ops == STREAM_OPS_CAPTURE) {
+ struct snd_sst_user_cap_list *entry =
+ kzalloc(sizeof(*entry), GFP_KERNEL);
+
+ if (!entry) {
+ kfree(stream_bufs);
+ return -ENOMEM;
+ }
+ entry->iov_index = index;
+ entry->iov_offset = (unsigned long) bufp -
+ (unsigned long)iovec[index].iov_base;
+ entry->offset = copied_size;
+ entry->size = size;
+ list_add_tail(&entry->node, copy_to_list);
+ }
+
+ stream->cur_ptr = bufp + size;
+
+ if (((unsigned long)iovec[index].iov_base
+ + iovec[index].iov_len) <
+ ((unsigned long)iovec[index].iov_base)) {
+ pr_debug("sst: Buffer overflows");
+ kfree(stream_bufs);
+ return -EINVAL;
+ }
+
+ if (((unsigned long)iovec[index].iov_base
+ + iovec[index].iov_len) ==
+ (unsigned long)stream->cur_ptr) {
+ stream->cur_ptr = NULL;
+ stream->sg_index++;
+ }
+
+ copied_size += size;
+ pr_debug("sst: copied_size - %lx\n", copied_size);
+ if ((copied_size >= mmap_len) ||
+ (stream->sg_index == nr_segs)) {
+ add_to_list = 1;
+ }
+
+ if (add_to_list) {
+ stream_bufs->in_use = false;
+ stream_bufs->size = copied_size;
+ /* locking here */
+ mutex_lock(&stream->lock);
+ list_add_tail(&stream_bufs->node, &stream->bufs);
+ mutex_unlock(&stream->lock);
+ break;
+ }
+ }
+ return retval;
+}
+
+/* This function copies the captured data returned from SST DSP engine
+ * to the user buffers*/
+static int snd_sst_copy_userbuf_capture(struct stream_info *stream,
+ const struct iovec *iovec,
+ struct list_head *copy_to_list)
+{
+ struct snd_sst_user_cap_list *entry, *_entry;
+ struct sst_stream_bufs *kbufs = NULL, *_kbufs;
+ int retval = 0;
+ unsigned long data_not_copied;
+
+ /* copy sent buffers */
+ pr_debug("sst: capture stream copying to user now...\n");
+ list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) {
+ if (kbufs->in_use == true) {
+ /* copy to user */
+ list_for_each_entry_safe(entry, _entry,
+ copy_to_list, node) {
+ data_not_copied = copy_to_user((void *)
+ iovec[entry->iov_index].iov_base +
+ entry->iov_offset,
+ kbufs->addr + entry->offset,
+ entry->size);
+ if (data_not_copied > 0) {
+ /* Clean up the list and return error */
+ retval = -EFAULT;
+ break;
+ }
+ list_del(&entry->node);
+ kfree(entry);
+ }
+ }
+ }
+ pr_debug("sst: end of cap copy\n");
+ return retval;
+}
+
+/*
+ * snd_sst_userbufs_play_cap - constructs the list from user buffers
+ *
+ * @iovec:pointer to iovec structure
+ * @nr_segs:number entries in the iovec structure
+ * @str_id:stream id
+ * @stream:pointer to stream_info structure
+ *
+ * This function will traverse the user list and copy the data to the kernel
+ * space buffers.
+ */
+static int snd_sst_userbufs_play_cap(const struct iovec *iovec,
+ unsigned long nr_segs, unsigned int str_id,
+ struct stream_info *stream)
+{
+ int retval;
+ LIST_HEAD(copy_to_list);
+
+
+ retval = snd_sst_fill_kernel_list(stream, iovec, nr_segs,
+ ©_to_list);
+
+ retval = intel_sst_play_capture(stream, str_id);
+ if (retval < 0)
+ return retval;
+
+ if (stream->ops == STREAM_OPS_CAPTURE) {
+ retval = snd_sst_copy_userbuf_capture(stream, iovec,
+ ©_to_list);
+ }
+ return retval;
+}
+
+/* This function is common function across read/write
+ for user buffers called from system calls*/
+static int intel_sst_read_write(unsigned int str_id, char __user *buf,
+ size_t count)
+{
+ int retval;
+ struct stream_info *stream;
+ struct iovec iovec;
+ unsigned long nr_segs;
+
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return -EINVAL;
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->mmapped == true) {
+ pr_warn("sst: user write and stream is mapped");
+ return -EIO;
+ }
+ if (!count)
+ return -EINVAL;
+ stream->curr_bytes = 0;
+ stream->cumm_bytes = 0;
+ /* copy user buf details */
+ pr_debug("sst: new buffers %p, copy size %d, status %d\n" ,
+ buf, (int) count, (int) stream->status);
+
+ stream->buf_type = SST_BUF_USER_STATIC;
+ iovec.iov_base = (void *)buf;
+ iovec.iov_len = count;
+ nr_segs = 1;
+
+ do {
+ retval = snd_sst_userbufs_play_cap(
+ &iovec, nr_segs, str_id, stream);
+ if (retval < 0)
+ break;
+
+ } while (stream->sg_index < nr_segs);
+
+ stream->sg_index = 0;
+ stream->cur_ptr = NULL;
+ if (retval >= 0)
+ retval = stream->cumm_bytes;
+ pr_debug("sst: end of play/rec bytes = %d!!\n", retval);
+ return retval;
+}
+
+/***
+ * intel_sst_write - This function is called when user tries to play out data
+ *
+ * @file_ptr:pointer to file
+ * @buf:user buffer to be played out
+ * @count:size of tthe buffer
+ * @offset:offset to start from
+ *
+ * writes the encoded data into DSP
+ */
+int intel_sst_write(struct file *file_ptr, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct ioctl_pvt_data *data = file_ptr->private_data;
+ int str_id = data->str_id;
+ struct stream_info *stream = &sst_drv_ctx->streams[str_id];
+
+ pr_debug("sst: called for %d\n", str_id);
+ if (stream->status == STREAM_UN_INIT ||
+ stream->status == STREAM_DECODE) {
+ return -EBADRQC;
+ }
+ return intel_sst_read_write(str_id, (char __user *)buf, count);
+}
+
+/*
+ * intel_sst_aio_write - write buffers
+ *
+ * @kiocb:pointer to a structure containing file pointer
+ * @iov:list of user buffer to be played out
+ * @nr_segs:number of entries
+ * @offset:offset to start from
+ *
+ * This function is called when user tries to play out multiple data buffers
+ */
+ssize_t intel_sst_aio_write(struct kiocb *kiocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t offset)
+{
+ int retval;
+ struct ioctl_pvt_data *data = kiocb->ki_filp->private_data;
+ int str_id = data->str_id;
+ struct stream_info *stream;
+
+ pr_debug("sst: entry - %ld\n", nr_segs);
+
+ if (is_sync_kiocb(kiocb) == false)
+ return -EINVAL;
+
+ pr_debug("sst: called for str_id %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return -EINVAL;
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->mmapped == true)
+ return -EIO;
+ if (stream->status == STREAM_UN_INIT ||
+ stream->status == STREAM_DECODE) {
+ return -EBADRQC;
+ }
+ stream->curr_bytes = 0;
+ stream->cumm_bytes = 0;
+ pr_debug("sst: new segs %ld, offset %d, status %d\n" ,
+ nr_segs, (int) offset, (int) stream->status);
+ stream->buf_type = SST_BUF_USER_STATIC;
+ do {
+ retval = snd_sst_userbufs_play_cap(iov, nr_segs,
+ str_id, stream);
+ if (retval < 0)
+ break;
+
+ } while (stream->sg_index < nr_segs);
+
+ stream->sg_index = 0;
+ stream->cur_ptr = NULL;
+ if (retval >= 0)
+ retval = stream->cumm_bytes;
+ pr_debug("sst: end of play/rec bytes = %d!!\n", retval);
+ return retval;
+}
+
+/*
+ * intel_sst_read - read the encoded data
+ *
+ * @file_ptr: pointer to file
+ * @buf: user buffer to be filled with captured data
+ * @count: size of tthe buffer
+ * @offset: offset to start from
+ *
+ * This function is called when user tries to capture data
+ */
+int intel_sst_read(struct file *file_ptr, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct ioctl_pvt_data *data = file_ptr->private_data;
+ int str_id = data->str_id;
+ struct stream_info *stream = &sst_drv_ctx->streams[str_id];
+
+ pr_debug("sst: called for %d\n", str_id);
+ if (stream->status == STREAM_UN_INIT ||
+ stream->status == STREAM_DECODE)
+ return -EBADRQC;
+ return intel_sst_read_write(str_id, buf, count);
+}
+
+/*
+ * intel_sst_aio_read - aio read
+ *
+ * @kiocb: pointer to a structure containing file pointer
+ * @iov: list of user buffer to be filled with captured
+ * @nr_segs: number of entries
+ * @offset: offset to start from
+ *
+ * This function is called when user tries to capture out multiple data buffers
+ */
+ssize_t intel_sst_aio_read(struct kiocb *kiocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t offset)
+{
+ int retval;
+ struct ioctl_pvt_data *data = kiocb->ki_filp->private_data;
+ int str_id = data->str_id;
+ struct stream_info *stream;
+
+ pr_debug("sst: entry - %ld\n", nr_segs);
+
+ if (is_sync_kiocb(kiocb) == false) {
+ pr_debug("sst: aio_read from user space is not allowed\n");
+ return -EINVAL;
+ }
+
+ pr_debug("sst: called for str_id %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return -EINVAL;
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->mmapped == true)
+ return -EIO;
+ if (stream->status == STREAM_UN_INIT ||
+ stream->status == STREAM_DECODE)
+ return -EBADRQC;
+ stream->curr_bytes = 0;
+ stream->cumm_bytes = 0;
+
+ pr_debug("sst: new segs %ld, offset %d, status %d\n" ,
+ nr_segs, (int) offset, (int) stream->status);
+ stream->buf_type = SST_BUF_USER_STATIC;
+ do {
+ retval = snd_sst_userbufs_play_cap(iov, nr_segs,
+ str_id, stream);
+ if (retval < 0)
+ break;
+
+ } while (stream->sg_index < nr_segs);
+
+ stream->sg_index = 0;
+ stream->cur_ptr = NULL;
+ if (retval >= 0)
+ retval = stream->cumm_bytes;
+ pr_debug("sst: end of play/rec bytes = %d!!\n", retval);
+ return retval;
+}
+
+/* sst_print_stream_params - prints the stream parameters (debug fn)*/
+static void sst_print_stream_params(struct snd_sst_get_stream_params *get_prm)
+{
+ pr_debug("sst: codec params:result =%d\n",
+ get_prm->codec_params.result);
+ pr_debug("sst: codec params:stream = %d\n",
+ get_prm->codec_params.stream_id);
+ pr_debug("sst: codec params:codec = %d\n",
+ get_prm->codec_params.codec);
+ pr_debug("sst: codec params:ops = %d\n",
+ get_prm->codec_params.ops);
+ pr_debug("sst: codec params:stream_type= %d\n",
+ get_prm->codec_params.stream_type);
+ pr_debug("sst: pcmparams:sfreq= %d\n",
+ get_prm->pcm_params.sfreq);
+ pr_debug("sst: pcmparams:num_chan= %d\n",
+ get_prm->pcm_params.num_chan);
+ pr_debug("sst: pcmparams:pcm_wd_sz= %d\n",
+ get_prm->pcm_params.pcm_wd_sz);
+ return;
+}
+
+/**
+ * intel_sst_ioctl - recieves the device ioctl's
+ * @file_ptr:pointer to file
+ * @cmd:Ioctl cmd
+ * @arg:data
+ *
+ * This function is called by OS when a user space component
+ * sends an Ioctl to SST driver
+ */
+long intel_sst_ioctl(struct file *file_ptr, unsigned int cmd, unsigned long arg)
+{
+ int retval = 0;
+ struct ioctl_pvt_data *data = NULL;
+ int str_id = 0, minor = 0;
+
+ lock_kernel();
+
+ data = file_ptr->private_data;
+ if (data) {
+ minor = 0;
+ str_id = data->str_id;
+ } else
+ minor = 1;
+
+ if (sst_drv_ctx->sst_state != SST_FW_RUNNING) {
+ unlock_kernel();
+ return -EBUSY;
+ }
+
+ switch (_IOC_NR(cmd)) {
+ case _IOC_NR(SNDRV_SST_STREAM_PAUSE):
+ pr_debug("sst: IOCTL_PAUSE recieved for %d!\n", str_id);
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ retval = sst_pause_stream(str_id);
+ break;
+
+ case _IOC_NR(SNDRV_SST_STREAM_RESUME):
+ pr_debug("sst: SNDRV_SST_IOCTL_RESUME recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ retval = sst_resume_stream(str_id);
+ break;
+
+ case _IOC_NR(SNDRV_SST_STREAM_SET_PARAMS): {
+ struct snd_sst_params *str_param = (struct snd_sst_params *)arg;
+
+ pr_debug("sst: IOCTL_SET_PARAMS recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+
+ if (!str_id) {
+
+ retval = sst_get_stream(str_param);
+ if (retval > 0) {
+ struct stream_info *str_info;
+ sst_drv_ctx->stream_cnt++;
+ data->str_id = retval;
+ str_info = &sst_drv_ctx->streams[retval];
+ str_info->src = SST_DRV;
+ retval = copy_to_user(&str_param->stream_id,
+ &retval, sizeof(__u32));
+ } else {
+ if (retval == -SST_ERR_INVALID_PARAMS)
+ retval = -EINVAL;
+ }
+ } else {
+ pr_debug("sst: SET_STREAM_PARAMS recieved!\n");
+ /* allocated set params only */
+ retval = sst_set_stream_param(str_id, str_param);
+ /* Block the call for reply */
+ if (!retval) {
+ int sfreq = 0, word_size = 0, num_channel = 0;
+ sfreq = str_param->sparams.uc.pcm_params.sfreq;
+ word_size = str_param->sparams.
+ uc.pcm_params.pcm_wd_sz;
+ num_channel = str_param->
+ sparams.uc.pcm_params.num_chan;
+ if (str_param->ops == STREAM_OPS_CAPTURE) {
+ sst_drv_ctx->scard_ops->\
+ set_pcm_audio_params(sfreq,
+ word_size, num_channel);
+ }
+ }
+ }
+ break;
+ }
+ case _IOC_NR(SNDRV_SST_SET_VOL): {
+ struct snd_sst_vol *set_vol;
+ struct snd_sst_vol *rec_vol = (struct snd_sst_vol *)arg;
+ pr_debug("sst: SET_VOLUME recieved for %d!\n",
+ rec_vol->stream_id);
+ if (minor == STREAM_MODULE && rec_vol->stream_id == 0) {
+ pr_debug("sst: invalid operation!\n");
+ retval = -EPERM;
+ break;
+ }
+ set_vol = kzalloc(sizeof(*set_vol), GFP_ATOMIC);
+ if (!set_vol) {
+ pr_debug("sst: mem allocation failed\n");
+ retval = -ENOMEM;
+ break;
+ }
+ retval = copy_from_user(set_vol, rec_vol, sizeof(*set_vol));
+ if (retval) {
+ pr_debug("sst: copy failed\n");
+ retval = -EAGAIN;
+ break;
+ }
+ retval = sst_set_vol(set_vol);
+ kfree(set_vol);
+ break;
+ }
+ case _IOC_NR(SNDRV_SST_GET_VOL): {
+ struct snd_sst_vol *rec_vol = (struct snd_sst_vol *)arg;
+ struct snd_sst_vol get_vol;
+ pr_debug("sst: IOCTL_GET_VOLUME recieved for stream = %d!\n",
+ rec_vol->stream_id);
+ if (minor == STREAM_MODULE && rec_vol->stream_id == 0) {
+ pr_debug("sst: invalid operation!\n");
+ retval = -EPERM;
+ break;
+ }
+ get_vol.stream_id = rec_vol->stream_id;
+ retval = sst_get_vol(&get_vol);
+ if (retval) {
+ retval = -EIO;
+ break;
+ }
+ pr_debug("sst: id:%d\n, vol:%d, ramp_dur:%d, ramp_type:%d\n",
+ get_vol.stream_id, get_vol.volume,
+ get_vol.ramp_duration, get_vol.ramp_type);
+ retval = copy_to_user((struct snd_sst_vol *)arg,
+ &get_vol, sizeof(get_vol));
+ if (retval) {
+ retval = -EIO;
+ break;
+ }
+ /*sst_print_get_vol_info(str_id, &get_vol);*/
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_MUTE): {
+ struct snd_sst_mute *set_mute;
+ struct snd_sst_vol *rec_mute = (struct snd_sst_vol *)arg;
+ pr_debug("sst: SNDRV_SST_SET_VOLUME recieved for %d!\n",
+ rec_mute->stream_id);
+ if (minor == STREAM_MODULE && rec_mute->stream_id == 0) {
+ retval = -EPERM;
+ break;
+ }
+ set_mute = kzalloc(sizeof(*set_mute), GFP_ATOMIC);
+ if (!set_mute) {
+ retval = -ENOMEM;
+ break;
+ }
+ retval = copy_from_user(set_mute, rec_mute, sizeof(*set_mute));
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = sst_set_mute(set_mute);
+ kfree(set_mute);
+ break;
+ }
+ case _IOC_NR(SNDRV_SST_STREAM_GET_PARAMS): {
+ struct snd_sst_get_stream_params get_params;
+
+ pr_debug("sst: IOCTL_GET_PARAMS recieved!\n");
+ if (minor != 0) {
+ retval = -EBADRQC;
+ break;
+ }
+
+ retval = sst_get_stream_params(str_id, &get_params);
+ if (retval) {
+ retval = -EIO;
+ break;
+ }
+ retval = copy_to_user((struct snd_sst_get_stream_params *)arg,
+ &get_params, sizeof(get_params));
+ if (retval) {
+ retval = -EBUSY;
+ break;
+ }
+ sst_print_stream_params(&get_params);
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_MMAP_PLAY):
+ case _IOC_NR(SNDRV_SST_MMAP_CAPTURE):
+ pr_debug("sst: SNDRV_SST_MMAP_PLAY/CAPTURE recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ retval = intel_sst_mmap_play_capture(str_id,
+ (struct snd_sst_mmap_buffs *)arg);
+ break;
+
+ case _IOC_NR(SNDRV_SST_STREAM_DROP):
+ pr_debug("sst: SNDRV_SST_IOCTL_DROP recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EINVAL;
+ break;
+ }
+ retval = sst_drop_stream(str_id);
+ break;
+
+ case _IOC_NR(SNDRV_SST_STREAM_GET_TSTAMP): {
+ unsigned long long *ms = (unsigned long long *)arg;
+ struct snd_sst_tstamp tstamp = {0};
+ unsigned long long time, freq, mod;
+
+ pr_debug("sst: SNDRV_SST_STREAM_GET_TSTAMP recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ memcpy_fromio(&tstamp,
+ ((void *)(sst_drv_ctx->mailbox + SST_TIME_STAMP)
+ +(str_id * sizeof(tstamp))),
+ sizeof(tstamp));
+ time = tstamp.samples_rendered;
+ freq = (unsigned long long) tstamp.sampling_frequency;
+ time = time * 1000; /* converting it to ms */
+ mod = do_div(time, freq);
+ retval = copy_to_user(ms, &time, sizeof(*ms));
+ if (retval)
+ retval = -EFAULT;
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_STREAM_START):{
+ struct stream_info *stream;
+
+ pr_debug("sst: SNDRV_SST_STREAM_START recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EINVAL;
+ break;
+ }
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ break;
+ stream = &sst_drv_ctx->streams[str_id];
+ mutex_lock(&stream->lock);
+ if (stream->status == STREAM_INIT &&
+ stream->need_draining != true) {
+ stream->prev = stream->status;
+ stream->status = STREAM_RUNNING;
+ if (stream->ops == STREAM_OPS_PLAYBACK ||
+ stream->ops == STREAM_OPS_PLAYBACK_DRM) {
+ retval = sst_play_frame(str_id);
+ } else if (stream->ops == STREAM_OPS_CAPTURE)
+ retval = sst_capture_frame(str_id);
+ else {
+ retval = -EINVAL;
+ mutex_unlock(
+ &sst_drv_ctx->streams[str_id].lock);
+ break;
+ }
+ if (retval < 0) {
+ stream->status = STREAM_INIT;
+ mutex_unlock(
+ &sst_drv_ctx->streams[str_id].lock);
+ break;
+ }
+ } else {
+ retval = -EINVAL;
+ }
+ mutex_unlock(&sst_drv_ctx->streams[str_id].lock);
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_SET_TARGET_DEVICE): {
+ struct snd_sst_target_device *target_device;
+
+ pr_debug("sst: SET_TARGET_DEVICE recieved!\n");
+ target_device = (struct snd_sst_target_device *)arg;
+ BUG_ON(!target_device);
+ if (minor != AM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ retval = sst_target_device_select(target_device);
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_DRIVER_INFO): {
+ struct snd_sst_driver_info *info =
+ (struct snd_sst_driver_info *)arg;
+
+ pr_debug("sst: SNDRV_SST_DRIVER_INFO recived\n");
+ info->version = SST_VERSION_NUM;
+ /* hard coding, shud get sumhow later */
+ info->active_pcm_streams = sst_drv_ctx->stream_cnt -
+ sst_drv_ctx->encoded_cnt;
+ info->active_enc_streams = sst_drv_ctx->encoded_cnt;
+ info->max_pcm_streams = MAX_ACTIVE_STREAM - MAX_ENC_STREAM;
+ info->max_enc_streams = MAX_ENC_STREAM;
+ info->buf_per_stream = sst_drv_ctx->mmap_len;
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_STREAM_DECODE): {
+ struct snd_sst_dbufs *param =
+ (struct snd_sst_dbufs *)arg, dbufs_local;
+ int i;
+ struct snd_sst_buffs ibufs, obufs;
+ struct snd_sst_buff_entry ibuf_temp[param->ibufs->entries],
+ obuf_temp[param->obufs->entries];
+
+ pr_debug("sst: SNDRV_SST_STREAM_DECODE recived\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EBADRQC;
+ break;
+ }
+ if (!param) {
+ retval = -EINVAL;
+ break;
+ }
+
+ dbufs_local.input_bytes_consumed = param->input_bytes_consumed;
+ dbufs_local.output_bytes_produced =
+ param->output_bytes_produced;
+ dbufs_local.ibufs = &ibufs;
+ dbufs_local.obufs = &obufs;
+ dbufs_local.ibufs->entries = param->ibufs->entries;
+ dbufs_local.ibufs->type = param->ibufs->type;
+ dbufs_local.obufs->entries = param->obufs->entries;
+ dbufs_local.obufs->type = param->obufs->type;
+
+ dbufs_local.ibufs->buff_entry = ibuf_temp;
+ for (i = 0; i < dbufs_local.ibufs->entries; i++) {
+ ibuf_temp[i].buffer =
+ param->ibufs->buff_entry[i].buffer;
+ ibuf_temp[i].size =
+ param->ibufs->buff_entry[i].size;
+ }
+ dbufs_local.obufs->buff_entry = obuf_temp;
+ for (i = 0; i < dbufs_local.obufs->entries; i++) {
+ obuf_temp[i].buffer =
+ param->obufs->buff_entry[i].buffer;
+ obuf_temp[i].size =
+ param->obufs->buff_entry[i].size;
+ }
+ retval = sst_decode(str_id, &dbufs_local);
+ if (retval)
+ retval = -EAGAIN;
+ retval = copy_to_user(¶m->input_bytes_consumed,
+ &dbufs_local.input_bytes_consumed,
+ sizeof(unsigned long long));
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = copy_to_user(¶m->output_bytes_produced,
+ &dbufs_local.output_bytes_produced,
+ sizeof(unsigned long long));
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case _IOC_NR(SNDRV_SST_STREAM_DRAIN):
+ pr_debug("sst: SNDRV_SST_STREAM_DRAIN recived\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EINVAL;
+ break;
+ }
+ retval = sst_drain_stream(str_id);
+ break;
+
+ case _IOC_NR(SNDRV_SST_STREAM_BYTES_DECODED): {
+ unsigned long long *bytes = (unsigned long long *)arg;
+ struct snd_sst_tstamp tstamp = {0};
+
+ pr_debug("sst: STREAM_BYTES_DECODED recieved!\n");
+ if (minor != STREAM_MODULE) {
+ retval = -EINVAL;
+ break;
+ }
+ memcpy_fromio(&tstamp,
+ ((void *)(sst_drv_ctx->mailbox + SST_TIME_STAMP)
+ +(str_id * sizeof(tstamp))),
+ sizeof(tstamp));
+ retval = copy_to_user(bytes, &tstamp.bytes_processed,
+ sizeof(*bytes));
+ if (retval)
+ retval = -EFAULT;
+ break;
+ }
+ case _IOC_NR(SNDRV_SST_FW_INFO): {
+ struct snd_sst_fw_info *fw_info;
+
+ pr_debug("sst: SNDRV_SST_FW_INFO recived\n");
+
+ fw_info = kzalloc(sizeof(*fw_info), GFP_ATOMIC);
+ if (!fw_info) {
+ retval = -ENOMEM;
+ break;
+ }
+ retval = sst_get_fw_info(fw_info);
+ if (retval) {
+ retval = -EIO;
+ kfree(fw_info);
+ break;
+ }
+ retval = copy_to_user((struct snd_sst_dbufs *)arg,
+ fw_info, sizeof(*fw_info));
+ if (retval) {
+ kfree(fw_info);
+ retval = -EFAULT;
+ break;
+ }
+ /*sst_print_fw_info(fw_info);*/
+ kfree(fw_info);
+ break;
+ }
+ default:
+ retval = -EINVAL;
+ }
+ unlock_kernel();
+ pr_debug("sst: intel_sst_ioctl:complete ret code = %d\n", retval);
+ return retval;
+}
+
diff --git a/drivers/staging/intel_sst/intel_sst_common.h b/drivers/staging/intel_sst/intel_sst_common.h
new file mode 100644
index 0000000..73a98c8
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_common.h
@@ -0,0 +1,618 @@
+#ifndef __INTEL_SST_COMMON_H__
+#define __INTEL_SST_COMMON_H__
+/*
+ * intel_sst_common.h - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Common private declarations for SST
+ */
+
+#define SST_DRIVER_VERSION "1.2.05"
+#define SST_VERSION_NUM 0x1205
+
+/* driver names */
+#define SST_DRV_NAME "intel_sst_driver"
+#define SST_FW_FILENAME_MRST "fw_sst_080a.bin"
+#define SST_FW_FILENAME_MFLD "fw_sst_082f.bin"
+#define SST_MRST_PCI_ID 0x080A
+#define SST_MFLD_PCI_ID 0x082F
+
+enum sst_states {
+ SST_FW_LOADED = 1,
+ SST_FW_RUNNING,
+ SST_UN_INIT,
+ SST_ERROR,
+ SST_SUSPENDED
+};
+
+#define MAX_ACTIVE_STREAM 3
+#define MAX_ENC_STREAM 1
+#define MAX_AM_HANDLES 1
+#define ALLOC_TIMEOUT 5000
+/* SST numbers */
+#define SST_BLOCK_TIMEOUT 5000
+#define TARGET_DEV_BLOCK_TIMEOUT 5000
+
+#define BLOCK_UNINIT -1
+#define RX_TIMESLOT_UNINIT -1
+
+/* SST register map */
+#define SST_CSR 0x00
+#define SST_PISR 0x08
+#define SST_PIMR 0x10
+#define SST_ISRX 0x18
+#define SST_IMRX 0x28
+#define SST_IPCX 0x38 /* IPC IA-SST */
+#define SST_IPCD 0x40 /* IPC SST-IA */
+#define SST_ISRD 0x20 /* dummy register for shim workaround */
+#define SST_SHIM_SIZE 0X44
+
+#define SPI_MODE_ENABLE_BASE_ADDR 0xffae4000
+#define FW_SIGNATURE_SIZE 4
+
+/* PMIC and SST hardware states */
+enum sst_mad_states {
+ SND_MAD_UN_INIT = 0,
+ SND_MAD_INIT_DONE,
+};
+
+/* stream states */
+enum sst_stream_states {
+ STREAM_UN_INIT = 0, /* Freed/Not used stream */
+ STREAM_RUNNING = 1, /* Running */
+ STREAM_PAUSED = 2, /* Paused stream */
+ STREAM_DECODE = 3, /* stream is in decoding only state */
+ STREAM_INIT = 4, /* stream init, waiting for data */
+};
+
+
+enum sst_ram_type {
+ SST_IRAM = 1,
+ SST_DRAM = 2,
+};
+/* SST shim registers to structure mapping */
+union config_status_reg {
+ struct {
+ u32 rsvd0:1;
+ u32 sst_reset:1;
+ u32 hw_rsvd:3;
+ u32 sst_clk:2;
+ u32 bypass:3;
+ u32 run_stall:1;
+ u32 rsvd1:2;
+ u32 strb_cntr_rst:1;
+ u32 rsvd:18;
+ } part;
+ u32 full;
+};
+
+union interrupt_reg {
+ struct {
+ u32 done_interrupt:1;
+ u32 busy_interrupt:1;
+ u32 rsvd:30;
+ } part;
+ u32 full;
+};
+
+union sst_pisr_reg {
+ struct {
+ u32 pssp0:1;
+ u32 pssp1:1;
+ u32 rsvd0:3;
+ u32 dmac:1;
+ u32 rsvd1:26;
+ } part;
+ u32 full;
+};
+
+union sst_pimr_reg {
+ struct {
+ u32 ssp0:1;
+ u32 ssp1:1;
+ u32 rsvd0:3;
+ u32 dmac:1;
+ u32 rsvd1:10;
+ u32 ssp0_sc:1;
+ u32 ssp1_sc:1;
+ u32 rsvd2:3;
+ u32 dmac_sc:1;
+ u32 rsvd3:10;
+ } part;
+ u32 full;
+};
+
+
+struct sst_stream_bufs {
+ struct list_head node;
+ u32 size;
+ const char *addr;
+ u32 data_copied;
+ bool in_use;
+ u32 offset;
+};
+
+struct snd_sst_user_cap_list {
+ unsigned int iov_index; /* index of iov */
+ unsigned long iov_offset; /* offset in iov */
+ unsigned long offset; /* offset in kmem */
+ unsigned long size; /* size copied */
+ struct list_head node;
+};
+/*
+This structure is used to block a user/fw data call to another
+fw/user call
+*/
+struct sst_block {
+ bool condition; /* condition for blocking check */
+ int ret_code; /* ret code when block is released */
+ void *data; /* data to be appsed for block if any */
+ bool on;
+};
+
+enum snd_sst_buf_type {
+ SST_BUF_USER_STATIC = 1,
+ SST_BUF_USER_DYNAMIC,
+ SST_BUF_MMAP_STATIC,
+ SST_BUF_MMAP_DYNAMIC,
+};
+
+enum snd_src {
+ SST_DRV = 1,
+ MAD_DRV = 2
+};
+
+/**
+ * struct stream_info - structure that holds the stream information
+ *
+ * @status : stream current state
+ * @prev : stream prev state
+ * @codec : stream codec
+ * @sst_id : stream id
+ * @ops : stream operation pb/cp/drm...
+ * @bufs: stream buffer list
+ * @lock : stream mutex for protecting state
+ * @pcm_lock : spinlock for pcm path only
+ * @mmapped : is stream mmapped
+ * @sg_index : current stream user buffer index
+ * @cur_ptr : stream user buffer pointer
+ * @buf_entry : current user buffer
+ * @data_blk : stream block for data operations
+ * @ctrl_blk : stream block for ctrl operations
+ * @buf_type : stream user buffer type
+ * @pcm_substream : PCM substream
+ * @period_elapsed : PCM period elapsed callback
+ * @sfreq : stream sampling freq
+ * @decode_ibuf : Decoded i/p buffers pointer
+ * @decode_obuf : Decoded o/p buffers pointer
+ * @decode_isize : Decoded i/p buffers size
+ * @decode_osize : Decoded o/p buffers size
+ * @decode_ibuf_type : Decoded i/p buffer type
+ * @decode_obuf_type : Decoded o/p buffer type
+ * @idecode_alloc : Decode alloc index
+ * @need_draining : stream set for drain
+ * @str_type : stream type
+ * @curr_bytes : current bytes decoded
+ * @cumm_bytes : cummulative bytes decoded
+ * @str_type : stream type
+ * @src : stream source
+ * @device : output device type (medfield only)
+ * @pcm_slot : pcm slot value
+ */
+struct stream_info {
+ unsigned int status;
+ unsigned int prev;
+ u8 codec;
+ unsigned int sst_id;
+ unsigned int ops;
+ struct list_head bufs;
+ struct mutex lock; /* mutex */
+ spinlock_t pcm_lock;
+ bool mmapped;
+ unsigned int sg_index; /* current buf Index */
+ unsigned char *cur_ptr; /* Current static bufs */
+ struct snd_sst_buf_entry *buf_entry;
+ struct sst_block data_blk; /* stream ops block */
+ struct sst_block ctrl_blk; /* stream control cmd block */
+ enum snd_sst_buf_type buf_type;
+ void *pcm_substream;
+ void (*period_elapsed) (void *pcm_substream);
+ unsigned int sfreq;
+ void *decode_ibuf, *decode_obuf;
+ unsigned int decode_isize, decode_osize;
+ u8 decode_ibuf_type, decode_obuf_type;
+ unsigned int idecode_alloc;
+ unsigned int need_draining;
+ unsigned int str_type;
+ u32 curr_bytes;
+ u32 cumm_bytes;
+ u32 src;
+ enum snd_sst_audio_device_type device;
+ u8 pcm_slot;
+};
+
+/*
+ * struct stream_alloc_bloc - this structure is used for blocking the user's
+ * alloc calls to fw's response to alloc calls
+ *
+ * @sst_id : session id of blocked stream
+ * @ops_block : ops block struture
+ */
+struct stream_alloc_block {
+ int sst_id; /* session id of blocked stream */
+ struct sst_block ops_block; /* ops block struture */
+};
+
+#define SST_FW_SIGN "$SST"
+#define SST_FW_LIB_SIGN "$LIB"
+
+/*
+ * struct fw_header - FW file headers
+ *
+ * @signature : FW signature
+ * @modules : # of modules
+ * @file_format : version of header format
+ * @reserved : reserved fields
+ */
+struct fw_header {
+ unsigned char signature[FW_SIGNATURE_SIZE]; /* FW signature */
+ u32 file_size; /* size of fw minus this header */
+ u32 modules; /* # of modules */
+ u32 file_format; /* version of header format */
+ u32 reserved[4];
+};
+
+struct fw_module_header {
+ unsigned char signature[FW_SIGNATURE_SIZE]; /* module signature */
+ u32 mod_size; /* size of module */
+ u32 blocks; /* # of blocks */
+ u32 type; /* codec type, pp lib */
+ u32 entry_point;
+};
+
+struct dma_block_info {
+ enum sst_ram_type type; /* IRAM/DRAM */
+ u32 size; /* Bytes */
+ u32 ram_offset; /* Offset in I/DRAM */
+ u32 rsvd; /* Reserved field */
+};
+
+struct ioctl_pvt_data {
+ int str_id;
+ int pvt_id;
+};
+
+struct sst_ipc_msg_wq {
+ union ipc_header header;
+ char mailbox[SST_MAILBOX_SIZE];
+ struct work_struct wq;
+};
+
+struct mad_ops_wq {
+ int stream_id;
+ enum sst_controls control_op;
+ struct work_struct wq;
+
+};
+
+#define SST_MMAP_PAGES (640*1024 / PAGE_SIZE)
+#define SST_MMAP_STEP (40*1024 / PAGE_SIZE)
+
+/***
+ * struct intel_sst_drv - driver ops
+ *
+ * @pmic_state : pmic state
+ * @pmic_vendor : pmic vendor detected
+ * @sst_state : current sst device state
+ * @pci_id : PCI device id loaded
+ * @shim : SST shim pointer
+ * @mailbox : SST mailbox pointer
+ * @iram : SST IRAM pointer
+ * @dram : SST DRAM pointer
+ * @shim_phy_add : SST shim phy addr
+ * @ipc_dispatch_list : ipc messages dispatched
+ * @ipc_post_msg_wq : wq to post IPC messages context
+ * @ipc_process_msg : wq to process msgs from FW context
+ * @ipc_process_reply : wq to process reply from FW context
+ * @ipc_post_msg : wq to post reply from FW context
+ * @mad_ops : MAD driver operations registered
+ * @mad_wq : MAD driver wq
+ * @post_msg_wq : wq to post IPC messages
+ * @process_msg_wq : wq to process msgs from FW
+ * @process_reply_wq : wq to process reply from FW
+ * @streams : sst stream contexts
+ * @alloc_block : block structure for alloc
+ * @tgt_dev_blk : block structure for target device
+ * @fw_info_blk : block structure for fw info block
+ * @vol_info_blk : block structure for vol info block
+ * @mute_info_blk : block structure for mute info block
+ * @hs_info_blk : block structure for hs info block
+ * @list_lock : sst driver list lock (deprecated)
+ * @list_spin_lock : sst driver spin lock block
+ * @scard_ops : sst card ops
+ * @pci : sst pci device struture
+ * @active_streams : sst active streams
+ * @sst_lock : sst device lock
+ * @stream_lock : sst stream lock
+ * @unique_id : sst unique id
+ * @stream_cnt : total sst active stream count
+ * @pb_streams : total active pb streams
+ * @cp_streams : total active cp streams
+ * @lpe_stalled : lpe stall status
+ * @pmic_port_instance : active pmic port instance
+ * @rx_time_slot_status : active rx slot
+ * @lpaudio_start : lpaudio status
+ * @audio_start : audio status
+ * @devt_d : pointer to /dev/lpe node
+ * @devt_c : pointer to /dev/lpe_ctrl node
+ * @max_streams : max streams allowed
+ */
+struct intel_sst_drv {
+ bool pmic_state;
+ int pmic_vendor;
+ int sst_state;
+ unsigned int pci_id;
+ void __iomem *shim;
+ void __iomem *mailbox;
+ void __iomem *iram;
+ void __iomem *dram;
+ unsigned int shim_phy_add;
+ struct list_head ipc_dispatch_list;
+ struct work_struct ipc_post_msg_wq;
+ struct sst_ipc_msg_wq ipc_process_msg;
+ struct sst_ipc_msg_wq ipc_process_reply;
+ struct sst_ipc_msg_wq ipc_post_msg;
+ struct mad_ops_wq mad_ops;
+ wait_queue_head_t wait_queue;
+ struct workqueue_struct *mad_wq;
+ struct workqueue_struct *post_msg_wq;
+ struct workqueue_struct *process_msg_wq;
+ struct workqueue_struct *process_reply_wq;
+
+ struct stream_info streams[MAX_NUM_STREAMS];
+ struct stream_alloc_block alloc_block[MAX_ACTIVE_STREAM];
+ struct sst_block tgt_dev_blk, fw_info_blk,
+ vol_info_blk, mute_info_blk, hs_info_blk;
+ struct mutex list_lock;/* mutex for IPC list locking */
+ spinlock_t list_spin_lock; /* mutex for IPC list locking */
+ struct snd_pmic_ops *scard_ops;
+ struct pci_dev *pci;
+ int active_streams[MAX_NUM_STREAMS];
+ void *mmap_mem;
+ struct mutex sst_lock;
+ struct mutex stream_lock;
+ unsigned int mmap_len;
+ unsigned int unique_id;
+ unsigned int stream_cnt; /* total streams */
+ unsigned int encoded_cnt; /* enocded streams only */
+ unsigned int am_cnt;
+ unsigned int pb_streams; /* pb streams active */
+ unsigned int cp_streams; /* cp streams active */
+ unsigned int lpe_stalled; /* LPE is stalled or not */
+ unsigned int pmic_port_instance; /*pmic port instance*/
+ int rx_time_slot_status;
+ unsigned int lpaudio_start;
+ /* 1 - LPA stream(MP3 pb) in progress*/
+ unsigned int audio_start;
+ dev_t devt_d, devt_c;
+ unsigned int max_streams;
+};
+
+extern struct intel_sst_drv *sst_drv_ctx;
+
+#define CHIP_REV_REG 0xff108000
+#define CHIP_REV_ADDR 0x78
+
+/* misc definitions */
+#define FW_DWNL_ID 0xFF
+#define LOOP1 0x11111111
+#define LOOP2 0x22222222
+#define LOOP3 0x33333333
+#define LOOP4 0x44444444
+
+#define SST_DEFAULT_PMIC_PORT 1 /*audio port*/
+/* NOTE: status will have +ve for good cases and -ve for error ones */
+#define MAX_STREAM_FIELD 255
+
+int sst_alloc_stream(char *params, unsigned int stream_ops, u8 codec,
+ unsigned int session_id);
+int sst_alloc_stream_response(unsigned int str_id,
+ struct snd_sst_alloc_response *response);
+int sst_stalled(void);
+int sst_pause_stream(int id);
+int sst_resume_stream(int id);
+int sst_enable_rx_timeslot(int status);
+int sst_drop_stream(int id);
+int sst_free_stream(int id);
+int sst_start_stream(int streamID);
+int sst_play_frame(int streamID);
+int sst_pcm_play_frame(int str_id, struct sst_stream_bufs *sst_buf);
+int sst_capture_frame(int streamID);
+int sst_set_stream_param(int streamID, struct snd_sst_params *str_param);
+int sst_target_device_select(struct snd_sst_target_device *target_device);
+int sst_decode(int str_id, struct snd_sst_dbufs *dbufs);
+int sst_get_decoded_bytes(int str_id, unsigned long long *bytes);
+int sst_get_fw_info(struct snd_sst_fw_info *info);
+int sst_get_stream_params(int str_id,
+ struct snd_sst_get_stream_params *get_params);
+int sst_get_stream(struct snd_sst_params *str_param);
+int sst_get_stream_allocated(struct snd_sst_params *str_param,
+ struct snd_sst_lib_download **lib_dnld);
+int sst_drain_stream(int str_id);
+int sst_get_vol(struct snd_sst_vol *set_vol);
+int sst_set_vol(struct snd_sst_vol *set_vol);
+int sst_set_mute(struct snd_sst_mute *set_mute);
+
+
+void sst_post_message(struct work_struct *work);
+void sst_process_message(struct work_struct *work);
+void sst_process_reply(struct work_struct *work);
+void sst_process_mad_ops(struct work_struct *work);
+void sst_process_mad_jack_detection(struct work_struct *work);
+
+long intel_sst_ioctl(struct file *file_ptr, unsigned int cmd,
+ unsigned long arg);
+int intel_sst_open(struct inode *i_node, struct file *file_ptr);
+int intel_sst_open_cntrl(struct inode *i_node, struct file *file_ptr);
+int intel_sst_release(struct inode *i_node, struct file *file_ptr);
+int intel_sst_release_cntrl(struct inode *i_node, struct file *file_ptr);
+int intel_sst_read(struct file *file_ptr, char __user *buf,
+ size_t count, loff_t *ppos);
+int intel_sst_write(struct file *file_ptr, const char __user *buf,
+ size_t count, loff_t *ppos);
+int intel_sst_mmap(struct file *fp, struct vm_area_struct *vma);
+ssize_t intel_sst_aio_write(struct kiocb *kiocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t offset);
+ssize_t intel_sst_aio_read(struct kiocb *kiocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t offset);
+
+int sst_load_fw(const struct firmware *fw, void *context);
+int sst_load_library(struct snd_sst_lib_download *lib, u8 ops);
+int sst_spi_mode_enable(void);
+int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx);
+
+int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block);
+int sst_wait_interruptible_timeout(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block, int timeout);
+int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx,
+ struct stream_alloc_block *block);
+int sst_create_large_msg(struct ipc_post **arg);
+int sst_create_short_msg(struct ipc_post **arg);
+void sst_wake_up_alloc_block(struct intel_sst_drv *sst_drv_ctx,
+ u8 sst_id, int status, void *data);
+void sst_clear_interrupt(void);
+int intel_sst_resume(struct pci_dev *pci);
+int sst_download_fw(void);
+void free_stream_context(unsigned int str_id);
+void sst_clean_stream(struct stream_info *stream);
+
+/*
+ * sst_fill_header - inline to fill sst header
+ *
+ * @header : ipc header
+ * @msg : IPC message to be sent
+ * @large : is ipc large msg
+ * @str_id : stream id
+ *
+ * this function is an inline function that sets the headers before
+ * sending a message
+ */
+static inline void sst_fill_header(union ipc_header *header,
+ int msg, int large, int str_id)
+{
+ header->part.msg_id = msg;
+ header->part.str_id = str_id;
+ header->part.large = large;
+ header->part.done = 0;
+ header->part.busy = 1;
+ header->part.data = 0;
+}
+
+/*
+ * sst_assign_pvt_id - assign a pvt id for stream
+ *
+ * @sst_drv_ctx : driver context
+ *
+ * this inline function assigns a private id for calls that dont have stream
+ * context yet, should be called with lock held
+ */
+static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx)
+{
+ sst_drv_ctx->unique_id++;
+ if (sst_drv_ctx->unique_id >= MAX_NUM_STREAMS)
+ sst_drv_ctx->unique_id = 1;
+ return sst_drv_ctx->unique_id;
+}
+
+/*
+ * sst_init_stream - this function initialzes stream context
+ *
+ * @stream : stream struture
+ * @codec : codec for stream
+ * @sst_id : stream id
+ * @ops : stream operation
+ * @slot : stream pcm slot
+ * @device : device type
+ *
+ * this inline function initialzes stream context for allocated stream
+ */
+static inline void sst_init_stream(struct stream_info *stream,
+ int codec, int sst_id, int ops, u8 slot,
+ enum snd_sst_audio_device_type device)
+{
+ stream->status = STREAM_INIT;
+ stream->prev = STREAM_UN_INIT;
+ stream->codec = codec;
+ stream->sst_id = sst_id;
+ stream->str_type = 0;
+ stream->ops = ops;
+ stream->data_blk.on = false;
+ stream->data_blk.condition = false;
+ stream->data_blk.ret_code = 0;
+ stream->data_blk.data = NULL;
+ stream->ctrl_blk.on = false;
+ stream->ctrl_blk.condition = false;
+ stream->ctrl_blk.ret_code = 0;
+ stream->ctrl_blk.data = NULL;
+ stream->need_draining = false;
+ stream->decode_ibuf = NULL;
+ stream->decode_isize = 0;
+ stream->mmapped = false;
+ stream->pcm_slot = slot;
+ stream->device = device;
+}
+
+
+/*
+ * sst_validate_strid - this function validates the stream id
+ *
+ * @str_id : stream id to be validated
+ *
+ * returns 0 if valid stream
+ */
+static inline int sst_validate_strid(int str_id)
+{
+ if (str_id <= 0 || str_id > sst_drv_ctx->max_streams) {
+ pr_err("SST ERR: invalid stream id : %d MAX_STREAMS:%d\n",
+ str_id, sst_drv_ctx->max_streams);
+ return -EINVAL;
+ } else
+ return 0;
+}
+
+static inline int sst_shim_write(void __iomem *addr, int offset, int value)
+{
+
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ writel(value, addr + SST_ISRD); /*dummy*/
+ writel(value, addr + offset);
+ return 0;
+}
+
+static inline int sst_shim_read(void __iomem *addr, int offset)
+{
+ return readl(addr + offset);
+}
+#endif /* __INTEL_SST_COMMON_H__ */
diff --git a/drivers/staging/intel_sst/intel_sst_drv_interface.c b/drivers/staging/intel_sst/intel_sst_drv_interface.c
new file mode 100644
index 0000000..715c2d8
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_drv_interface.c
@@ -0,0 +1,492 @@
+/*
+ * intel_sst_interface.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com)
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ * Upper layer interfaces (MAD driver, MMF) to SST driver
+ */
+
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+
+/*
+ * sst_download_fw - download the audio firmware to DSP
+ *
+ * This function is called when the FW needs to be downloaded to SST DSP engine
+ */
+int sst_download_fw(void)
+{
+ int retval;
+ const struct firmware *fw_sst;
+ const char *name;
+ if (sst_drv_ctx->sst_state != SST_UN_INIT)
+ return -EPERM;
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ name = SST_FW_FILENAME_MRST;
+ else
+ name = SST_FW_FILENAME_MFLD;
+ pr_debug("sst: Downloading %s FW now...\n", name);
+ retval = request_firmware(&fw_sst, name, &sst_drv_ctx->pci->dev);
+ if (retval) {
+ pr_err("sst: request fw failed %d\n", retval);
+ return retval;
+ }
+ sst_drv_ctx->alloc_block[0].sst_id = FW_DWNL_ID;
+ sst_drv_ctx->alloc_block[0].ops_block.condition = false;
+ retval = sst_load_fw(fw_sst, NULL);
+ if (retval)
+ goto end_restore;
+
+ retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[0]);
+ if (retval)
+ pr_err("sst: fw download failed %d\n" , retval);
+end_restore:
+ release_firmware(fw_sst);
+ sst_drv_ctx->alloc_block[0].sst_id = BLOCK_UNINIT;
+ return retval;
+}
+
+
+/*
+ * sst_stalled - this function checks if the lpe is in stalled state
+ */
+int sst_stalled(void)
+{
+ int retry = 1000;
+ int retval = -1;
+
+ while (retry) {
+ if (!sst_drv_ctx->lpe_stalled)
+ return 0;
+ /*wait for time and re-check*/
+ msleep(1);
+
+ retry--;
+ }
+ pr_debug("sst: in Stalled State\n");
+ return retval;
+}
+
+void free_stream_context(unsigned int str_id)
+{
+ struct stream_info *stream;
+
+ if (!sst_validate_strid(str_id)) {
+ /* str_id is valid, so stream is alloacted */
+ stream = &sst_drv_ctx->streams[str_id];
+ if (stream->ops == STREAM_OPS_PLAYBACK ||
+ stream->ops == STREAM_OPS_PLAYBACK_DRM) {
+ sst_drv_ctx->pb_streams--;
+ if (sst_drv_ctx->pb_streams == 0)
+ sst_drv_ctx->scard_ops->power_down_pmic_pb();
+ } else if (stream->ops == STREAM_OPS_CAPTURE) {
+ sst_drv_ctx->cp_streams--;
+ if (sst_drv_ctx->cp_streams == 0)
+ sst_drv_ctx->scard_ops->power_down_pmic_cp();
+ }
+ if (sst_drv_ctx->pb_streams == 0
+ && sst_drv_ctx->cp_streams == 0)
+ sst_drv_ctx->scard_ops->power_down_pmic();
+ if (sst_free_stream(str_id))
+ sst_clean_stream(&sst_drv_ctx->streams[str_id]);
+ }
+}
+
+/*
+ * sst_get_stream_allocated - this function gets a stream allocated with
+ * the given params
+ *
+ * @str_param : stream params
+ * @lib_dnld : pointer to pointer of lib downlaod struct
+ *
+ * This creates new stream id for a stream, in case lib is to be downloaded to
+ * DSP, it downloads that
+ */
+int sst_get_stream_allocated(struct snd_sst_params *str_param,
+ struct snd_sst_lib_download **lib_dnld)
+{
+ int retval, str_id;
+ struct stream_info *str_info;
+
+ retval = sst_alloc_stream((char *) &str_param->sparams, str_param->ops,
+ str_param->codec, str_param->device_type);
+ if (retval < 0) {
+ pr_err("sst: sst_alloc_stream failed %d\n", retval);
+ return retval;
+ }
+ pr_debug("sst: Stream allocated %d\n", retval);
+ str_id = retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+ /* Block the call for reply */
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if ((retval != 0) || (str_info->ctrl_blk.ret_code != 0)) {
+ pr_debug("sst: FW alloc failed retval %d, ret_code %d\n",
+ retval, str_info->ctrl_blk.ret_code);
+ str_id = -str_info->ctrl_blk.ret_code; /*return error*/
+ *lib_dnld = str_info->ctrl_blk.data;
+ sst_clean_stream(str_info);
+ } else
+ pr_debug("sst: FW Stream allocated sucess\n");
+ return str_id; /*will ret either error (in above if) or correct str id*/
+}
+
+/*
+ * sst_get_sfreq - this function returns the frequency of the stream
+ *
+ * @str_param : stream params
+ */
+static int sst_get_sfreq(struct snd_sst_params *str_param)
+{
+ switch (str_param->codec) {
+ case SST_CODEC_TYPE_PCM:
+ return 48000; /*str_param->sparams.uc.pcm_params.sfreq;*/
+ case SST_CODEC_TYPE_MP3:
+ return str_param->sparams.uc.mp3_params.sfreq;
+ case SST_CODEC_TYPE_AAC:
+ return str_param->sparams.uc.aac_params.sfreq;;
+ case SST_CODEC_TYPE_WMA9:
+ return str_param->sparams.uc.wma_params.sfreq;;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * sst_get_stream - this function prepares for stream allocation
+ *
+ * @str_param : stream param
+ */
+int sst_get_stream(struct snd_sst_params *str_param)
+{
+ int i, retval;
+ struct stream_info *str_info;
+ struct snd_sst_lib_download *lib_dnld;
+
+ /* stream is not allocated, we are allocating */
+ retval = sst_get_stream_allocated(str_param, &lib_dnld);
+ if (retval == -(SST_LIB_ERR_LIB_DNLD_REQUIRED)) {
+ /* codec download is required */
+ struct snd_sst_alloc_response *response;
+
+ pr_debug("sst: Codec is required.... trying that\n");
+ if (lib_dnld == NULL) {
+ pr_err("sst: lib download null!!! abort\n");
+ return -EIO;
+ }
+ i = sst_get_block_stream(sst_drv_ctx);
+ response = sst_drv_ctx->alloc_block[i].ops_block.data;
+ pr_debug("sst: alloc block allocated = %d\n", i);
+ if (i < 0) {
+ kfree(lib_dnld);
+ return -ENOMEM;
+ }
+ retval = sst_load_library(lib_dnld, str_param->ops);
+ kfree(lib_dnld);
+
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ if (!retval) {
+ pr_debug("sst: codec was downloaded sucesfully\n");
+
+ retval = sst_get_stream_allocated(str_param, &lib_dnld);
+ if (retval <= 0)
+ goto err;
+
+ pr_debug("sst: Alloc done stream id %d\n", retval);
+ } else {
+ pr_debug("sst: codec download failed\n");
+ retval = -EIO;
+ goto err;
+ }
+ } else if (retval <= 0)
+ goto err;
+ /*else
+ set_port_params(str_param, str_param->ops);*/
+
+ /* store sampling freq */
+ str_info = &sst_drv_ctx->streams[retval];
+ str_info->sfreq = sst_get_sfreq(str_param);
+
+ /* power on the analog, if reqd */
+ if (str_param->ops == STREAM_OPS_PLAYBACK ||
+ str_param->ops == STREAM_OPS_PLAYBACK_DRM) {
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ sst_drv_ctx->scard_ops->power_up_pmic_pb(
+ sst_drv_ctx->pmic_port_instance);
+ else
+ sst_drv_ctx->scard_ops->power_up_pmic_pb(
+ str_info->device);
+ /*Only if the playback is MP3 - Send a message*/
+ sst_drv_ctx->pb_streams++;
+ } else if (str_param->ops == STREAM_OPS_CAPTURE) {
+
+ sst_drv_ctx->scard_ops->power_up_pmic_cp(
+ sst_drv_ctx->pmic_port_instance);
+ /*Send a messageif not sent already*/
+ sst_drv_ctx->cp_streams++;
+ }
+
+err:
+ return retval;
+}
+
+void sst_process_mad_ops(struct work_struct *work)
+{
+
+ struct mad_ops_wq *mad_ops =
+ container_of(work, struct mad_ops_wq, wq);
+ int retval = 0;
+
+ switch (mad_ops->control_op) {
+ case SST_SND_PAUSE:
+ retval = sst_pause_stream(mad_ops->stream_id);
+ break;
+ case SST_SND_RESUME:
+ retval = sst_resume_stream(mad_ops->stream_id);
+ break;
+ case SST_SND_DROP:
+/* retval = sst_drop_stream(mad_ops->stream_id);
+*/ break;
+ case SST_SND_START:
+ pr_debug("SST Debug: start stream\n");
+ retval = sst_start_stream(mad_ops->stream_id);
+ break;
+ case SST_SND_STREAM_PROCESS:
+ pr_debug("sst: play/capt frames...\n");
+ break;
+ default:
+ pr_err("sst: wrong control_ops reported\n");
+ }
+ return;
+}
+/*
+ * sst_control_set - Set Control params
+ *
+ * @control_list: list of controls to be set
+ *
+ * This function is called by MID sound card driver to set
+ * SST/Sound card controls. This is registered with MID driver
+ */
+int sst_control_set(int control_element, void *value)
+{
+ int retval = 0, str_id = 0;
+ struct stream_info *stream;
+
+ if (sst_drv_ctx->sst_state == SST_SUSPENDED) {
+ /*LPE is suspended, resume it before proceding*/
+ pr_debug("sst: Resuming from Suspended state\n");
+ retval = intel_sst_resume(sst_drv_ctx->pci);
+ if (retval) {
+ pr_err("sst: Resume Failed = %#x, abort\n", retval);
+ return retval;
+ }
+ }
+ if (sst_drv_ctx->sst_state == SST_UN_INIT) {
+ /* FW is not downloaded */
+ pr_debug("sst: DSP Downloading FW now...\n");
+ retval = sst_download_fw();
+ if (retval) {
+ pr_err("sst: FW download fail %x, abort\n", retval);
+ return retval;
+ }
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID &&
+ sst_drv_ctx->rx_time_slot_status != RX_TIMESLOT_UNINIT
+ && sst_drv_ctx->pmic_vendor != SND_NC)
+ sst_enable_rx_timeslot(
+ sst_drv_ctx->rx_time_slot_status);
+ }
+
+ switch (control_element) {
+ case SST_SND_ALLOC: {
+ struct snd_sst_params *str_param;
+ struct stream_info *str_info;
+
+ str_param = (struct snd_sst_params *)value;
+ BUG_ON(!str_param);
+ retval = sst_get_stream(str_param);
+ if (retval >= 0)
+ sst_drv_ctx->stream_cnt++;
+ str_info = &sst_drv_ctx->streams[retval];
+ str_info->src = MAD_DRV;
+ break;
+ }
+
+ case SST_SND_PAUSE:
+ case SST_SND_RESUME:
+ case SST_SND_DROP:
+ case SST_SND_START:
+ sst_drv_ctx->mad_ops.control_op = control_element;
+ sst_drv_ctx->mad_ops.stream_id = *(int *)value;
+ queue_work(sst_drv_ctx->mad_wq, &sst_drv_ctx->mad_ops.wq);
+ break;
+
+ case SST_SND_FREE:
+ str_id = *(int *)value;
+ stream = &sst_drv_ctx->streams[str_id];
+ free_stream_context(str_id);
+ stream->pcm_substream = NULL;
+ stream->status = STREAM_UN_INIT;
+ stream->period_elapsed = NULL;
+ sst_drv_ctx->stream_cnt--;
+ break;
+
+ case SST_SND_STREAM_INIT: {
+ struct pcm_stream_info *str_info;
+ struct stream_info *stream;
+
+ pr_debug("sst: stream init called\n");
+ str_info = (struct pcm_stream_info *)value;
+ str_id = str_info->str_id;
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ break;
+
+ stream = &sst_drv_ctx->streams[str_id];
+ pr_debug("sst: setting the period ptrs\n");
+ stream->pcm_substream = str_info->mad_substream;
+ stream->period_elapsed = str_info->period_elapsed;
+ stream->sfreq = str_info->sfreq;
+ stream->prev = stream->status;
+ stream->status = STREAM_INIT;
+ break;
+ }
+
+ case SST_SND_BUFFER_POINTER: {
+ struct pcm_stream_info *stream_info;
+ struct snd_sst_tstamp fw_tstamp = {0,};
+ struct stream_info *stream;
+
+
+ stream_info = (struct pcm_stream_info *)value;
+ str_id = stream_info->str_id;
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ break;
+ stream = &sst_drv_ctx->streams[str_id];
+
+ if (!stream->pcm_substream)
+ break;
+ memcpy_fromio(&fw_tstamp,
+ ((void *)(sst_drv_ctx->mailbox + SST_TIME_STAMP)
+ +(str_id * sizeof(fw_tstamp))),
+ sizeof(fw_tstamp));
+
+ pr_debug("sst: Pointer Query on strid = %d ops %d\n",
+ str_id, stream->ops);
+
+ if (stream->ops == STREAM_OPS_PLAYBACK)
+ stream_info->buffer_ptr = fw_tstamp.samples_rendered;
+ else
+ stream_info->buffer_ptr = fw_tstamp.samples_processed;
+ pr_debug("sst: Samples rendered = %llu, buffer ptr %llu\n",
+ fw_tstamp.samples_rendered, stream_info->buffer_ptr);
+ break;
+ }
+ case SST_ENABLE_RX_TIME_SLOT: {
+ int status = *(int *)value;
+ sst_drv_ctx->rx_time_slot_status = status ;
+ sst_enable_rx_timeslot(status);
+ break;
+ }
+ default:
+ /* Illegal case */
+ pr_warn("sst: illegal req\n");
+ return -EINVAL;
+ }
+
+ return retval;
+}
+
+
+struct intel_sst_card_ops sst_pmic_ops = {
+ .control_set = sst_control_set,
+};
+
+/*
+ * register_sst_card - function for sound card to register
+ *
+ * @card: pointer to structure of operations
+ *
+ * This function is called card driver loads and is ready for registration
+ */
+int register_sst_card(struct intel_sst_card_ops *card)
+{
+ if (!sst_drv_ctx) {
+ pr_err("sst: No SST driver register card reject\n");
+ return -ENODEV;
+ }
+
+ if (!card || !card->module_name) {
+ pr_err("sst: Null Pointer Passed\n");
+ return -EINVAL;
+ }
+ if (sst_drv_ctx->pmic_state == SND_MAD_UN_INIT) {
+ /* register this driver */
+ if ((strncmp(SST_CARD_NAMES, card->module_name,
+ strlen(SST_CARD_NAMES))) == 0) {
+ sst_drv_ctx->pmic_vendor = card->vendor_id;
+ sst_drv_ctx->scard_ops = card->scard_ops;
+ sst_pmic_ops.module_name = card->module_name;
+ sst_drv_ctx->pmic_state = SND_MAD_INIT_DONE;
+ sst_drv_ctx->rx_time_slot_status = 0; /*default AMIC*/
+ card->control_set = sst_pmic_ops.control_set;
+ sst_drv_ctx->scard_ops->card_status = SND_CARD_UN_INIT;
+ return 0;
+ } else {
+ pr_err("sst: strcmp fail %s\n", card->module_name);
+ return -EINVAL;
+ }
+
+ } else {
+ /* already registered a driver */
+ pr_err("sst: Repeat for registeration..denied\n");
+ return -EBADRQC;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(register_sst_card);
+
+/*
+ * unregister_sst_card- function for sound card to un-register
+ *
+ * @card: pointer to structure of operations
+ *
+ * This function is called when card driver unloads
+ */
+void unregister_sst_card(struct intel_sst_card_ops *card)
+{
+ if (sst_pmic_ops.control_set == card->control_set) {
+ /* unreg */
+ sst_pmic_ops.module_name = "";
+ sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
+ pr_debug("sst: Unregistered %s\n", card->module_name);
+ }
+ return;
+}
+EXPORT_SYMBOL_GPL(unregister_sst_card);
diff --git a/drivers/staging/intel_sst/intel_sst_dsp.c b/drivers/staging/intel_sst/intel_sst_dsp.c
new file mode 100644
index 0000000..d80a6ee
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_dsp.c
@@ -0,0 +1,486 @@
+/*
+ * intel_sst_dsp.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ *
+ * This file contains all dsp controlling functions like firmware download,
+ * setting/resetting dsp cores, etc
+ */
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+
+/**
+ * intel_sst_reset_dsp_mrst - Resetting SST DSP
+ *
+ * This resets DSP in case of MRST platfroms
+ */
+static int intel_sst_reset_dsp_mrst(void)
+{
+ union config_status_reg csr;
+
+ pr_debug("sst: Resetting the DSP in mrst\n");
+ csr.full = 0x3a2;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.part.strb_cntr_rst = 0;
+ csr.part.run_stall = 0x1;
+ csr.part.bypass = 0x7;
+ csr.part.sst_reset = 0x1;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ return 0;
+}
+
+/**
+ * intel_sst_reset_dsp_medfield - Resetting SST DSP
+ *
+ * This resets DSP in case of Medfield platfroms
+ */
+static int intel_sst_reset_dsp_medfield(void)
+{
+ union config_status_reg csr;
+
+ pr_debug("sst: Resetting the DSP in medfield\n");
+ csr.full = 0x048303E2;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ return 0;
+}
+
+/**
+ * sst_start_mrst - Start the SST DSP processor
+ *
+ * This starts the DSP in MRST platfroms
+ */
+static int sst_start_mrst(void)
+{
+ union config_status_reg csr;
+
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.part.bypass = 0;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ csr.part.run_stall = 0;
+ csr.part.sst_reset = 0;
+ csr.part.strb_cntr_rst = 1;
+ pr_debug("sst: Setting SST to execute_mrst 0x%x\n", csr.full);
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ return 0;
+}
+
+/**
+ * sst_start_medfield - Start the SST DSP processor
+ *
+ * This starts the DSP in MRST platfroms
+ */
+static int sst_start_medfield(void)
+{
+ union config_status_reg csr;
+
+ csr.full = 0x04830062;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ csr.full = 0x04830063;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ csr.full = 0x04830061;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+ pr_debug("sst: Starting the DSP_medfld\n");
+
+ return 0;
+}
+
+/**
+ * sst_parse_module - Parse audio FW modules
+ *
+ * @module: FW module header
+ *
+ * Parses modules that need to be placed in SST IRAM and DRAM
+ * returns error or 0 if module sizes are proper
+ */
+static int sst_parse_module(struct fw_module_header *module)
+{
+ struct dma_block_info *block;
+ u32 count;
+ void __iomem *ram;
+
+ pr_debug("sst: module sign %s size %x blocks %x type %x\n",
+ module->signature, module->mod_size,
+ module->blocks, module->type);
+ pr_debug("sst: module entrypoint 0x%x\n", module->entry_point);
+
+ block = (void *)module + sizeof(*module);
+
+ for (count = 0; count < module->blocks; count++) {
+ if (block->size <= 0) {
+ pr_err("sst: block size invalid\n");
+ return -EINVAL;
+ }
+ switch (block->type) {
+ case SST_IRAM:
+ ram = sst_drv_ctx->iram;
+ break;
+ case SST_DRAM:
+ ram = sst_drv_ctx->dram;
+ break;
+ default:
+ pr_err("sst: wrong ram type0x%x in block0x%x\n",
+ block->type, count);
+ return -EINVAL;
+ }
+ memcpy_toio(ram + block->ram_offset,
+ (void *)block + sizeof(*block), block->size);
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ return 0;
+}
+
+/**
+ * sst_parse_fw_image - parse and load FW
+ *
+ * @sst_fw: pointer to audio fw
+ *
+ * This function is called to parse and download the FW image
+ */
+static int sst_parse_fw_image(const struct firmware *sst_fw)
+{
+ struct fw_header *header;
+ u32 count;
+ int ret_val;
+ struct fw_module_header *module;
+
+ BUG_ON(!sst_fw);
+
+ /* Read the header information from the data pointer */
+ header = (struct fw_header *)sst_fw->data;
+
+ /* verify FW */
+ if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) ||
+ (sst_fw->size != header->file_size + sizeof(*header))) {
+ /* Invalid FW signature */
+ pr_err("sst: InvalidFW sign/filesize mismatch\n");
+ return -EINVAL;
+ }
+ pr_debug("sst: header sign=%s size=%x modules=%x fmt=%x size=%x\n",
+ header->signature, header->file_size, header->modules,
+ header->file_format, sizeof(*header));
+ module = (void *)sst_fw->data + sizeof(*header);
+ for (count = 0; count < header->modules; count++) {
+ /* module */
+ ret_val = sst_parse_module(module);
+ if (ret_val)
+ return ret_val;
+ module = (void *)module + sizeof(*module) + module->mod_size ;
+ }
+
+ return 0;
+}
+
+/**
+ * sst_load_fw - function to load FW into DSP
+ *
+ * @fw: Pointer to driver loaded FW
+ * @context: driver context
+ *
+ * This function is called by OS when the FW is loaded into kernel
+ */
+int sst_load_fw(const struct firmware *fw, void *context)
+{
+ int ret_val;
+
+ pr_debug("sst: load_fw called\n");
+ BUG_ON(!fw);
+
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ ret_val = intel_sst_reset_dsp_mrst();
+ else if (sst_drv_ctx->pci_id == SST_MFLD_PCI_ID)
+ ret_val = intel_sst_reset_dsp_medfield();
+ if (ret_val)
+ return ret_val;
+
+ ret_val = sst_parse_fw_image(fw);
+ if (ret_val)
+ return ret_val;
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_FW_LOADED;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ /* 7. ask scu to reset the bypass bits */
+ /* 8.bring sst out of reset */
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ ret_val = sst_start_mrst();
+ else if (sst_drv_ctx->pci_id == SST_MFLD_PCI_ID)
+ ret_val = sst_start_medfield();
+ if (ret_val)
+ return ret_val;
+
+ pr_debug("sst: fw loaded successful!!!\n");
+ return ret_val;
+}
+
+/*This function is called when any codec/post processing library
+ needs to be downloaded*/
+static int sst_download_library(const struct firmware *fw_lib,
+ struct snd_sst_lib_download_info *lib)
+{
+ /* send IPC message and wait */
+ int i;
+ u8 pvt_id;
+ struct ipc_post *msg = NULL;
+ union config_status_reg csr;
+ struct snd_sst_str_type str_type = {0};
+ int retval = 0;
+
+ if (sst_create_large_msg(&msg))
+ return -ENOMEM;
+
+ pvt_id = sst_assign_pvt_id(sst_drv_ctx);
+ i = sst_get_block_stream(sst_drv_ctx);
+ pr_debug("sst: alloc block allocated = %d, pvt_id %d\n", i, pvt_id);
+ if (i < 0) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+ sst_drv_ctx->alloc_block[i].sst_id = pvt_id;
+ sst_fill_header(&msg->header, IPC_IA_PREP_LIB_DNLD, 1, pvt_id);
+ msg->header.part.data = sizeof(u32) + sizeof(str_type);
+ str_type.codec_type = lib->dload_lib.lib_info.lib_type;
+ /*str_type.pvt_id = pvt_id;*/
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), &str_type, sizeof(str_type));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[i]);
+ if (retval) {
+ /* error */
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ pr_err("sst: Prep codec downloaded failed %d\n",
+ retval);
+ return -EIO;
+ }
+ pr_debug("sst: FW responded, ready for download now...\n");
+ /* downloading on success */
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_FW_LOADED;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ csr.full = readl(sst_drv_ctx->shim + SST_CSR);
+ csr.part.run_stall = 1;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.part.bypass = 0x7;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ sst_parse_fw_image(fw_lib);
+
+ /* set the FW to running again */
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.part.bypass = 0x0;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
+ csr.part.run_stall = 0;
+ sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
+
+ /* send download complete and wait */
+ if (sst_create_large_msg(&msg)) {
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ return -ENOMEM;
+ }
+
+ sst_fill_header(&msg->header, IPC_IA_LIB_DNLD_CMPLT, 1, pvt_id);
+ sst_drv_ctx->alloc_block[i].sst_id = pvt_id;
+ msg->header.part.data = sizeof(u32) + sizeof(*lib);
+ lib->pvt_id = pvt_id;
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), lib, sizeof(*lib));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ pr_debug("sst: Waiting for FW response Download complete\n");
+ sst_drv_ctx->alloc_block[i].ops_block.condition = false;
+ retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[i]);
+ if (retval) {
+ /* error */
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_UN_INIT;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ return -EIO;
+ }
+
+ pr_debug("sst: FW sucess on Download complete\n");
+ sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_FW_RUNNING;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ return 0;
+
+}
+
+/* This function is called befoer downloading the codec/postprocessing
+library is set for download to SST DSP*/
+static int sst_validate_library(const struct firmware *fw_lib,
+ struct lib_slot_info *slot,
+ u32 *entry_point)
+{
+ struct fw_header *header;
+ struct fw_module_header *module;
+ struct dma_block_info *block;
+ unsigned int n_blk, isize = 0, dsize = 0;
+ int err = 0;
+
+ header = (struct fw_header *)fw_lib->data;
+ if (header->modules != 1) {
+ pr_err("sst: Module no mismatch found\n ");
+ err = -EINVAL;
+ goto exit;
+ }
+ module = (void *)fw_lib->data + sizeof(*header);
+ *entry_point = module->entry_point;
+ pr_debug("sst: Module entry point 0x%x\n", *entry_point);
+ pr_debug("sst: Module Sign %s, Size 0x%x, Blocks 0x%x Type 0x%x\n",
+ module->signature, module->mod_size,
+ module->blocks, module->type);
+
+ block = (void *)module + sizeof(*module);
+ for (n_blk = 0; n_blk < module->blocks; n_blk++) {
+ switch (block->type) {
+ case SST_IRAM:
+ isize += block->size;
+ break;
+ case SST_DRAM:
+ dsize += block->size;
+ break;
+ default:
+ pr_err("sst: Invalid block type for 0x%x\n", n_blk);
+ err = -EINVAL;
+ goto exit;
+ }
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ if (isize > slot->iram_size || dsize > slot->dram_size) {
+ pr_err("sst: library exceeds size allocated\n");
+ err = -EINVAL;
+ goto exit;
+ } else
+ pr_debug("sst: Library is safe for download...\n");
+
+ pr_debug("sst: iram 0x%x, dram 0x%x, iram 0x%x, dram 0x%x\n",
+ isize, dsize, slot->iram_size, slot->dram_size);
+exit:
+ return err;
+
+}
+
+/* This function is called when FW requests for a particular libary download
+This function prepares the library to download*/
+int sst_load_library(struct snd_sst_lib_download *lib, u8 ops)
+{
+ char buf[20];
+ const char *type, *dir;
+ int len = 0, error = 0;
+ u32 entry_point;
+ const struct firmware *fw_lib;
+ struct snd_sst_lib_download_info dload_info = {{{0},},};
+
+ memset(buf, 0, sizeof(buf));
+
+ pr_debug("sst: Lib Type 0x%x, Slot 0x%x, ops 0x%x\n",
+ lib->lib_info.lib_type, lib->slot_info.slot_num, ops);
+ pr_debug("sst: Version 0x%x, name %s, caps 0x%x media type 0x%x\n",
+ lib->lib_info.lib_version, lib->lib_info.lib_name,
+ lib->lib_info.lib_caps, lib->lib_info.media_type);
+
+ pr_debug("sst: IRAM Size 0x%x, offset 0x%x\n",
+ lib->slot_info.iram_size, lib->slot_info.iram_offset);
+ pr_debug("sst: DRAM Size 0x%x, offset 0x%x\n",
+ lib->slot_info.dram_size, lib->slot_info.dram_offset);
+
+ switch (lib->lib_info.lib_type) {
+ case SST_CODEC_TYPE_MP3:
+ type = "mp3_";
+ break;
+ case SST_CODEC_TYPE_AAC:
+ type = "aac_";
+ break;
+ case SST_CODEC_TYPE_AACP:
+ type = "aac_v1_";
+ break;
+ case SST_CODEC_TYPE_eAACP:
+ type = "aac_v2_";
+ break;
+ case SST_CODEC_TYPE_WMA9:
+ type = "wma9_";
+ break;
+ default:
+ pr_err("sst: Invalid codec type\n");
+ error = -EINVAL;
+ goto wake;
+ }
+
+ if (ops == STREAM_OPS_CAPTURE)
+ dir = "enc_";
+ else
+ dir = "dec_";
+ len = strlen(type) + strlen(dir);
+ strncpy(buf, type, sizeof(buf)-1);
+ strncpy(buf + strlen(type), dir, sizeof(buf)-strlen(type)-1);
+ len += snprintf(buf + len, sizeof(buf) - len, "%d",
+ lib->slot_info.slot_num);
+ len += snprintf(buf + len, sizeof(buf) - len, ".bin");
+
+ pr_debug("sst: Requesting %s\n", buf);
+
+ error = request_firmware(&fw_lib, buf, &sst_drv_ctx->pci->dev);
+ if (error) {
+ pr_err("sst: library load failed %d\n", error);
+ goto wake;
+ }
+ error = sst_validate_library(fw_lib, &lib->slot_info, &entry_point);
+ if (error)
+ goto wake_free;
+
+ lib->mod_entry_pt = entry_point;
+ memcpy(&dload_info.dload_lib, lib, sizeof(*lib));
+ error = sst_download_library(fw_lib, &dload_info);
+ if (error)
+ goto wake_free;
+
+ /* lib is downloaded and init send alloc again */
+ pr_debug("sst: Library is downloaded now...\n");
+wake_free:
+ /* sst_wake_up_alloc_block(sst_drv_ctx, pvt_id, error, NULL); */
+ release_firmware(fw_lib);
+wake:
+ return error;
+}
+
diff --git a/drivers/staging/intel_sst/intel_sst_fw_ipc.h b/drivers/staging/intel_sst/intel_sst_fw_ipc.h
new file mode 100644
index 0000000..1a2f67f
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_fw_ipc.h
@@ -0,0 +1,393 @@
+#ifndef __INTEL_SST_FW_IPC_H__
+#define __INTEL_SST_FW_IPC_H__
+/*
+* intel_sst_fw_ipc.h - Intel SST Driver for audio engine
+*
+* Copyright (C) 2008-10 Intel Corporation
+* Author: Vinod Koul <vinod.koul(a)intel.com>
+* Harsha Priya <priya.harsha(a)intel.com>
+* Dharageswari R <dharageswari.r(a)intel.com>
+* KP Jeeja <jeeja.kp(a)intel.com>
+* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; version 2 of the License.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with this program; if not, write to the Free Software Foundation, Inc.,
+* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+*
+* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+*
+* This driver exposes the audio engine functionalities to the ALSA
+* and middleware.
+* This file has definitions shared between the firmware and driver
+*/
+
+#define MAX_NUM_STREAMS_MRST 3
+#define MAX_NUM_STREAMS_MFLD 6
+#define MAX_NUM_STREAMS 6
+#define MAX_DBG_RW_BYTES 80
+#define MAX_NUM_SCATTER_BUFFERS 8
+#define MAX_LOOP_BACK_DWORDS 8
+/* IPC base address and mailbox, timestamp offsets */
+#define SST_MAILBOX_SIZE 0x0400
+#define SST_MAILBOX_SEND 0x0000
+#define SST_MAILBOX_RCV 0x0804
+#define SST_TIME_STAMP 0x1800
+#define SST_RESERVED_OFFSET 0x1A00
+#define SST_CHEKPOINT_OFFSET 0x1C00
+#define REPLY_MSG 0x80
+
+/* Message ID's for IPC messages */
+/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */
+
+/* I2L Firmware/Codec Download msgs */
+#define IPC_IA_PREP_LIB_DNLD 0x01
+#define IPC_IA_LIB_DNLD_CMPLT 0x02
+
+#define IPC_IA_SET_PMIC_TYPE 0x03
+#define IPC_IA_GET_FW_VERSION 0x04
+#define IPC_IA_GET_FW_BUILD_INF 0x05
+#define IPC_IA_GET_FW_INFO 0x06
+
+/* I2L Codec Config/control msgs */
+#define IPC_IA_SET_CODEC_PARAMS 0x10
+#define IPC_IA_GET_CODEC_PARAMS 0x11
+#define IPC_IA_SET_PPP_PARAMS 0x12
+#define IPC_IA_GET_PPP_PARAMS 0x13
+#define IPC_IA_PLAY_FRAMES 0x14
+#define IPC_IA_CAPT_FRAMES 0x15
+#define IPC_IA_PLAY_VOICE 0x16
+#define IPC_IA_CAPT_VOICE 0x17
+#define IPC_IA_DECODE_FRAMES 0x18
+
+/* I2L Stream config/control msgs */
+#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */
+#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */
+#define IPC_IA_SET_STREAM_PARAMS 0x22
+#define IPC_IA_GET_STREAM_PARAMS 0x23
+#define IPC_IA_PAUSE_STREAM 0x24
+#define IPC_IA_RESUME_STREAM 0x25
+#define IPC_IA_DROP_STREAM 0x26
+#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */
+#define IPC_IA_TARGET_DEV_SELECT 0x28
+#define IPC_IA_CONTROL_ROUTING 0x29
+
+#define IPC_IA_SET_STREAM_VOL 0x2A /*Vol for stream, pre mixer */
+#define IPC_IA_GET_STREAM_VOL 0x2B
+#define IPC_IA_SET_STREAM_MUTE 0x2C
+#define IPC_IA_GET_STREAM_MUTE 0x2D
+#define IPC_IA_ENABLE_RX_TIME_SLOT 0x2E /* Enable Rx time slot 0 or 1 */
+
+#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */
+
+/* Debug msgs */
+#define IPC_IA_DBG_MEM_READ 0x40
+#define IPC_IA_DBG_MEM_WRITE 0x41
+#define IPC_IA_DBG_LOOP_BACK 0x42
+
+/* L2I Firmware/Codec Download msgs */
+#define IPC_IA_FW_INIT_CMPLT 0x81
+#define IPC_IA_LPE_GETTING_STALLED 0x82
+#define IPC_IA_LPE_UNSTALLED 0x83
+
+/* L2I Codec Config/control msgs */
+#define IPC_SST_GET_PLAY_FRAMES 0x90 /* Request IA more data */
+#define IPC_SST_GET_CAPT_FRAMES 0x91 /* Request IA more data */
+#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */
+#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */
+#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */
+#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */
+#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */
+#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */
+#define IPC_IA_TARGET_DEV_CHNGD 0x98 /* error in processing a stream */
+
+#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occured */
+/* L2S messages */
+#define IPC_SC_DDR_LINK_UP 0xC0
+#define IPC_SC_DDR_LINK_DOWN 0xC1
+#define IPC_SC_SET_LPECLK_REQ 0xC2
+#define IPC_SC_SSP_BIT_BANG 0xC3
+
+/* L2I Error reporting msgs */
+#define IPC_IA_MEM_ALLOC_FAIL 0xE0
+#define IPC_IA_PROC_ERR 0xE1 /* error in processing a
+ stream can be used by playback and
+ capture modules */
+
+/* L2I Debug msgs */
+#define IPC_IA_PRINT_STRING 0xF0
+
+
+
+/* Command Response or Acknowledge message to any IPC message will have
+ * same message ID and stream ID information which is sent.
+ * There is no specific Ack message ID. The data field is used as response
+ * meaning.
+ */
+enum ackData {
+ IPC_ACK_SUCCESS = 0,
+ IPC_ACK_FAILURE
+};
+
+
+enum sst_error_codes {
+ /* Error code,response to msgId: Description */
+ /* Common error codes */
+ SST_SUCCESS = 0, /* Success */
+ SST_ERR_INVALID_STREAM_ID, /* Invalid stream ID */
+ SST_ERR_INVALID_MSG_ID, /* Invalid message ID */
+ SST_ERR_INVALID_STREAM_OP, /* Invalid stream operation request */
+ SST_ERR_INVALID_PARAMS, /* Invalid params */
+ SST_ERR_INVALID_CODEC, /* Invalid codec type */
+ SST_ERR_INVALID_MEDIA_TYPE, /* Invalid media type */
+ SST_ERR_STREAM_ERR, /* ANY: Stream control or config or
+ processing error */
+
+ /* IPC specific error codes */
+ SST_IPC_ERR_CALL_BACK_NOT_REGD, /* Call back for msg not regd */
+ SST_IPC_ERR_STREAM_NOT_ALLOCATED, /* Stream is not allocated */
+ SST_IPC_ERR_STREAM_ALLOC_FAILED, /* ALLOC:Stream alloc failed */
+ SST_IPC_ERR_GET_STREAM_FAILED, /* ALLOC:Get stream id failed*/
+ SST_ERR_MOD_NOT_AVAIL, /* SET/GET: Mod(AEC/AGC/ALC) not available */
+ SST_ERR_MOD_DNLD_RQD, /* SET/GET: Mod(AEC/AGC/ALC) download required */
+ SST_ERR_STREAM_STOPPED, /* ANY: Stream is in stopped state */
+ SST_ERR_STREAM_IN_USE, /* ANY: Stream is already in use */
+
+ /* Capture specific error codes */
+ SST_CAP_ERR_INCMPLTE_CAPTURE_MSG,/* ANY:Incomplete message */
+ SST_CAP_ERR_CAPTURE_FAIL, /* ANY:Capture op failed */
+ SST_CAP_ERR_GET_DDR_NEW_SGLIST,
+ SST_CAP_ERR_UNDER_RUN, /* lack of input data */
+ SST_CAP_ERR_OVERFLOW, /* lack of output space */
+
+ /* Playback specific error codes*/
+ SST_PB_ERR_INCMPLTE_PLAY_MSG, /* ANY: Incomplete message */
+ SST_PB_ERR_PLAY_FAIL, /* ANY: Playback operation failed */
+ SST_PB_ERR_GET_DDR_NEW_SGLIST,
+
+ /* Codec manager specific error codes */
+ SST_LIB_ERR_LIB_DNLD_REQUIRED, /* ALLOC: Codec download required */
+ SST_LIB_ERR_LIB_NOT_SUPPORTED, /* Library is not supported */
+
+ /* Library manager specific error codes */
+ SST_SCC_ERR_PREP_DNLD_FAILED, /* Failed to prepare for codec download */
+ SST_SCC_ERR_LIB_DNLD_RES_FAILED, /* Lib download resume failed */
+ /* Scheduler specific error codes */
+ SST_SCH_ERR_FAIL, /* REPORT: */
+
+ /* DMA specific error codes */
+ SST_DMA_ERR_NO_CHNL_AVAILABLE, /* DMA Ch not available */
+ SST_DMA_ERR_INVALID_INPUT_PARAMS, /* Invalid input params */
+ SST_DMA_ERR_CHNL_ALREADY_SUSPENDED, /* Ch is suspended */
+ SST_DMA_ERR_CHNL_ALREADY_STARTED, /* Ch already started */
+ SST_DMA_ERR_CHNL_NOT_ENABLED, /* Ch not enabled */
+ SST_DMA_ERR_TRANSFER_FAILED, /* Transfer failed */
+ SST_SSP_ERR_ALREADY_ENABLED, /* REPORT: SSP already enabled */
+ SST_SSP_ERR_ALREADY_DISABLED, /* REPORT: SSP already disabled */
+ SST_SSP_ERR_NOT_INITIALIZED,
+
+ /* Other error codes */
+ SST_ERR_MOD_INIT_FAIL, /* Firmware Module init failed */
+
+ /* FW init error codes */
+ SST_RDR_ERR_IO_DEV_SEL_NOT_ALLOWED,
+ SST_RDR_ERR_ROUTE_ALREADY_STARTED,
+ SST_RDR_PREP_CODEC_DNLD_FAILED,
+
+ /* Memory debug error codes */
+ SST_ERR_DBG_MEM_READ_FAIL,
+ SST_ERR_DBG_MEM_WRITE_FAIL,
+
+ /* Decode error codes */
+ SST_ERR_DEC_NEED_INPUT_BUF,
+
+};
+
+enum dbg_mem_data_type {
+ /* Data type of debug read/write */
+ DATA_TYPE_U32,
+ DATA_TYPE_U16,
+ DATA_TYPE_U8,
+};
+
+/* CAUTION NOTE: All IPC message body must be multiple of 32 bits.*/
+
+/* IPC Header */
+union ipc_header {
+ struct {
+ u32 msg_id:8; /* Message ID - Max 256 Message Types */
+ u32 str_id:5;
+ u32 large:1; /* Large Message if large = 1 */
+ u32 reserved:2; /* Reserved for future use */
+ u32 data:14; /* Ack/Info for msg, size of msg in Mailbox */
+ u32 done:1; /* bit 30 */
+ u32 busy:1; /* bit 31 */
+ } part;
+ u32 full;
+} __attribute__ ((packed));
+
+/* Firmware build info */
+struct sst_fw_build_info {
+ unsigned char date[16]; /* Firmware build date */
+ unsigned char time[16]; /* Firmware build time */
+} __attribute__ ((packed));
+
+struct ipc_header_fw_init {
+ struct snd_sst_fw_version fw_version;/* Firmware version details */
+ struct sst_fw_build_info build_info;
+ u16 result; /* Fw init result */
+ u8 module_id; /* Module ID in case of error */
+ u8 debug_info; /* Debug info from Module ID in case of fail */
+} __attribute__ ((packed));
+
+/* Address and size info of a frame buffer in DDR */
+struct sst_address_info {
+ u32 addr; /* Address at IA */
+ u32 size; /* Size of the buffer */
+} __attribute__ ((packed));
+
+/* Time stamp */
+struct snd_sst_tstamp {
+ u64 samples_processed;/* capture - data in DDR */
+ u64 samples_rendered;/* playback - data rendered */
+ u64 bytes_processed;/* bytes decoded or encoded */
+ u32 sampling_frequency;/* eg: 48000, 44100 */
+ u32 dma_base_address;/* DMA base address */
+ u16 dma_channel_no;/* DMA Channel used for the data transfer*/
+ u16 reserved;/* 32 bit alignment */
+};
+
+/* Frame info to play or capture */
+struct sst_frame_info {
+ u16 num_entries; /* number of entries to follow */
+ u16 rsrvd;
+ struct sst_address_info addr[MAX_NUM_SCATTER_BUFFERS];
+} __attribute__ ((packed));
+
+/* Frames info for decode */
+struct snd_sst_decode_info {
+ unsigned long long input_bytes_consumed;
+ unsigned long long output_bytes_produced;
+ struct sst_frame_info frames_in;
+ struct sst_frame_info frames_out;
+} __attribute__ ((packed));
+
+/* SST to IA print debug message*/
+struct ipc_sst_ia_print_params {
+ u32 string_size;/* Max value is 160 */
+ u8 prt_string[160];/* Null terminated Char string */
+} __attribute__ ((packed));
+
+/* Voice data message */
+struct snd_sst_voice_data {
+ u16 num_bytes;/* Number of valid voice data bytes */
+ u8 pcm_wd_size;/* 0=8 bit, 1=16 bit 2=32 bit */
+ u8 reserved;/* Reserved */
+ u8 voice_data_buf[0];/* Voice data buffer in bytes, little endian */
+} __attribute__ ((packed));
+
+/* SST to IA memory read debug message */
+struct ipc_sst_ia_dbg_mem_rw {
+ u16 num_bytes;/* Maximum of MAX_DBG_RW_BYTES */
+ u16 data_type;/* enum: dbg_mem_data_type */
+ u32 address; /* Memory address of data memory of data_type */
+ u8 rw_bytes[MAX_DBG_RW_BYTES];/* Maximum of 64 bytes can be RW */
+} __attribute__ ((packed));
+
+struct ipc_sst_ia_dbg_loop_back {
+ u16 num_dwords; /* Maximum of MAX_DBG_RW_BYTES */
+ u16 increment_val;/* Increments dwords by this value, 0- no increment */
+ u32 lpbk_dwords[MAX_LOOP_BACK_DWORDS];/* Maximum of 8 dwords loopback */
+} __attribute__ ((packed));
+
+/* Stream type params struture for Alloc stream */
+struct snd_sst_str_type {
+ u8 codec_type; /* Codec type */
+ u8 str_type; /* 1 = voice 2 = music */
+ u8 operation; /* Playback or Capture */
+ u8 protected_str; /* 0=Non DRM, 1=DRM */
+ u8 time_slots;
+ u8 reserved; /* Reserved */
+ u16 result; /* Result used for acknowledgment */
+} __attribute__ ((packed));
+
+/* Library info structure */
+struct module_info {
+ u32 lib_version;
+ u32 lib_type;/*TBD- KLOCKWORK u8 lib_type;*/
+ u32 media_type;
+ u8 lib_name[12];
+ u32 lib_caps;
+ unsigned char b_date[16]; /* Lib build date */
+ unsigned char b_time[16]; /* Lib build time */
+} __attribute__ ((packed));
+
+/* Library slot info */
+struct lib_slot_info {
+ u8 slot_num; /* 1 or 2 */
+ u8 reserved1;
+ u16 reserved2;
+ u32 iram_size; /* slot size in IRAM */
+ u32 dram_size; /* slot size in DRAM */
+ u32 iram_offset; /* starting offset of slot in IRAM */
+ u32 dram_offset; /* starting offset of slot in DRAM */
+} __attribute__ ((packed));
+
+struct snd_sst_lib_download {
+ struct module_info lib_info; /* library info type, capabilities etc */
+ struct lib_slot_info slot_info; /* slot info to be downloaded */
+ u32 mod_entry_pt;
+};
+
+struct snd_sst_lib_download_info {
+ struct snd_sst_lib_download dload_lib;
+ u16 result; /* Result used for acknowledgment */
+ u8 pvt_id; /* Private ID */
+ u8 reserved; /* for alignment */
+};
+
+/* Alloc stream params structure */
+struct snd_sst_alloc_params {
+ struct snd_sst_str_type str_type;
+ struct snd_sst_stream_params stream_params;
+};
+
+struct snd_sst_fw_get_stream_params {
+ struct snd_sst_stream_params codec_params;
+ struct snd_sst_pmic_config pcm_params;
+};
+
+/* Alloc stream response message */
+struct snd_sst_alloc_response {
+ struct snd_sst_str_type str_type; /* Stream type for allocation */
+ struct snd_sst_lib_download lib_dnld; /* Valid only for codec dnld */
+};
+
+/* Drop response */
+struct snd_sst_drop_response {
+ u32 result;
+ u32 bytes;
+};
+
+/* CSV Voice call routing structure */
+struct snd_sst_control_routing {
+ u8 control; /* 0=start, 1=Stop */
+ u8 reserved[3]; /* Reserved- for 32 bit alignment */
+};
+
+
+struct ipc_post {
+ struct list_head node;
+ union ipc_header header; /* driver specific */
+ char *mailbox_data;
+};
+
+#endif /* __INTEL_SST_FW_IPC_H__ */
diff --git a/drivers/staging/intel_sst/intel_sst_ioctl.h b/drivers/staging/intel_sst/intel_sst_ioctl.h
new file mode 100644
index 0000000..03b9316
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_ioctl.h
@@ -0,0 +1,435 @@
+#ifndef __INTEL_SST_IOCTL_H__
+#define __INTEL_SST_IOCTL_H__
+/*
+ * intel_sst_ioctl.h - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file defines all sst ioctls
+ */
+
+/* codec and post/pre processing related info */
+
+#include <linux/types.h>
+
+enum sst_codec_types {
+/* AUDIO/MUSIC CODEC Type Definitions */
+ SST_CODEC_TYPE_UNKNOWN = 0,
+ SST_CODEC_TYPE_PCM, /* Pass through Audio codec */
+ SST_CODEC_TYPE_MP3,
+ SST_CODEC_TYPE_MP24,
+ SST_CODEC_TYPE_AAC,
+ SST_CODEC_TYPE_AACP,
+ SST_CODEC_TYPE_eAACP,
+ SST_CODEC_TYPE_WMA9,
+ SST_CODEC_TYPE_WMA10,
+ SST_CODEC_TYPE_WMA10P,
+ SST_CODEC_TYPE_RA,
+ SST_CODEC_TYPE_DDAC3,
+ SST_CODEC_TYPE_STEREO_TRUE_HD,
+ SST_CODEC_TYPE_STEREO_HD_PLUS,
+
+ /* VOICE CODEC Type Definitions */
+ SST_CODEC_TYPE_VOICE_PCM = 0x21, /* Pass through voice codec */
+};
+
+enum sst_algo_types {
+ SST_CODEC_SRC = 0x64,
+ SST_CODEC_MIXER = 0x65,
+ SST_CODEC_DOWN_MIXER = 0x66,
+ SST_CODEC_VOLUME_CONTROL = 0x67,
+ SST_CODEC_OEM1 = 0xC8,
+ SST_CODEC_OEM2 = 0xC9,
+};
+
+enum snd_sst_stream_ops {
+ STREAM_OPS_PLAYBACK = 0, /* Decode */
+ STREAM_OPS_CAPTURE, /* Encode */
+ STREAM_OPS_PLAYBACK_DRM, /* Play Audio/Voice */
+ STREAM_OPS_PLAYBACK_ALERT, /* Play Audio/Voice */
+ STREAM_OPS_CAPTURE_VOICE_CALL, /* CSV Voice recording */
+};
+
+enum stream_mode {
+ SST_STREAM_MODE_NONE = 0,
+ SST_STREAM_MODE_DNR = 1,
+ SST_STREAM_MODE_FNF = 2,
+ SST_STREAM_MODE_CAPTURE = 3
+};
+
+enum stream_type {
+ SST_STREAM_TYPE_NONE = 0,
+ SST_STREAM_TYPE_MUSIC = 1,
+ SST_STREAM_TYPE_NORMAL = 2,
+ SST_STREAM_TYPE_LONG_PB = 3,
+ SST_STREAM_TYPE_LOW_LATENCY = 4,
+};
+
+enum snd_sst_audio_device_type {
+ SND_SST_DEVICE_HEADSET = 1,
+ SND_SST_DEVICE_IHF,
+ SND_SST_DEVICE_VIBRA,
+ SND_SST_DEVICE_HAPTIC,
+ SND_SST_DEVICE_CAPTURE,
+};
+
+/* Firmware Version info */
+struct snd_sst_fw_version {
+ __u8 build; /* build number*/
+ __u8 minor; /* minor number*/
+ __u8 major; /* major number*/
+ __u8 type; /* build type */
+};
+
+/* Port info structure */
+struct snd_sst_port_info {
+ __u16 port_type;
+ __u16 reserved;
+};
+
+/* Mixer info structure */
+struct snd_sst_mix_info {
+ __u16 max_streams;
+ __u16 reserved;
+};
+
+/* PCM Parameters */
+struct snd_pcm_params {
+ __u16 codec; /* codec type */
+ __u8 num_chan; /* 1=Mono, 2=Stereo */
+ __u8 pcm_wd_sz; /* 16/24 - bit*/
+ __u32 reserved; /* Bitrate in bits per second */
+ __u32 sfreq; /* Sampling rate in Hz */
+ __u32 ring_buffer_size;
+ __u32 period_count; /* period elapsed in samples*/
+ __u32 ring_buffer_addr;
+};
+
+/* MP3 Music Parameters Message */
+struct snd_mp3_params {
+ __u16 codec;
+ __u8 num_chan; /* 1=Mono, 2=Stereo */
+ __u8 pcm_wd_sz; /* 16/24 - bit*/
+ __u32 brate; /* Use the hard coded value. */
+ __u32 sfreq; /* Sampling freq eg. 8000, 441000, 48000 */
+ __u8 crc_check; /* crc_check - disable (0) or enable (1) */
+ __u8 op_align; /* op align 0- 16 bit, 1- MSB, 2 LSB*/
+ __u16 reserved; /* Unused */
+};
+
+#define AAC_BIT_STREAM_ADTS 0
+#define AAC_BIT_STREAM_ADIF 1
+#define AAC_BIT_STREAM_RAW 2
+
+/* AAC Music Parameters Message */
+struct snd_aac_params {
+ __u16 codec;
+ __u8 num_chan; /* 1=Mono, 2=Stereo*/
+ __u8 pcm_wd_sz; /* 16/24 - bit*/
+ __u32 brate;
+ __u32 sfreq; /* Sampling freq eg. 8000, 441000, 48000 */
+ __u32 aac_srate; /* Plain AAC decoder operating sample rate */
+ __u8 mpg_id; /* 0=MPEG-2, 1=MPEG-4 */
+ __u8 bs_format; /* input bit stream format adts=0, adif=1, raw=2 */
+ __u8 aac_profile; /* 0=Main Profile, 1=LC profile, 3=SSR profile */
+ __u8 ext_chl; /* No.of external channels */
+ __u8 aot; /* Audio object type. 1=Main , 2=LC , 3=SSR, 4=SBR*/
+ __u8 op_align; /* output alignment 0=16 bit , 1=MSB, 2= LSB align */
+ __u8 brate_type; /* 0=CBR, 1=VBR */
+ __u8 crc_check; /* crc check 0= disable, 1=enable */
+ __s8 bit_stream_format[8]; /* input bit stream format adts/adif/raw */
+ __u8 jstereo; /* Joint stereo Flag */
+ __u8 sbr_present; /* 1 = SBR Present, 0 = SBR absent, for RAW */
+ __u8 downsample; /* 1 = Downsampling ON, 0 = Downsampling OFF */
+ __u8 num_syntc_elems; /* 1- Mono/stereo, 0 - Dual Mono, 0 - for raw */
+ __s8 syntc_id[2]; /* 0 for ID_SCE(Dula Mono), -1 for raw */
+ __s8 syntc_tag[2]; /* raw - -1 and 0 -16 for rest of the streams */
+ __u8 pce_present; /* Flag. 1- present 0 - not present, for RAW */
+ __u8 sbr_type; /* sbr_type: 0-plain aac, 1-aac-v1, 2-aac-v2 */
+ __u8 outchmode; /*0- mono, 1-stereo, 2-dual mono 3-Parametric stereo */
+ __u8 ps_present;
+};
+
+/* WMA Music Parameters Message */
+struct snd_wma_params {
+ __u16 codec;
+ __u8 num_chan; /* 1=Mono, 2=Stereo */
+ __u8 pcm_wd_sz; /* 16/24 - bit*/
+ __u32 brate; /* Use the hard coded value. */
+ __u32 sfreq; /* Sampling freq eg. 8000, 441000, 48000 */
+ __u32 channel_mask; /* Channel Mask */
+ __u16 format_tag; /* Format Tag */
+ __u16 block_align; /* packet size */
+ __u16 wma_encode_opt;/* Encoder option */
+ __u8 op_align; /* op align 0- 16 bit, 1- MSB, 2 LSB */
+ __u8 pcm_src; /* input pcm bit width */
+};
+
+/* Pre processing param structure */
+struct snd_prp_params {
+ __u32 reserved; /* No pre-processing defined yet */
+};
+
+struct snd_params_block {
+ __u32 type; /*Type of the parameter*/
+ __u32 size; /*size of the parameters in the block*/
+ __u8 params[0]; /*Parameters of the algorithm*/
+};
+
+/* Pre and post processing params structure */
+struct snd_ppp_params {
+ enum sst_algo_types algo_id;/* Post/Pre processing algorithm ID */
+ __u8 str_id; /*Only 5 bits used 0 - 31 are valid*/
+ __u8 enable; /* 0= disable, 1= enable*/
+ __u8 reserved;
+ __u32 size; /*Size of parameters for all blocks*/
+ struct snd_params_block params[0];
+};
+
+struct snd_sst_postproc_info {
+ __u32 src_min; /* Supported SRC Min sampling freq */
+ __u32 src_max; /* Supported SRC Max sampling freq */
+ __u8 src; /* 0=Not supported, 1=Supported */
+ __u8 bass_boost; /* 0=Not Supported, 1=Supported */
+ __u8 stereo_widening; /* 0=Not Supported, 1=Supported */
+ __u8 volume_control; /* 0=Not Supported, 1=Supported */
+ __s16 min_vol; /* Minimum value of Volume in dB */
+ __s16 max_vol; /* Maximum value of Volume in dB */
+ __u8 mute_control; /* 0=No Mute, 1=Mute */
+ __u8 reserved1;
+ __u16 reserved2;
+};
+
+/* pre processing Capability info structure */
+struct snd_sst_prp_info {
+ __s16 min_vol; /* Minimum value of Volume in dB */
+ __s16 max_vol; /* Maximum value of Volume in dB */
+ __u8 volume_control; /* 0=Not Supported, 1=Supported */
+ __u8 reserved1; /* for 32 bit alignment */
+ __u16 reserved2; /* for 32 bit alignment */
+} __attribute__ ((packed));
+
+/*Pre / Post processing algorithms support*/
+struct snd_sst_ppp_info {
+ __u32 src:1; /* 0=Not supported, 1=Supported */
+ __u32 mixer:1; /* 0=Not supported, 1=Supported */
+ __u32 volume_control:1; /* 0=Not Supported, 1=Supported */
+ __u32 mute_control:1; /* 0=Not Supported, 1=Supported */
+ __u32 anc:1; /* 0=Not Supported, 1=Supported */
+ __u32 side_tone:1; /* 0=Not Supported, 1=Supported */
+ __u32 dc_removal:1; /* 0=Not Supported, 1=Supported */
+ __u32 equalizer:1; /* 0=Not Supported, 1=Supported */
+ __u32 spkr_prot:1; /* 0=Not Supported, 1=Supported */
+ __u32 bass_boost:1; /* 0=Not Supported, 1=Supported */
+ __u32 stereo_widening:1;/* 0=Not Supported, 1=Supported */
+ __u32 rsvd1:21;
+ __u32 rsvd2;
+};
+
+/* Firmware capabilities info */
+struct snd_sst_fw_info {
+ struct snd_sst_fw_version fw_version; /* Firmware version */
+ __u8 audio_codecs_supported[8]; /* Codecs supported by FW */
+ __u32 recommend_min_duration; /* Min duration for Lowpower Playback */
+ __u8 max_pcm_streams_supported; /* Max num of PCM streams supported */
+ __u8 max_enc_streams_supported; /* Max number of Encoded streams */
+ __u16 reserved; /* 32 bit alignment*/
+ struct snd_sst_ppp_info ppp_info; /* pre_processing mod cap info */
+ struct snd_sst_postproc_info pop_info; /* Post processing cap info*/
+ struct snd_sst_port_info port_info[3]; /* Port info */
+ struct snd_sst_mix_info mix_info;/* Mixer info */
+ __u32 min_input_buf; /* minmum i/p buffer for decode */
+};
+
+/* Codec params struture */
+union snd_sst_codec_params {
+ struct snd_pcm_params pcm_params;
+ struct snd_mp3_params mp3_params;
+ struct snd_aac_params aac_params;
+ struct snd_wma_params wma_params;
+};
+
+
+struct snd_sst_stream_params {
+ union snd_sst_codec_params uc;
+} __attribute__ ((packed));
+
+struct snd_sst_params {
+ __u32 result;
+ __u32 stream_id;
+ __u8 codec;
+ __u8 ops;
+ __u8 stream_type;
+ __u8 device_type;
+ struct snd_sst_stream_params sparams;
+};
+
+struct snd_sst_vol {
+ __u32 stream_id;
+ __s32 volume;
+ __u32 ramp_duration;
+ __u32 ramp_type; /* Ramp type, default=0 */
+};
+
+struct snd_sst_mute {
+ __u32 stream_id;
+ __u32 mute;
+};
+
+/* ioctl related stuff here */
+struct snd_sst_pmic_config {
+ __u32 sfreq; /* Sampling rate in Hz */
+ __u16 num_chan; /* Mono =1 or Stereo =2 */
+ __u16 pcm_wd_sz; /* Number of bits per sample */
+} __attribute__ ((packed));
+
+struct snd_sst_get_stream_params {
+ struct snd_sst_params codec_params;
+ struct snd_sst_pmic_config pcm_params;
+};
+
+enum snd_sst_target_type {
+ SND_SST_TARGET_PMIC = 1,
+ SND_SST_TARGET_LPE,
+ SND_SST_TARGET_MODEM,
+ SND_SST_TARGET_BT,
+ SND_SST_TARGET_FM,
+ SND_SST_TARGET_NONE,
+};
+
+enum snd_sst_device_type {
+ SND_SST_DEVICE_SSP = 1,
+ SND_SST_DEVICE_PCM,
+ SND_SST_DEVICE_OTHER,
+};
+
+enum snd_sst_device_mode {
+
+ SND_SST_DEV_MODE_PCM_MODE1 = 1, /*(16-bit word, bit-length frame sync)*/
+ SND_SST_DEV_MODE_PCM_MODE2,
+ SND_SST_DEV_MODE_PCM_MODE3,
+ SND_SST_DEV_MODE_PCM_MODE4_RIGHT_JUSTIFIED,
+ SND_SST_DEV_MODE_PCM_MODE4_LEFT_JUSTIFIED,
+ SND_SST_DEV_MODE_PCM_MODE4_I2S, /*(I2S mode, 16-bit words)*/
+ SND_SST_DEV_MODE_PCM_MODE5,
+ SND_SST_DEV_MODE_PCM_MODE6,
+};
+
+enum snd_sst_port_action {
+ SND_SST_PORT_PREPARE = 1,
+ SND_SST_PORT_ACTIVATE,
+};
+
+/* Target selection per device structure */
+struct snd_sst_slot_info {
+ __u8 mix_enable; /* Mixer enable or disable */
+ __u8 device_type;
+ __u8 device_instance; /* 0, 1, 2 */
+ __u8 target_device;
+ __u16 target_sink;
+ __u8 slot[2];
+ __u8 master;
+ __u8 action;
+ __u8 device_mode;
+ __u8 reserved;
+ struct snd_sst_pmic_config pcm_params;
+} __attribute__ ((packed));
+
+#define SST_MAX_TARGET_DEVICES 3
+/* Target device list structure */
+struct snd_sst_target_device {
+ __u32 device_route;
+ struct snd_sst_slot_info devices[SST_MAX_TARGET_DEVICES];
+} __attribute__ ((packed));
+
+struct snd_sst_driver_info {
+ __u32 version; /* Version of the driver */
+ __u32 active_pcm_streams;
+ __u32 active_enc_streams;
+ __u32 max_pcm_streams;
+ __u32 max_enc_streams;
+ __u32 buf_per_stream;
+};
+
+enum snd_sst_buff_type {
+ SST_BUF_USER = 1,
+ SST_BUF_MMAP,
+ SST_BUF_RAR,
+};
+
+struct snd_sst_mmap_buff_entry {
+ unsigned int offset;
+ unsigned int size;
+};
+
+struct snd_sst_mmap_buffs {
+ unsigned int entries;
+ enum snd_sst_buff_type type;
+ struct snd_sst_mmap_buff_entry *buff;
+};
+
+struct snd_sst_buff_entry {
+ void *buffer;
+ unsigned int size;
+};
+
+struct snd_sst_buffs {
+ unsigned int entries;
+ __u8 type;
+ struct snd_sst_buff_entry *buff_entry;
+};
+
+struct snd_sst_dbufs {
+ unsigned long long input_bytes_consumed;
+ unsigned long long output_bytes_produced;
+ struct snd_sst_buffs *ibufs;
+ struct snd_sst_buffs *obufs;
+};
+
+/*IOCTL defined here */
+/*SST MMF IOCTLS only */
+#define SNDRV_SST_STREAM_SET_PARAMS _IOR('L', 0x00, \
+ struct snd_sst_stream_params *)
+#define SNDRV_SST_STREAM_GET_PARAMS _IOWR('L', 0x01, \
+ struct snd_sst_get_stream_params *)
+#define SNDRV_SST_STREAM_GET_TSTAMP _IOWR('L', 0x02, __u64 *)
+#define SNDRV_SST_STREAM_DECODE _IOWR('L', 0x03, struct snd_sst_dbufs *)
+#define SNDRV_SST_STREAM_BYTES_DECODED _IOWR('L', 0x04, __u64 *)
+#define SNDRV_SST_STREAM_START _IO('A', 0x42)
+#define SNDRV_SST_STREAM_DROP _IO('A', 0x43)
+#define SNDRV_SST_STREAM_DRAIN _IO('A', 0x44)
+#define SNDRV_SST_STREAM_PAUSE _IOW('A', 0x45, int)
+#define SNDRV_SST_STREAM_RESUME _IO('A', 0x47)
+#define SNDRV_SST_MMAP_PLAY _IOW('L', 0x05, struct snd_sst_mmap_buffs *)
+#define SNDRV_SST_MMAP_CAPTURE _IOW('L', 0x06, struct snd_sst_mmap_buffs *)
+/*SST common ioctls */
+#define SNDRV_SST_DRIVER_INFO _IOR('L', 0x10, struct snd_sst_driver_info *)
+#define SNDRV_SST_SET_VOL _IOW('L', 0x11, struct snd_sst_vol *)
+#define SNDRV_SST_GET_VOL _IOW('L', 0x12, struct snd_sst_vol *)
+#define SNDRV_SST_MUTE _IOW('L', 0x13, struct snd_sst_mute *)
+/*AM Ioctly only */
+#define SNDRV_SST_FW_INFO _IOR('L', 0x20, struct snd_sst_fw_info *)
+#define SNDRV_SST_SET_TARGET_DEVICE _IOW('L', 0x21, \
+ struct snd_sst_target_device *)
+
+#endif /* __INTEL_SST_IOCTL_H__ */
diff --git a/drivers/staging/intel_sst/intel_sst_ipc.c b/drivers/staging/intel_sst/intel_sst_ipc.c
new file mode 100644
index 0000000..39c67fa
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_ipc.c
@@ -0,0 +1,656 @@
+/*
+ * intel_sst_ipc.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file defines all ipc functions
+ */
+
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+/*
+ * sst_send_sound_card_type - send sound card type
+ *
+ * this function sends the sound card type to sst dsp engine
+ */
+static void sst_send_sound_card_type(void)
+{
+ struct ipc_post *msg = NULL;
+
+ if (sst_create_short_msg(&msg))
+ return;
+
+ sst_fill_header(&msg->header, IPC_IA_SET_PMIC_TYPE, 0, 0);
+ msg->header.part.data = sst_drv_ctx->pmic_vendor;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ return;
+}
+
+/**
+* sst_post_message - Posts message to SST
+*
+* @work: Pointer to work structure
+*
+* This function is called by any component in driver which
+* wants to send an IPC message. This will post message only if
+* busy bit is free
+*/
+void sst_post_message(struct work_struct *work)
+{
+ struct ipc_post *msg;
+ union ipc_header header;
+ union interrupt_reg imr;
+ int retval = 0;
+ imr.full = 0;
+
+ /*To check if LPE is in stalled state.*/
+ retval = sst_stalled();
+ if (retval < 0) {
+ pr_err("sst: in stalled state\n");
+ return;
+ }
+ pr_debug("sst: post message called\n");
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+
+ /* check list */
+ if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) {
+ /* list is empty, mask imr */
+ pr_debug("sst: Empty msg queue... masking\n");
+ imr.full = readl(sst_drv_ctx->shim + SST_IMRX);
+ imr.part.done_interrupt = 1;
+ /* dummy register for shim workaround */
+ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ return;
+ }
+
+ /* check busy bit */
+ header.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCX);
+ if (header.part.busy) {
+ /* busy, unmask */
+ pr_debug("sst: Busy not free... unmasking\n");
+ imr.full = readl(sst_drv_ctx->shim + SST_IMRX);
+ imr.part.done_interrupt = 0;
+ /* dummy register for shim workaround */
+ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ return;
+ }
+ /* copy msg from list */
+ msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next,
+ struct ipc_post, node);
+ list_del(&msg->node);
+ pr_debug("sst: Post message: header = %x\n", msg->header.full);
+ pr_debug("sst: size: = %x\n", msg->header.part.data);
+ if (msg->header.part.large)
+ memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND,
+ msg->mailbox_data, msg->header.part.data);
+ /* dummy register for shim workaround */
+
+ sst_shim_write(sst_drv_ctx->shim, SST_IPCX, msg->header.full);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+
+ kfree(msg->mailbox_data);
+ kfree(msg);
+ return;
+}
+
+/*
+ * sst_clear_interrupt - clear the SST FW interrupt
+ *
+ * This function clears the interrupt register after the interrupt
+ * bottom half is complete allowing next interrupt to arrive
+ */
+void sst_clear_interrupt(void)
+{
+ union interrupt_reg isr;
+ union interrupt_reg imr;
+ union ipc_header clear_ipc;
+
+ imr.full = sst_shim_read(sst_drv_ctx->shim, SST_IMRX);
+ isr.full = sst_shim_read(sst_drv_ctx->shim, SST_ISRX);
+ /* write 1 to clear */;
+ isr.part.busy_interrupt = 1;
+ sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full);
+ /* Set IA done bit */
+ clear_ipc.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCD);
+ clear_ipc.part.busy = 0;
+ clear_ipc.part.done = 1;
+ clear_ipc.part.data = IPC_ACK_SUCCESS;
+ sst_shim_write(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full);
+ /* un mask busy interrupt */
+ imr.part.busy_interrupt = 0;
+ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full);
+}
+
+/*
+ * process_fw_init - process the FW init msg
+ *
+ * @msg: IPC message from FW
+ *
+ * This function processes the FW init msg from FW
+ * marks FW state and prints debug info of loaded FW
+ */
+int process_fw_init(struct sst_ipc_msg_wq *msg)
+{
+ struct ipc_header_fw_init *init =
+ (struct ipc_header_fw_init *)msg->mailbox;
+ int retval = 0;
+
+ pr_debug("sst: *** FW Init msg came***\n");
+ if (init->result) {
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_ERROR;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ pr_debug("sst: FW Init failed, Error %x\n", init->result);
+ pr_err("sst: FW Init failed, Error %x\n", init->result);
+ retval = -init->result;
+ return retval;
+ }
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
+ sst_send_sound_card_type();
+ mutex_lock(&sst_drv_ctx->sst_lock);
+ sst_drv_ctx->sst_state = SST_FW_RUNNING;
+ mutex_unlock(&sst_drv_ctx->sst_lock);
+ pr_debug("sst: FW Version %x.%x\n",
+ init->fw_version.major, init->fw_version.minor);
+ pr_debug("sst: Build No %x Type %x\n",
+ init->fw_version.build, init->fw_version.type);
+ pr_debug("sst: Build date %s Time %s\n",
+ init->build_info.date, init->build_info.time);
+ sst_wake_up_alloc_block(sst_drv_ctx, FW_DWNL_ID, retval, NULL);
+ return retval;
+}
+/**
+* sst_process_message - Processes message from SST
+*
+* @work: Pointer to work structure
+*
+* This function is scheduled by ISR
+* It take a msg from process_queue and does action based on msg
+*/
+void sst_process_message(struct work_struct *work)
+{
+ struct sst_ipc_msg_wq *msg =
+ container_of(work, struct sst_ipc_msg_wq, wq);
+ int str_id = msg->header.part.str_id;
+
+ pr_debug("sst: IPC process for %x\n", msg->header.full);
+
+ /* based on msg in list call respective handler */
+ switch (msg->header.part.msg_id) {
+ case IPC_SST_BUF_UNDER_RUN:
+ case IPC_SST_BUF_OVER_RUN:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+ pr_err("sst: Buffer under/overrun for%d\n",
+ msg->header.part.str_id);
+ pr_err("sst: Got Underrun & not to send data...ignore\n");
+ break;
+
+ case IPC_SST_GET_PLAY_FRAMES:
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ struct stream_info *stream ;
+
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: strid %d invalid\n", str_id);
+ break;
+ }
+ /* call sst_play_frame */
+ stream = &sst_drv_ctx->streams[str_id];
+ pr_debug("sst: sst_play_frames for %d\n",
+ msg->header.part.str_id);
+ mutex_lock(&sst_drv_ctx->streams[str_id].lock);
+ sst_play_frame(msg->header.part.str_id);
+ mutex_unlock(&sst_drv_ctx->streams[str_id].lock);
+ break;
+ } else
+ pr_err("sst: sst_play_frames for Penwell!!\n");
+
+ case IPC_SST_GET_CAPT_FRAMES:
+ if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) {
+ struct stream_info *stream;
+ /* call sst_capture_frame */
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: str id %d invalid\n", str_id);
+ break;
+ }
+ stream = &sst_drv_ctx->streams[str_id];
+ pr_debug("sst: sst_capture_frames for %d\n",
+ msg->header.part.str_id);
+ mutex_lock(&stream->lock);
+ if (stream->mmapped == false &&
+ stream->src == SST_DRV) {
+ pr_debug("sst: waking up block for copy.\n");
+ stream->data_blk.ret_code = 0;
+ stream->data_blk.condition = true;
+ stream->data_blk.on = false;
+ wake_up(&sst_drv_ctx->wait_queue);
+ } else
+ sst_capture_frame(msg->header.part.str_id);
+ mutex_unlock(&stream->lock);
+ } else
+ pr_err("sst: sst_play_frames for Penwell!!\n");
+ break;
+
+ case IPC_IA_PRINT_STRING:
+ pr_debug("sst: been asked to print something by fw\n");
+ /* TBD */
+ break;
+
+ case IPC_IA_FW_INIT_CMPLT: {
+ /* send next data to FW */
+ process_fw_init(msg);
+ break;
+ }
+
+ case IPC_SST_STREAM_PROCESS_FATAL_ERR:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+ pr_err("sst: codec fatal error %x stream %d...\n",
+ msg->header.full, msg->header.part.str_id);
+ pr_err("sst: Dropping the stream\n");
+ sst_drop_stream(msg->header.part.str_id);
+ break;
+ case IPC_IA_LPE_GETTING_STALLED:
+ sst_drv_ctx->lpe_stalled = 1;
+ break;
+ case IPC_IA_LPE_UNSTALLED:
+ sst_drv_ctx->lpe_stalled = 0;
+ break;
+ default:
+ /* Illegal case */
+ pr_err("sst: Unhandled msg %x header %x\n",
+ msg->header.part.msg_id, msg->header.full);
+ }
+ sst_clear_interrupt();
+ return;
+}
+
+/**
+* sst_process_reply - Processes reply message from SST
+*
+* @work: Pointer to work structure
+*
+* This function is scheduled by ISR
+* It take a reply msg from response_queue and
+* does action based on msg
+*/
+void sst_process_reply(struct work_struct *work)
+{
+ struct sst_ipc_msg_wq *msg =
+ container_of(work, struct sst_ipc_msg_wq, wq);
+
+ int str_id = msg->header.part.str_id;
+ struct stream_info *str_info;
+
+ switch (msg->header.part.msg_id) {
+ case IPC_IA_TARGET_DEV_SELECT:
+ if (!msg->header.part.data) {
+ sst_drv_ctx->tgt_dev_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ sst_drv_ctx->tgt_dev_blk.ret_code =
+ -msg->header.part.data;
+ }
+
+ if (sst_drv_ctx->tgt_dev_blk.on == true) {
+ sst_drv_ctx->tgt_dev_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_GET_FW_INFO: {
+ struct snd_sst_fw_info *fw_info =
+ (struct snd_sst_fw_info *)msg->mailbox;
+ if (msg->header.part.large) {
+ int major = fw_info->fw_version.major;
+ int minor = fw_info->fw_version.minor;
+ int build = fw_info->fw_version.build;
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ pr_debug("INFO: ***FW*** = %02d.%02d.%02d\n",
+ major, minor, build);
+ memcpy_fromio(sst_drv_ctx->fw_info_blk.data,
+ ((struct snd_sst_fw_info *)(msg->mailbox)),
+ sizeof(struct snd_sst_fw_info));
+ sst_drv_ctx->fw_info_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ sst_drv_ctx->fw_info_blk.ret_code =
+ -msg->header.part.data;
+ }
+ if (sst_drv_ctx->fw_info_blk.on == true) {
+ pr_debug("sst: Memcopy succedded\n");
+ sst_drv_ctx->fw_info_blk.on = false;
+ sst_drv_ctx->fw_info_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ }
+ case IPC_IA_SET_STREAM_MUTE:
+ if (!msg->header.part.data) {
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ sst_drv_ctx->mute_info_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ sst_drv_ctx->mute_info_blk.ret_code =
+ -msg->header.part.data;
+
+ }
+ if (sst_drv_ctx->mute_info_blk.on == true) {
+ sst_drv_ctx->mute_info_blk.on = false;
+ sst_drv_ctx->mute_info_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_SET_STREAM_VOL:
+ if (!msg->header.part.data) {
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ sst_drv_ctx->vol_info_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id,
+ msg->header.part.data);
+ sst_drv_ctx->vol_info_blk.ret_code =
+ -msg->header.part.data;
+
+ }
+
+ if (sst_drv_ctx->vol_info_blk.on == true) {
+ sst_drv_ctx->vol_info_blk.on = false;
+ sst_drv_ctx->vol_info_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_GET_STREAM_VOL:
+ if (msg->header.part.large) {
+ pr_debug("sst: Large Msg Received Successfully\n");
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ memcpy_fromio(sst_drv_ctx->vol_info_blk.data,
+ (void *) msg->mailbox,
+ sizeof(struct snd_sst_vol));
+ sst_drv_ctx->vol_info_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ sst_drv_ctx->vol_info_blk.ret_code =
+ -msg->header.part.data;
+ }
+ if (sst_drv_ctx->vol_info_blk.on == true) {
+ sst_drv_ctx->vol_info_blk.on = false;
+ sst_drv_ctx->vol_info_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+
+ case IPC_IA_GET_STREAM_PARAMS:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (msg->header.part.large) {
+ pr_debug("sst: Get stream large success\n");
+ memcpy_fromio(str_info->ctrl_blk.data,
+ ((void *)(msg->mailbox)),
+ sizeof(struct snd_sst_fw_get_stream_params));
+ str_info->ctrl_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ str_info->ctrl_blk.ret_code = -msg->header.part.data;
+ }
+ if (str_info->ctrl_blk.on == true) {
+ str_info->ctrl_blk.on = false;
+ str_info->ctrl_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_DECODE_FRAMES:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (msg->header.part.large) {
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ memcpy_fromio(str_info->data_blk.data,
+ ((void *)(msg->mailbox)),
+ sizeof(struct snd_sst_decode_info));
+ str_info->data_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ str_info->data_blk.ret_code = -msg->header.part.data;
+ }
+ if (str_info->data_blk.on == true) {
+ str_info->data_blk.on = false;
+ str_info->data_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_DRAIN_STREAM:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (!msg->header.part.data) {
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ str_info->ctrl_blk.ret_code = 0;
+
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ str_info->ctrl_blk.ret_code = -msg->header.part.data;
+
+ }
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->data_blk.on == true) {
+ str_info->data_blk.on = false;
+ str_info->data_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+
+ case IPC_IA_DROP_STREAM:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: str id %d invalid\n", str_id);
+ break;
+ }
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (msg->header.part.large) {
+ struct snd_sst_drop_response *drop_resp =
+ (struct snd_sst_drop_response *)msg->mailbox;
+
+ pr_debug("sst: Drop ret bytes %x\n", drop_resp->bytes);
+
+ str_info->curr_bytes = drop_resp->bytes;
+ str_info->ctrl_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id, msg->header.part.data);
+ str_info->ctrl_blk.ret_code = -msg->header.part.data;
+ }
+ if (str_info->ctrl_blk.on == true) {
+ str_info->ctrl_blk.on = false;
+ str_info->ctrl_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_ENABLE_RX_TIME_SLOT:
+ if (!msg->header.part.data) {
+ pr_debug("sst: RX_TIME_SLOT success\n");
+ sst_drv_ctx->hs_info_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id,
+ msg->header.part.data);
+ sst_drv_ctx->hs_info_blk.ret_code =
+ -msg->header.part.data;
+ }
+ if (sst_drv_ctx->hs_info_blk.on == true) {
+ sst_drv_ctx->hs_info_blk.on = false;
+ sst_drv_ctx->hs_info_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+ case IPC_IA_PAUSE_STREAM:
+ case IPC_IA_RESUME_STREAM:
+ case IPC_IA_SET_STREAM_PARAMS:
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (!msg->header.part.data) {
+ pr_debug("sst: Msg succedded %x\n",
+ msg->header.part.msg_id);
+ str_info->ctrl_blk.ret_code = 0;
+ } else {
+ pr_err("sst: Msg %x reply error %x\n",
+ msg->header.part.msg_id,
+ msg->header.part.data);
+ str_info->ctrl_blk.ret_code = -msg->header.part.data;
+ }
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n", str_id);
+ break;
+ }
+
+ if (str_info->ctrl_blk.on == true) {
+ str_info->ctrl_blk.on = false;
+ str_info->ctrl_blk.condition = true;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ break;
+
+ case IPC_IA_FREE_STREAM:
+ if (!msg->header.part.data) {
+ pr_debug("sst: Stream %d freed\n", str_id);
+ } else {
+ pr_err("sst: Free for %d ret error %x\n",
+ str_id, msg->header.part.data);
+ }
+ break;
+ case IPC_IA_ALLOC_STREAM: {
+ /* map to stream, call play */
+ struct snd_sst_alloc_response *resp =
+ (struct snd_sst_alloc_response *)msg->mailbox;
+ if (resp->str_type.result)
+ pr_err("sst: error alloc stream = %x\n",
+ resp->str_type.result);
+ sst_alloc_stream_response(str_id, resp);
+ break;
+ }
+
+ case IPC_IA_PLAY_FRAMES:
+ case IPC_IA_CAPT_FRAMES:
+ if (sst_validate_strid(str_id)) {
+ pr_err("sst: stream id %d invalid\n" , str_id);
+ break;
+ }
+ pr_debug("sst: Ack for play/capt frames recived\n");
+ break;
+
+ case IPC_IA_PREP_LIB_DNLD: {
+ struct snd_sst_str_type *str_type =
+ (struct snd_sst_str_type *)msg->mailbox;
+ pr_debug("sst: Prep Lib download %x\n",
+ msg->header.part.msg_id);
+ if (str_type->result)
+ pr_err("sst: Prep lib download %x\n", str_type->result);
+ else
+ pr_debug("sst: Can download codec now...\n");
+ sst_wake_up_alloc_block(sst_drv_ctx, str_id,
+ str_type->result, NULL);
+ break;
+ }
+
+ case IPC_IA_LIB_DNLD_CMPLT: {
+ struct snd_sst_lib_download_info *resp =
+ (struct snd_sst_lib_download_info *)msg->mailbox;
+ int retval = resp->result;
+
+ pr_debug("sst: Lib downloaded %x\n", msg->header.part.msg_id);
+ if (resp->result) {
+ pr_err("sst: err in lib dload %x\n", resp->result);
+ } else {
+ pr_debug("sst: Codec download complete...\n");
+ pr_debug("sst: codec Type %d Ver %d Built %s: %s\n",
+ resp->dload_lib.lib_info.lib_type,
+ resp->dload_lib.lib_info.lib_version,
+ resp->dload_lib.lib_info.b_date,
+ resp->dload_lib.lib_info.b_time);
+ }
+ sst_wake_up_alloc_block(sst_drv_ctx, str_id,
+ retval, NULL);
+ break;
+ }
+
+ case IPC_IA_GET_FW_VERSION: {
+ struct ipc_header_fw_init *version =
+ (struct ipc_header_fw_init *)msg->mailbox;
+ int major = version->fw_version.major;
+ int minor = version->fw_version.minor;
+ int build = version->fw_version.build;
+ dev_info(&sst_drv_ctx->pci->dev,
+ "INFO: ***LOADED SST FW VERSION*** = %02d.%02d.%02d\n",
+ major, minor, build);
+ break;
+ }
+ case IPC_IA_GET_FW_BUILD_INF: {
+ struct sst_fw_build_info *build =
+ (struct sst_fw_build_info *)msg->mailbox;
+ pr_debug("sst: Build date:%sTime:%s", build->date, build->time);
+ break;
+ }
+ case IPC_IA_SET_PMIC_TYPE:
+ break;
+ case IPC_IA_START_STREAM:
+ pr_debug("sst: reply for START STREAM %x\n", msg->header.full);
+ break;
+ default:
+ /* Illegal case */
+ pr_err("sst: process reply:default = %x\n", msg->header.full);
+ }
+ sst_clear_interrupt();
+ return;
+}
diff --git a/drivers/staging/intel_sst/intel_sst_pvt.c b/drivers/staging/intel_sst/intel_sst_pvt.c
new file mode 100644
index 0000000..6487e19
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_pvt.c
@@ -0,0 +1,311 @@
+/*
+ * intel_sst_pvt.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver exposes the audio engine functionalities to the ALSA
+ * and middleware.
+ *
+ * This file contains all private functions
+ */
+
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+/*
+ * sst_get_block_stream - get a new block stream
+ *
+ * @sst_drv_ctx: Driver context structure
+ *
+ * This function assigns a block for the calls that dont have stream context yet
+ * the blocks are used for waiting on Firmware's response for any operation
+ * Should be called with stream lock held
+ */
+int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx)
+{
+ int i;
+
+ for (i = 0; i < MAX_ACTIVE_STREAM; i++) {
+ if (sst_drv_ctx->alloc_block[i].sst_id == BLOCK_UNINIT) {
+ sst_drv_ctx->alloc_block[i].ops_block.condition = false;
+ sst_drv_ctx->alloc_block[i].ops_block.ret_code = 0;
+ sst_drv_ctx->alloc_block[i].sst_id = 0;
+ break;
+ }
+ }
+ if (i == MAX_ACTIVE_STREAM) {
+ pr_err("sst: max alloc_stream reached");
+ i = -EBUSY; /* active stream limit reached */
+ }
+ return i;
+}
+
+/*
+ * sst_wait_interruptible - wait on event
+ *
+ * @sst_drv_ctx: Driver context
+ * @block: Driver block to wait on
+ *
+ * This function waits without a timeout (and is interruptable) for a
+ * given block event
+ */
+int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block)
+{
+ int retval = 0;
+
+ if (!wait_event_interruptible(sst_drv_ctx->wait_queue,
+ block->condition)) {
+ /* event wake */
+ if (block->ret_code < 0) {
+ pr_err("sst: stream failed %d\n", block->ret_code);
+ retval = -EBUSY;
+ } else {
+ pr_debug("sst: event up\n");
+ retval = 0;
+ }
+ } else {
+ pr_err("sst: signal interrupted\n");
+ retval = -EINTR;
+ }
+ return retval;
+
+}
+
+
+/*
+ * sst_wait_interruptible_timeout - wait on event interruptable
+ *
+ * @sst_drv_ctx: Driver context
+ * @block: Driver block to wait on
+ * @timeout: time for wait on
+ *
+ * This function waits with a timeout value (and is interruptible) on a
+ * given block event
+ */
+int sst_wait_interruptible_timeout(
+ struct intel_sst_drv *sst_drv_ctx,
+ struct sst_block *block, int timeout)
+{
+ int retval = 0;
+
+ pr_debug("sst: sst_wait_interruptible_timeout - waiting....\n");
+ if (wait_event_interruptible_timeout(sst_drv_ctx->wait_queue,
+ block->condition,
+ msecs_to_jiffies(timeout))) {
+ if (block->ret_code < 0)
+ pr_err("sst: stream failed %d\n", block->ret_code);
+ else
+ pr_debug("sst: event up\n");
+ retval = block->ret_code;
+ } else {
+ block->on = false;
+ pr_err("sst: timeout occured...\n");
+ /*setting firmware state as uninit so that the
+ firmware will get re-downloaded on next request
+ this is because firmare not responding for 5 sec
+ is equalant to some unrecoverable error of FW
+ sst_drv_ctx->sst_state = SST_UN_INIT;*/
+ retval = -EBUSY;
+ }
+ return retval;
+
+}
+
+
+/*
+ * sst_wait_timeout - wait on event for timeout
+ *
+ * @sst_drv_ctx: Driver context
+ * @block: Driver block to wait on
+ *
+ * This function waits with a timeout value (and is not interruptible) on a
+ * given block event
+ */
+int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx,
+ struct stream_alloc_block *block)
+{
+ int retval = 0;
+
+ /* NOTE:
+ Observed that FW processes the alloc msg and replies even
+ before the alloc thread has finished execution */
+ pr_debug("sst: waiting for %x, condition %x\n",
+ block->sst_id, block->ops_block.condition);
+ if (wait_event_interruptible_timeout(sst_drv_ctx->wait_queue,
+ block->ops_block.condition,
+ msecs_to_jiffies(SST_BLOCK_TIMEOUT))) {
+ /* event wake */
+ pr_debug("sst: Event wake %x\n", block->ops_block.condition);
+ pr_debug("sst: message ret: %d\n", block->ops_block.ret_code);
+ retval = block->ops_block.ret_code;
+ } else {
+ block->ops_block.on = false;
+ pr_err("sst: Wait timed-out %x\n", block->ops_block.condition);
+ /* settign firmware state as uninit so that the
+ firmware will get redownloaded on next request
+ this is because firmare not responding for 5 sec
+ is equalant to some unrecoverable error of FW
+ sst_drv_ctx->sst_state = SST_UN_INIT;*/
+ retval = -EBUSY;
+ }
+ return retval;
+
+}
+
+/*
+ * sst_create_large_msg - create a large IPC message
+ *
+ * @arg: ipc message
+ *
+ * this function allocates structures to send a large message to the firmware
+ */
+int sst_create_large_msg(struct ipc_post **arg)
+{
+ struct ipc_post *msg;
+
+ msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC);
+ if (!msg) {
+ pr_err("sst: kzalloc msg failed\n");
+ return -ENOMEM;
+ }
+
+ msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC);
+ if (!msg->mailbox_data) {
+ kfree(msg);
+ pr_err("sst: kzalloc mailbox_data failed");
+ return -ENOMEM;
+ };
+ *arg = msg;
+ return 0;
+}
+
+/*
+ * sst_create_short_msg - create a short IPC message
+ *
+ * @arg: ipc message
+ *
+ * this function allocates structures to send a short message to the firmware
+ */
+int sst_create_short_msg(struct ipc_post **arg)
+{
+ struct ipc_post *msg;
+
+ msg = kzalloc(sizeof(*msg), GFP_ATOMIC);
+ if (!msg) {
+ pr_err("sst: kzalloc msg failed\n");
+ return -ENOMEM;
+ }
+ msg->mailbox_data = NULL;
+ *arg = msg;
+ return 0;
+}
+
+/*
+ * sst_clean_stream - clean the stream context
+ *
+ * @stream: stream structure
+ *
+ * this function resets the stream contexts
+ * should be called in free
+ */
+void sst_clean_stream(struct stream_info *stream)
+{
+ struct sst_stream_bufs *bufs = NULL, *_bufs;
+ stream->status = STREAM_UN_INIT;
+ stream->prev = STREAM_UN_INIT;
+ mutex_lock(&stream->lock);
+ list_for_each_entry_safe(bufs, _bufs, &stream->bufs, node) {
+ list_del(&bufs->node);
+ kfree(bufs);
+ }
+ mutex_unlock(&stream->lock);
+
+ if (stream->ops != STREAM_OPS_PLAYBACK_DRM)
+ kfree(stream->decode_ibuf);
+}
+
+/*
+ * sst_wake_up_alloc_block - wake up waiting block
+ *
+ * @sst_drv_ctx: Driver context
+ * @sst_id: stream id
+ * @status: status of wakeup
+ * @data: data pointer of wakeup
+ *
+ * This function wakes up a sleeping block event based on the response
+ */
+void sst_wake_up_alloc_block(struct intel_sst_drv *sst_drv_ctx,
+ u8 sst_id, int status, void *data)
+{
+ int i;
+
+ /* Unblock with retval code */
+ for (i = 0; i < MAX_ACTIVE_STREAM; i++) {
+ if (sst_id == sst_drv_ctx->alloc_block[i].sst_id) {
+ sst_drv_ctx->alloc_block[i].ops_block.condition = true;
+ sst_drv_ctx->alloc_block[i].ops_block.ret_code = status;
+ sst_drv_ctx->alloc_block[i].ops_block.data = data;
+ wake_up(&sst_drv_ctx->wait_queue);
+ break;
+ }
+ }
+}
+
+/*
+ * sst_enable_rx_timeslot - Send msg to query for stream parameters
+ * @status: rx timeslot to be enabled
+ *
+ * This function is called when the RX timeslot is required to be enabled
+ */
+int sst_enable_rx_timeslot(int status)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+
+ if (sst_create_short_msg(&msg)) {
+ pr_err("sst: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ pr_debug("sst: ipc message sending: ENABLE_RX_TIME_SLOT\n");
+ sst_fill_header(&msg->header, IPC_IA_ENABLE_RX_TIME_SLOT, 0, 0);
+ msg->header.part.data = status;
+ sst_drv_ctx->hs_info_blk.condition = false;
+ sst_drv_ctx->hs_info_blk.ret_code = 0;
+ sst_drv_ctx->hs_info_blk.on = true;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node,
+ &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->hs_info_blk, SST_BLOCK_TIMEOUT);
+ return retval;
+}
+
diff --git a/drivers/staging/intel_sst/intel_sst_stream.c b/drivers/staging/intel_sst/intel_sst_stream.c
new file mode 100644
index 0000000..ff46d5c
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_stream.c
@@ -0,0 +1,575 @@
+/*
+ * intel_sst_stream.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the stream operations of SST driver
+ */
+
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include "intel_sst_ioctl.h"
+#include "intel_sst.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+
+/*
+ * sst_check_device_type - Check the medfield device type
+ *
+ * @device: Device to be checked
+ * @num_ch: Number of channels queried
+ * @pcm_slot: slot to be enabled for this device
+ *
+ * This checks the deivce against the map and calculates pcm_slot value
+ */
+int sst_check_device_type(u32 device, u32 num_chan, u32 *pcm_slot)
+{
+ if (device > MAX_NUM_STREAMS_MFLD) {
+ pr_debug("sst: device type invalid %d\n", device);
+ return -EINVAL;
+ }
+ if (sst_drv_ctx->streams[device].status == STREAM_UN_INIT) {
+ if (device == SND_SST_DEVICE_VIBRA && num_chan == 1)
+ *pcm_slot = 0x10;
+ else if (device == SND_SST_DEVICE_HAPTIC && num_chan == 1)
+ *pcm_slot = 0x20;
+ else if (device == SND_SST_DEVICE_IHF && num_chan == 1)
+ *pcm_slot = 0x04;
+ else if (device == SND_SST_DEVICE_IHF && num_chan == 2)
+ *pcm_slot = 0x0C;
+ else if (device == SND_SST_DEVICE_HEADSET && num_chan == 1)
+ *pcm_slot = 0x01;
+ else if (device == SND_SST_DEVICE_HEADSET && num_chan == 2)
+ *pcm_slot = 0x03;
+ else if (device == SND_SST_DEVICE_CAPTURE && num_chan == 1)
+ *pcm_slot = 0x01;
+ else if (device == SND_SST_DEVICE_CAPTURE && num_chan == 2)
+ *pcm_slot = 0x03;
+ else if (device == SND_SST_DEVICE_CAPTURE && num_chan == 3)
+ *pcm_slot = 0x07;
+ else if (device == SND_SST_DEVICE_CAPTURE && num_chan == 4)
+ *pcm_slot = 0x0F;
+ else {
+ pr_debug("sst: No condition satisfied.. ret err\n");
+ return -EINVAL;
+ }
+ } else {
+ pr_debug("sst: this stream state is not uni-init, is %d\n",
+ sst_drv_ctx->streams[device].status);
+ return -EBADRQC;
+ }
+ pr_debug("sst: returning slot %x\n", *pcm_slot);
+ return 0;
+}
+/**
+ * get_mrst_stream_id - gets a new stream id for use
+ *
+ * This functions searches the current streams and allocated an empty stream
+ * lock stream_lock required to be held before calling this
+ */
+static unsigned int get_mrst_stream_id(void)
+{
+ int i;
+
+ for (i = 1; i <= MAX_NUM_STREAMS_MRST; i++) {
+ if (sst_drv_ctx->streams[i].status == STREAM_UN_INIT)
+ return i;
+ }
+ pr_debug("sst: Didnt find empty stream for mrst\n");
+ return -EBUSY;
+}
+
+/**
+ * sst_alloc_stream - Send msg for a new stream ID
+ *
+ * @params: stream params
+ * @stream_ops: operation of stream PB/capture
+ * @codec: codec for stream
+ * @device: device stream to be allocated for
+ *
+ * This function is called by any function which wants to start
+ * a new stream. This also check if a stream exists which is idle
+ * it initializes idle stream id to this request
+ */
+int sst_alloc_stream(char *params, unsigned int stream_ops,
+ u8 codec, unsigned int device)
+{
+ struct ipc_post *msg = NULL;
+ struct snd_sst_alloc_params alloc_param;
+ unsigned int pcm_slot = 0, num_ch, str_id;
+ struct snd_sst_stream_params *sparams;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:entering sst_alloc_stream\n");
+ pr_debug("SST DBG:%d %d %d\n", stream_ops, codec, device);
+
+ BUG_ON(!params);
+ sparams = (struct snd_sst_stream_params *)params;
+ num_ch = sparams->uc.pcm_params.num_chan;
+ /*check the device type*/
+ if (sst_drv_ctx->pci_id == SST_MFLD_PCI_ID) {
+ if (sst_check_device_type(device, num_ch, &pcm_slot))
+ return -EINVAL;
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ str_id = device;
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ pr_debug("SST_DBG: slot %x\n", pcm_slot);
+ } else {
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ str_id = get_mrst_stream_id();
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ if (str_id <= 0)
+ return -EBUSY;
+ }
+ /*allocate device type context*/
+ sst_init_stream(&sst_drv_ctx->streams[str_id], codec,
+ str_id, stream_ops, pcm_slot, device);
+ /* send msg to FW to allocate a stream */
+ if (sst_create_large_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header, IPC_IA_ALLOC_STREAM, 1, str_id);
+ msg->header.part.data = sizeof(alloc_param) + sizeof(u32);
+ alloc_param.str_type.codec_type = codec;
+ alloc_param.str_type.str_type = SST_STREAM_TYPE_MUSIC;
+ alloc_param.str_type.operation = stream_ops;
+ alloc_param.str_type.protected_str = 0; /* non drm */
+ alloc_param.str_type.time_slots = pcm_slot;
+ alloc_param.str_type.result = alloc_param.str_type.reserved = 0;
+ memcpy(&alloc_param.stream_params, params,
+ sizeof(struct snd_sst_stream_params));
+
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), &alloc_param,
+ sizeof(alloc_param));
+ str_info = &sst_drv_ctx->streams[str_id];
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ pr_debug("SST DBG:alloc stream done\n");
+ return str_id;
+}
+
+
+/*
+ * sst_alloc_stream_response - process alloc reply
+ *
+ * @str_id: stream id for which the stream has been allocated
+ * @resp the stream response from firware
+ *
+ * This function is called by firmware as a response to stream allcoation
+ * request
+ */
+int sst_alloc_stream_response(unsigned int str_id,
+ struct snd_sst_alloc_response *resp)
+{
+ int retval = 0;
+ struct stream_info *str_info;
+ struct snd_sst_lib_download *lib_dnld;
+
+ pr_debug("SST DEBUG: stream number given = %d\n", str_id);
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (resp->str_type.result == SST_LIB_ERR_LIB_DNLD_REQUIRED) {
+ lib_dnld = kzalloc(sizeof(*lib_dnld), GFP_KERNEL);
+ memcpy(lib_dnld, &resp->lib_dnld, sizeof(*lib_dnld));
+ } else
+ lib_dnld = NULL;
+ if (str_info->ctrl_blk.on == true) {
+ str_info->ctrl_blk.on = false;
+ str_info->ctrl_blk.data = lib_dnld;
+ str_info->ctrl_blk.condition = true;
+ str_info->ctrl_blk.ret_code = resp->str_type.result;
+ pr_debug("SST DEBUG: sst_alloc_stream_response: waking up.\n");
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ return retval;
+}
+
+
+/**
+* sst_get_fw_info - Send msg to query for firmware configurations
+* @info: out param that holds the firmare configurations
+*
+* This function is called when the firmware configurations are queiried for
+*/
+int sst_get_fw_info(struct snd_sst_fw_info *info)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+
+ pr_debug("SST DBG:sst_get_fw_info called\n");
+
+ if (sst_create_short_msg(&msg)) {
+ pr_err("SST ERR: message creation failed\n");
+ return -ENOMEM;
+ }
+
+ sst_fill_header(&msg->header, IPC_IA_GET_FW_INFO, 0, 0);
+ sst_drv_ctx->fw_info_blk.condition = false;
+ sst_drv_ctx->fw_info_blk.ret_code = 0;
+ sst_drv_ctx->fw_info_blk.on = true;
+ sst_drv_ctx->fw_info_blk.data = info;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->fw_info_blk, SST_BLOCK_TIMEOUT);
+ if (retval) {
+ pr_err("SST ERR: error in fw_info = %d\n", retval);
+ retval = -EIO;
+ }
+ return retval;
+}
+
+
+/**
+* sst_pause_stream - Send msg for a pausing stream
+* @str_id: stream ID
+*
+* This function is called by any function which wants to pause
+* an already running stream.
+*/
+int sst_start_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ pr_debug("sst_start_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->status != STREAM_INIT)
+ return -EBADRQC;
+ if (sst_create_short_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header, IPC_IA_START_STREAM, 0, str_id);
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ return retval;
+}
+
+/*
+ * sst_pause_stream - Send msg for a pausing stream
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to pause
+ * an already running stream.
+ */
+int sst_pause_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:sst_pause_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->status == STREAM_PAUSED)
+ return 0;
+ if (str_info->status == STREAM_RUNNING ||
+ str_info->status == STREAM_INIT) {
+ if (str_info->prev == STREAM_UN_INIT)
+ return -EBADRQC;
+ if (str_info->ctrl_blk.on == true) {
+ pr_err("SST ERR: control path is in use\n ");
+ return -EINVAL;
+ }
+ if (sst_create_short_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header, IPC_IA_PAUSE_STREAM, 0, str_id);
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node,
+ &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if (retval == 0) {
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_PAUSED;
+ } else if (retval == SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ }
+ } else {
+ retval = -EBADRQC;
+ pr_err("SST ERR:BADQRC for stream\n ");
+ }
+
+ return retval;
+}
+
+/**
+ * sst_resume_stream - Send msg for resuming stream
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to resume
+ * an already paused stream.
+ */
+int sst_resume_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:sst_resume_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->status == STREAM_RUNNING)
+ return 0;
+ if (str_info->status == STREAM_PAUSED) {
+ if (str_info->ctrl_blk.on == true) {
+ pr_err("SST ERR: control path in use\n");
+ return -EINVAL;
+ }
+ if (sst_create_short_msg(&msg)) {
+ pr_err("SST ERR: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_RESUME_STREAM, 0, str_id);
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node,
+ &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if (!retval) {
+ if (str_info->prev == STREAM_RUNNING)
+ str_info->status = STREAM_RUNNING;
+ else
+ str_info->status = STREAM_INIT;
+ str_info->prev = STREAM_PAUSED;
+ } else if (retval == -SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ }
+ } else {
+ retval = -EBADRQC;
+ pr_err("SST ERR: BADQRC for stream\n");
+ }
+
+ return retval;
+}
+
+
+/**
+ * sst_drop_stream - Send msg for stopping stream
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to stop
+ * a stream.
+ */
+int sst_drop_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct sst_stream_bufs *bufs = NULL, *_bufs;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:sst_drop_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+
+ if (str_info->status != STREAM_UN_INIT &&
+ str_info->status != STREAM_DECODE) {
+ if (str_info->ctrl_blk.on == true) {
+ pr_err("SST ERR: control path in use\n");
+ return -EINVAL;
+ }
+ if (sst_create_short_msg(&msg)) {
+ pr_err("SST ERR: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_DROP_STREAM, 0, str_id);
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node,
+ &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if (!retval) {
+ pr_debug("SST DBG:drop success\n");
+ str_info->prev = STREAM_UN_INIT;
+ str_info->status = STREAM_INIT;
+ if (str_info->src != MAD_DRV) {
+ mutex_lock(&str_info->lock);
+ list_for_each_entry_safe(bufs, _bufs,
+ &str_info->bufs, node) {
+ list_del(&bufs->node);
+ kfree(bufs);
+ }
+ mutex_unlock(&str_info->lock);
+ }
+ str_info->cumm_bytes += str_info->curr_bytes;
+ } else if (retval == -SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ }
+ if (str_info->data_blk.on == true) {
+ str_info->data_blk.condition = true;
+ str_info->data_blk.ret_code = retval;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ } else {
+ retval = -EBADRQC;
+ pr_err("SST ERR:BADQRC for stream\n");
+ }
+ return retval;
+}
+
+/**
+* sst_drain_stream - Send msg for draining stream
+* @str_id: stream ID
+*
+* This function is called by any function which wants to drain
+* a stream.
+*/
+int sst_drain_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:sst_drain_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+
+ if (str_info->status != STREAM_RUNNING &&
+ str_info->status != STREAM_INIT &&
+ str_info->status != STREAM_PAUSED) {
+ pr_err("SST ERR: BADQRC for stream = %d\n",
+ str_info->status);
+ return -EBADRQC;
+ }
+
+ if (str_info->status == STREAM_INIT) {
+ if (sst_create_short_msg(&msg)) {
+ pr_err("SST ERR: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_DRAIN_STREAM, 0, str_id);
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ } else
+ str_info->need_draining = true;
+ str_info->data_blk.condition = false;
+ str_info->data_blk.ret_code = 0;
+ str_info->data_blk.on = true;
+ retval = sst_wait_interruptible(sst_drv_ctx, &str_info->data_blk);
+ str_info->need_draining = false;
+ if (retval == -SST_ERR_INVALID_STREAM_ID) {
+ retval = -EINVAL;
+ sst_clean_stream(str_info);
+ }
+ return retval;
+}
+
+/**
+ * sst_free_stream - Frees a stream
+ * @str_id: stream ID
+ *
+ * This function is called by any function which wants to free
+ * a stream.
+ */
+int sst_free_stream(int str_id)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ pr_debug("SST DBG:sst_free_stream for %d\n", str_id);
+
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ str_info = &sst_drv_ctx->streams[str_id];
+
+ if (str_info->status != STREAM_UN_INIT) {
+ if (sst_create_short_msg(&msg)) {
+ pr_err("SST ERR: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_FREE_STREAM, 0, str_id);
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_UN_INIT;
+ if (str_info->data_blk.on == true) {
+ str_info->data_blk.condition = true;
+ str_info->data_blk.ret_code = 0;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ mutex_lock(&sst_drv_ctx->stream_lock);
+ sst_clean_stream(str_info);
+ mutex_unlock(&sst_drv_ctx->stream_lock);
+ pr_debug("SST DBG:Stream freed\n");
+ } else {
+ retval = -EBADRQC;
+ pr_debug("SST DBG:BADQRC for stream\n");
+ }
+
+ return retval;
+}
+
+
diff --git a/drivers/staging/intel_sst/intel_sst_stream_encoded.c b/drivers/staging/intel_sst/intel_sst_stream_encoded.c
new file mode 100644
index 0000000..fbae39f
--- /dev/null
+++ b/drivers/staging/intel_sst/intel_sst_stream_encoded.c
@@ -0,0 +1,1275 @@
+/*
+ * intel_sst_stream.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the stream operations of SST driver
+ */
+
+#include <linux/pci.h>
+#include <linux/syscalls.h>
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include <linux/rar_register.h>
+#ifdef CONFIG_MRST_RAR_HANDLER
+#include "../../../drivers/staging/memrar/memrar.h"
+#endif
+#include "intel_sst_ioctl.h"
+#include "intel_sst.h"
+#include "intel_sst_fw_ipc.h"
+#include "intel_sst_common.h"
+/**
+* sst_get_stream_params - Send msg to query for stream parameters
+* @str_id: stream id for which the parameters are queried for
+* @get_params: out parameters to which the parameters are copied to
+*
+* This function is called when the stream parameters are queiried for
+*/
+int sst_get_stream_params(int str_id,
+ struct snd_sst_get_stream_params *get_params)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+ struct snd_sst_fw_get_stream_params *fw_params;
+
+ pr_debug("sst: get_stream for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->status != STREAM_UN_INIT) {
+ if (str_info->ctrl_blk.on == true) {
+ pr_err("sst: control path in use\n");
+ return -EINVAL;
+ }
+ if (sst_create_short_msg(&msg)) {
+ pr_err("sst: message creation failed\n");
+ return -ENOMEM;
+ }
+ fw_params = kzalloc(sizeof(*fw_params), GFP_ATOMIC);
+ if (!fw_params) {
+ pr_err("sst: mem allcoation failed\n ");
+ kfree(msg);
+ return -ENOMEM;
+ }
+
+ sst_fill_header(&msg->header, IPC_IA_GET_STREAM_PARAMS,
+ 0, str_id);
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ str_info->ctrl_blk.data = (void *) fw_params;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if (retval) {
+ get_params->codec_params.result = retval;
+ kfree(fw_params);
+ return -EIO;
+ }
+ memcpy(&get_params->pcm_params, &fw_params->pcm_params,
+ sizeof(fw_params->pcm_params));
+ memcpy(&get_params->codec_params.sparams,
+ &fw_params->codec_params,
+ sizeof(fw_params->codec_params));
+ get_params->codec_params.result = 0;
+ get_params->codec_params.stream_id = str_id;
+ get_params->codec_params.codec = str_info->codec;
+ get_params->codec_params.ops = str_info->ops;
+ get_params->codec_params.stream_type = str_info->str_type;
+ kfree(fw_params);
+ } else {
+ pr_debug("sst: Stream is not in the init state\n");
+ }
+ return retval;
+}
+
+/**
+ * sst_set_stream_param - Send msg for setting stream parameters
+ *
+ * @str_id: stream id
+ * @str_param: stream params
+ *
+ * This function sets stream params during runtime
+ */
+int sst_set_stream_param(int str_id, struct snd_sst_params *str_param)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct stream_info *str_info;
+
+ BUG_ON(!str_param);
+ if (sst_drv_ctx->streams[str_id].ops != str_param->ops) {
+ pr_err("sst: Invalid operation\n");
+ return -EINVAL;
+ }
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ pr_debug("sst: set_stream for %d\n", str_id);
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (sst_drv_ctx->streams[str_id].status == STREAM_INIT) {
+ if (str_info->ctrl_blk.on == true) {
+ pr_err("sst: control path in use\n");
+ return -EAGAIN;
+ }
+ if (sst_create_large_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header,
+ IPC_IA_SET_STREAM_PARAMS, 1, str_id);
+ str_info->ctrl_blk.condition = false;
+ str_info->ctrl_blk.ret_code = 0;
+ str_info->ctrl_blk.on = true;
+ msg->header.part.data = sizeof(u32) +
+ sizeof(str_param->sparams);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), &str_param->sparams,
+ sizeof(str_param->sparams));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
+ if (retval < 0) {
+ retval = -EIO;
+ sst_clean_stream(str_info);
+ }
+ } else {
+ retval = -EBADRQC;
+ pr_err("sst: BADQRC for stream\n");
+ }
+ return retval;
+}
+
+/**
+* sst_get_vol - This fuction allows to get the premix gain or gain of a stream
+*
+* @get_vol: this is an output param through which the volume
+* structure is passed back to user
+*
+* This function is called when the premix gain or stream gain is queried for
+*/
+int sst_get_vol(struct snd_sst_vol *get_vol)
+{
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+ struct snd_sst_vol *fw_get_vol;
+ int str_id = get_vol->stream_id;
+
+ pr_debug("sst: get vol called\n");
+
+ if (sst_create_short_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header,
+ IPC_IA_GET_STREAM_VOL, 0, str_id);
+ sst_drv_ctx->vol_info_blk.condition = false;
+ sst_drv_ctx->vol_info_blk.ret_code = 0;
+ sst_drv_ctx->vol_info_blk.on = true;
+ fw_get_vol = kzalloc(sizeof(*fw_get_vol), GFP_ATOMIC);
+ if (!fw_get_vol) {
+ pr_err("sst: mem allocation failed\n");
+ kfree(msg);
+ return -ENOMEM;
+ }
+ sst_drv_ctx->vol_info_blk.data = (void *)fw_get_vol;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->vol_info_blk, SST_BLOCK_TIMEOUT);
+ if (retval)
+ retval = -EIO;
+ else {
+ pr_debug("sst: stream id %d\n", fw_get_vol->stream_id);
+ pr_debug("sst: volume %d\n", fw_get_vol->volume);
+ pr_debug("sst: ramp duration %d\n", fw_get_vol->ramp_duration);
+ pr_debug("sst: ramp_type %d\n", fw_get_vol->ramp_type);
+ memcpy(get_vol, fw_get_vol, sizeof(*fw_get_vol));
+ }
+ return retval;
+}
+
+/**
+* sst_set_vol - This fuction allows to set the premix gain or gain of a stream
+*
+* @set_vol: this holds the volume structure that needs to be set
+*
+* This function is called when premix gain or stream gain is requested to be set
+*/
+int sst_set_vol(struct snd_sst_vol *set_vol)
+{
+
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+
+ pr_debug("sst: set vol called\n");
+
+ if (sst_create_large_msg(&msg)) {
+ pr_err("sst: message creation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_SET_STREAM_VOL, 1,
+ set_vol->stream_id);
+
+ msg->header.part.data = sizeof(u32) + sizeof(*set_vol);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), set_vol, sizeof(*set_vol));
+ sst_drv_ctx->vol_info_blk.condition = false;
+ sst_drv_ctx->vol_info_blk.ret_code = 0;
+ sst_drv_ctx->vol_info_blk.on = true;
+ sst_drv_ctx->vol_info_blk.data = set_vol;
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->vol_info_blk, SST_BLOCK_TIMEOUT);
+ if (retval) {
+ pr_err("sst: error in set_vol = %d\n", retval);
+ retval = -EIO;
+ }
+ return retval;
+}
+
+/**
+* sst_set_mute - This fuction sets premix mute or soft mute of a stream
+*
+* @set_mute: this holds the mute structure that needs to be set
+*
+* This function is called when premix mute or stream mute requested to be set
+*/
+int sst_set_mute(struct snd_sst_mute *set_mute)
+{
+
+ int retval = 0;
+ struct ipc_post *msg = NULL;
+
+ pr_debug("sst: set mute called\n");
+
+ if (sst_create_large_msg(&msg)) {
+ pr_err("sst: message creation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_SET_STREAM_MUTE, 1,
+ set_mute->stream_id);
+ sst_drv_ctx->mute_info_blk.condition = false;
+ sst_drv_ctx->mute_info_blk.ret_code = 0;
+ sst_drv_ctx->mute_info_blk.on = true;
+ sst_drv_ctx->mute_info_blk.data = set_mute;
+
+ msg->header.part.data = sizeof(u32) + sizeof(*set_mute);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), set_mute,
+ sizeof(*set_mute));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->mute_info_blk, SST_BLOCK_TIMEOUT);
+ if (retval) {
+ pr_err("sst: error in set_mute = %d\n", retval);
+ retval = -EIO;
+ }
+ return retval;
+}
+
+int sst_prepare_target(struct snd_sst_slot_info *slot)
+{
+ if (slot->target_device == SND_SST_TARGET_PMIC
+ && slot->device_instance == 1) {
+ /*music mode*/
+ if (sst_drv_ctx->pmic_port_instance == 0)
+ sst_drv_ctx->scard_ops->set_voice_port(
+ DEACTIVATE);
+ } else if ((slot->target_device == SND_SST_TARGET_PMIC ||
+ slot->target_device == SND_SST_TARGET_MODEM) &&
+ slot->device_instance == 0) {
+ /*voip mode where pcm0 is active*/
+ if (sst_drv_ctx->pmic_port_instance == 1)
+ sst_drv_ctx->scard_ops->set_audio_port(
+ DEACTIVATE);
+ }
+ return 0;
+}
+
+int sst_activate_target(struct snd_sst_slot_info *slot)
+{
+ if (slot->target_device == SND_SST_TARGET_PMIC &&
+ slot->device_instance == 1) {
+ /*music mode*/
+ sst_drv_ctx->pmic_port_instance = 1;
+ sst_drv_ctx->scard_ops->set_audio_port(ACTIVATE);
+ sst_drv_ctx->scard_ops->set_pcm_audio_params(
+ slot->pcm_params.sfreq,
+ slot->pcm_params.pcm_wd_sz,
+ slot->pcm_params.num_chan);
+ if (sst_drv_ctx->pb_streams)
+ sst_drv_ctx->scard_ops->power_up_pmic_pb(1);
+ if (sst_drv_ctx->cp_streams)
+ sst_drv_ctx->scard_ops->power_up_pmic_cp(1);
+ } else if ((slot->target_device == SND_SST_TARGET_PMIC ||
+ slot->target_device == SND_SST_TARGET_MODEM) &&
+ slot->device_instance == 0) {
+ /*voip mode where pcm0 is active*/
+ sst_drv_ctx->pmic_port_instance = 0;
+ sst_drv_ctx->scard_ops->set_voice_port(
+ ACTIVATE);
+ sst_drv_ctx->scard_ops->power_up_pmic_pb(0);
+ /*sst_drv_ctx->scard_ops->power_up_pmic_cp(0);*/
+ }
+ return 0;
+}
+
+int sst_parse_target(struct snd_sst_slot_info *slot)
+{
+ int retval = 0;
+
+ if (slot->action == SND_SST_PORT_ACTIVATE &&
+ slot->device_type == SND_SST_DEVICE_PCM) {
+ retval = sst_activate_target(slot);
+ if (retval)
+ pr_err("sst: SST_Activate_target_fail\n");
+ else
+ pr_err("sst: SST_Activate_target_pass\n");
+ return retval;
+ } else if (slot->action == SND_SST_PORT_PREPARE &&
+ slot->device_type == SND_SST_DEVICE_PCM) {
+ retval = sst_prepare_target(slot);
+ if (retval)
+ pr_err("sst: SST_prepare_target_fail\n");
+ else
+ pr_err("sst: SST_prepare_target_pass\n");
+ return retval;
+ } else {
+ pr_err("sst: slot_action : %d, device_type: %d\n",
+ slot->action, slot->device_type);
+ return retval;
+ }
+}
+
+int sst_send_target(struct snd_sst_target_device *target)
+{
+ int retval;
+ struct ipc_post *msg;
+
+ if (sst_create_large_msg(&msg)) {
+ pr_err("sst: message creation failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_TARGET_DEV_SELECT, 1, 0);
+ sst_drv_ctx->tgt_dev_blk.condition = false;
+ sst_drv_ctx->tgt_dev_blk.ret_code = 0;
+ sst_drv_ctx->tgt_dev_blk.on = true;
+
+ msg->header.part.data = sizeof(u32) + sizeof(*target);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), target,
+ sizeof(*target));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ pr_debug("sst: message sent- waiting\n");
+ retval = sst_wait_interruptible_timeout(sst_drv_ctx,
+ &sst_drv_ctx->tgt_dev_blk, TARGET_DEV_BLOCK_TIMEOUT);
+ if (retval)
+ pr_err("sst: target device ipc failed = 0x%x\n", retval);
+ return retval;
+
+}
+
+int sst_target_device_validate(struct snd_sst_target_device *target)
+{
+ int retval = 0;
+ int i;
+
+ for (i = 0; i < SST_MAX_TARGET_DEVICES; i++) {
+ if (target->devices[i].device_type == SND_SST_DEVICE_PCM) {
+ /*pcm device, check params*/
+ if (target->devices[i].device_instance == 1) {
+ if ((target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE4_I2S) &&
+ (target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE4_RIGHT_JUSTIFIED)
+ && (target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE1))
+ goto err;
+ } else if (target->devices[i].device_instance == 0) {
+ if ((target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE2)
+ && (target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE4_I2S)
+ && (target->devices[i].device_mode !=
+ SND_SST_DEV_MODE_PCM_MODE1))
+ goto err;
+ if (target->devices[i].pcm_params.sfreq != 8000
+ || target->devices[i].pcm_params.num_chan != 1
+ || target->devices[i].pcm_params.pcm_wd_sz !=
+ 16)
+ goto err;
+ } else {
+err:
+ pr_err("sst: i/p params incorrect\n");
+ return -EINVAL;
+ }
+ }
+ }
+ return retval;
+}
+
+/**
+ * sst_target_device_select - This fuction sets the target device configurations
+ *
+ * @target: this parameter holds the configurations to be set
+ *
+ * This function is called when the user layer wants to change the target
+ * device's configurations
+ */
+
+int sst_target_device_select(struct snd_sst_target_device *target)
+{
+ int retval, i, prepare_count = 0;
+
+ pr_debug("sst: Target Device Select\n");
+
+ if (target->device_route < 0 || target->device_route > 2) {
+ pr_err("sst: device route is invalid\n");
+ return -EINVAL;
+ }
+
+ if (target->device_route != 0) {
+ pr_err("sst: Unsupported config\n");
+ return -EIO;
+ }
+ retval = sst_target_device_validate(target);
+ if (retval)
+ return retval;
+
+ retval = sst_send_target(target);
+ if (retval)
+ return retval;
+ for (i = 0; i < SST_MAX_TARGET_DEVICES; i++) {
+ if (target->devices[i].action == SND_SST_PORT_ACTIVATE) {
+ pr_debug("sst: activate called in %d\n", i);
+ retval = sst_parse_target(&target->devices[i]);
+ if (retval)
+ return retval;
+ } else if (target->devices[i].action == SND_SST_PORT_PREPARE) {
+ pr_debug("sst: PREPARE in %d, Forwading\n", i);
+ retval = sst_parse_target(&target->devices[i]);
+ if (retval) {
+ pr_err("sst: Parse Target fail %d", retval);
+ return retval;
+ }
+ pr_debug("sst: Parse Target successful %d", retval);
+ if (target->devices[i].device_type ==
+ SND_SST_DEVICE_PCM)
+ prepare_count++;
+ }
+ }
+ if (target->devices[0].action == SND_SST_PORT_PREPARE &&
+ prepare_count == 0)
+ sst_drv_ctx->scard_ops->power_down_pmic();
+
+ return retval;
+}
+#ifdef CONFIG_MRST_RAR_HANDLER
+/*This function gets the physical address of the secure memory from the handle*/
+static inline int sst_get_RAR(struct RAR_buffer *buffers, int count)
+{
+ int retval = 0, rar_status = 0;
+
+ rar_status = rar_handle_to_bus(buffers, count);
+
+ if (count != rar_status) {
+ pr_err("sst: The rar CALL Failed");
+ retval = -EIO;
+ }
+ if (buffers->info.type != RAR_TYPE_AUDIO) {
+ pr_err("sst: Invalid RAR type\n");
+ return -EINVAL;
+ }
+ return retval;
+}
+
+#endif
+
+/* This function creates the scatter gather list to be sent to firmware to
+capture/playback data*/
+static int sst_create_sg_list(struct stream_info *stream,
+ struct sst_frame_info *sg_list)
+{
+ struct sst_stream_bufs *kbufs = NULL;
+#ifdef CONFIG_MRST_RAR_HANDLER
+ struct RAR_buffer rar_buffers;
+ int retval = 0;
+#endif
+ int i = 0;
+ list_for_each_entry(kbufs, &stream->bufs, node) {
+ if (kbufs->in_use == false) {
+#ifdef CONFIG_MRST_RAR_HANDLER
+ if (stream->ops == STREAM_OPS_PLAYBACK_DRM) {
+ pr_debug("sst: DRM playback handling\n");
+ rar_buffers.info.handle = (__u32)kbufs->addr;
+ rar_buffers.info.size = kbufs->size;
+ pr_debug("sst: rar handle 0x%x size=0x%x",
+ rar_buffers.info.handle,
+ rar_buffers.info.size);
+ retval = sst_get_RAR(&rar_buffers, 1);
+
+ if (retval)
+ return retval;
+ sg_list->addr[i].addr = rar_buffers.bus_address;
+ /* rar_buffers.info.size; */
+ sg_list->addr[i].size = (__u32)kbufs->size;
+ pr_debug("sst: phyaddr[%d] 0x%x Size:0x%x\n"
+ , i, sg_list->addr[i].addr,
+ sg_list->addr[i].size);
+ }
+#endif
+ if (stream->ops != STREAM_OPS_PLAYBACK_DRM) {
+ sg_list->addr[i].addr =
+ virt_to_phys((void *)
+ kbufs->addr + kbufs->offset);
+ sg_list->addr[i].size = kbufs->size;
+ pr_debug("sst: phyaddr[%d]:0x%x Size:0x%x\n"
+ , i , sg_list->addr[i].addr, kbufs->size);
+ }
+ stream->curr_bytes += sg_list->addr[i].size;
+ kbufs->in_use = true;
+ i++;
+ }
+ if (i >= MAX_NUM_SCATTER_BUFFERS)
+ break;
+ }
+
+ sg_list->num_entries = i;
+ pr_debug("sst:sg list entries = %d\n", sg_list->num_entries);
+ return i;
+}
+
+
+/**
+ * sst_play_frame - Send msg for sending stream frames
+ *
+ * @str_id: ID of stream
+ *
+ * This function is called to send data to be played out
+ * to the firmware
+ */
+int sst_play_frame(int str_id)
+{
+ int i = 0, retval = 0;
+ struct ipc_post *msg = NULL;
+ struct sst_frame_info sg_list = {0};
+ struct sst_stream_bufs *kbufs = NULL, *_kbufs;
+ struct stream_info *stream;
+
+ pr_debug("sst: play frame for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+
+ stream = &sst_drv_ctx->streams[str_id];
+ /* clear prev sent buffers */
+ list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) {
+ if (kbufs->in_use == true) {
+ spin_lock(&stream->pcm_lock);
+ list_del(&kbufs->node);
+ spin_unlock(&stream->pcm_lock);
+ kfree(kbufs);
+ }
+ }
+ /* update bytes sent */
+ stream->cumm_bytes += stream->curr_bytes;
+ stream->curr_bytes = 0;
+ if (list_empty(&stream->bufs)) {
+ /* no user buffer available */
+ pr_debug("sst: Null buffer stream status %d\n", stream->status);
+ stream->prev = stream->status;
+ stream->status = STREAM_INIT;
+ pr_debug("sst:new stream status = %d\n", stream->status);
+ if (stream->need_draining == true) {
+ pr_debug("sst:draining stream\n");
+ if (sst_create_short_msg(&msg)) {
+ pr_err("sst: mem alloc failed\n");
+ return -ENOMEM;
+ }
+ sst_fill_header(&msg->header, IPC_IA_DRAIN_STREAM,
+ 0, str_id);
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node,
+ &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ } else if (stream->data_blk.on == true) {
+ pr_debug("sst:user list empty.. wake\n");
+ /* unblock */
+ stream->data_blk.ret_code = 0;
+ stream->data_blk.condition = true;
+ stream->data_blk.on = false;
+ wake_up(&sst_drv_ctx->wait_queue);
+ }
+ return 0;
+ }
+
+ /* create list */
+ i = sst_create_sg_list(stream, &sg_list);
+
+ /* post msg */
+ if (sst_create_large_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header, IPC_IA_PLAY_FRAMES, 1, str_id);
+ msg->header.part.data = sizeof(u32) + sizeof(sg_list);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), &sg_list, sizeof(sg_list));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ return 0;
+
+}
+
+/**
+ * sst_capture_frame - Send msg for sending stream frames
+ *
+ * @str_id: ID of stream
+ *
+ * This function is called to capture data from the firmware
+ */
+int sst_capture_frame(int str_id)
+{
+ int i = 0, retval = 0;
+ struct ipc_post *msg = NULL;
+ struct sst_frame_info sg_list = {0};
+ struct sst_stream_bufs *kbufs = NULL, *_kbufs;
+ struct stream_info *stream;
+
+
+ pr_debug("sst:capture frame for %d\n", str_id);
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+ stream = &sst_drv_ctx->streams[str_id];
+ /* clear prev sent buffers */
+ list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) {
+ if (kbufs->in_use == true) {
+ list_del(&kbufs->node);
+ kfree(kbufs);
+ pr_debug("sst:del node\n");
+ }
+ }
+ if (list_empty(&stream->bufs)) {
+ /* no user buffer available */
+ pr_debug("sst:Null buffer!!!!stream status %d\n",
+ stream->status);
+ stream->prev = stream->status;
+ stream->status = STREAM_INIT;
+ pr_debug("sst:new stream status = %d\n",
+ stream->status);
+ if (stream->data_blk.on == true) {
+ pr_debug("sst:user list empty.. wake\n");
+ /* unblock */
+ stream->data_blk.ret_code = 0;
+ stream->data_blk.condition = true;
+ stream->data_blk.on = false;
+ wake_up(&sst_drv_ctx->wait_queue);
+
+ }
+ return 0;
+ }
+ /* create new sg list */
+ i = sst_create_sg_list(stream, &sg_list);
+
+ /* post msg */
+ if (sst_create_large_msg(&msg))
+ return -ENOMEM;
+
+ sst_fill_header(&msg->header, IPC_IA_CAPT_FRAMES, 1, str_id);
+ msg->header.part.data = sizeof(u32) + sizeof(sg_list);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), &sg_list, sizeof(sg_list));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+
+
+ /*update bytes recevied*/
+ stream->cumm_bytes += stream->curr_bytes;
+ stream->curr_bytes = 0;
+
+ pr_debug("sst:Cum bytes = %d\n", stream->cumm_bytes);
+ return 0;
+}
+
+/*This function is used to calculate the minimum size of input buffers given*/
+static unsigned int calculate_min_size(struct snd_sst_buffs *bufs)
+{
+ int i, min_val = bufs->buff_entry[0].size;
+ for (i = 1 ; i < bufs->entries; i++) {
+ if (bufs->buff_entry[i].size < min_val)
+ min_val = bufs->buff_entry[i].size;
+ }
+ pr_debug("sst:min_val = %d\n", min_val);
+ return min_val;
+}
+
+static unsigned int calculate_max_size(struct snd_sst_buffs *bufs)
+{
+ int i, max_val = bufs->buff_entry[0].size;
+ for (i = 1 ; i < bufs->entries; i++) {
+ if (bufs->buff_entry[i].size > max_val)
+ max_val = bufs->buff_entry[i].size;
+ }
+ pr_debug("sst:max_val = %d\n", max_val);
+ return max_val;
+}
+
+/*This function is used to allocate input and output buffers to be sent to
+the firmware that will take encoded data and return decoded data*/
+static int sst_allocate_decode_buf(struct stream_info *str_info,
+ struct snd_sst_dbufs *dbufs,
+ unsigned int cum_input_given,
+ unsigned int cum_output_given)
+{
+#ifdef CONFIG_MRST_RAR_HANDLER
+ if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) {
+
+ if (dbufs->ibufs->type == SST_BUF_RAR &&
+ dbufs->obufs->type == SST_BUF_RAR) {
+ if (dbufs->ibufs->entries == dbufs->obufs->entries)
+ return 0;
+ else {
+ pr_err("sst: RAR entries dont match\n");
+ return -EINVAL;
+ }
+ } else
+ str_info->decode_osize = cum_output_given;
+ return 0;
+
+ }
+#endif
+ if (!str_info->decode_ibuf) {
+ pr_debug("sst:no i/p buffers, trying full size\n");
+ str_info->decode_isize = cum_input_given;
+ str_info->decode_ibuf = kzalloc(str_info->decode_isize,
+ GFP_KERNEL);
+ str_info->idecode_alloc = str_info->decode_isize;
+ }
+ if (!str_info->decode_ibuf) {
+ pr_debug("sst:buff alloc failed, try max size\n");
+ str_info->decode_isize = calculate_max_size(dbufs->ibufs);
+ str_info->decode_ibuf = kzalloc(
+ str_info->decode_isize, GFP_KERNEL);
+ str_info->idecode_alloc = str_info->decode_isize;
+ }
+ if (!str_info->decode_ibuf) {
+ pr_debug("sst:buff alloc failed, try min size\n");
+ str_info->decode_isize = calculate_min_size(dbufs->ibufs);
+ str_info->decode_ibuf = kzalloc(str_info->decode_isize,
+ GFP_KERNEL);
+ if (!str_info->decode_ibuf) {
+ pr_err("sst: mem allocation failed\n");
+ return -ENOMEM;
+ }
+ str_info->idecode_alloc = str_info->decode_isize;
+ }
+ str_info->decode_osize = cum_output_given;
+ if (str_info->decode_osize > sst_drv_ctx->mmap_len)
+ str_info->decode_osize = sst_drv_ctx->mmap_len;
+ return 0;
+}
+
+/*This function is used to send the message to firmware to decode the data*/
+static int sst_send_decode_mess(int str_id, struct stream_info *str_info,
+ struct snd_sst_decode_info *dec_info)
+{
+ struct ipc_post *msg = NULL;
+ int retval = 0;
+
+ pr_debug("SST DBGsst_set_mute:called\n");
+
+ if (str_info->decode_ibuf_type == SST_BUF_RAR) {
+#ifdef CONFIG_MRST_RAR_HANDLER
+ dec_info->frames_in.addr[0].addr =
+ (unsigned long)str_info->decode_ibuf;
+ dec_info->frames_in.addr[0].size =
+ str_info->decode_isize;
+#endif
+
+ } else {
+ dec_info->frames_in.addr[0].addr = virt_to_phys((void *)
+ str_info->decode_ibuf);
+ dec_info->frames_in.addr[0].size = str_info->decode_isize;
+ }
+
+
+ if (str_info->decode_obuf_type == SST_BUF_RAR) {
+#ifdef CONFIG_MRST_RAR_HANDLER
+ dec_info->frames_out.addr[0].addr =
+ (unsigned long)str_info->decode_obuf;
+ dec_info->frames_out.addr[0].size = str_info->decode_osize;
+#endif
+
+ } else {
+ dec_info->frames_out.addr[0].addr = virt_to_phys((void *)
+ str_info->decode_obuf) ;
+ dec_info->frames_out.addr[0].size = str_info->decode_osize;
+ }
+
+ dec_info->frames_in.num_entries = 1;
+ dec_info->frames_out.num_entries = 1;
+ dec_info->frames_in.rsrvd = 0;
+ dec_info->frames_out.rsrvd = 0;
+ dec_info->input_bytes_consumed = 0;
+ dec_info->output_bytes_produced = 0;
+ if (sst_create_large_msg(&msg)) {
+ pr_err("sst: message creation failed\n");
+ return -ENOMEM;
+ }
+
+ sst_fill_header(&msg->header, IPC_IA_DECODE_FRAMES, 1, str_id);
+ msg->header.part.data = sizeof(u32) + sizeof(*dec_info);
+ memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
+ memcpy(msg->mailbox_data + sizeof(u32), dec_info,
+ sizeof(*dec_info));
+ spin_lock(&sst_drv_ctx->list_spin_lock);
+ list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list);
+ spin_unlock(&sst_drv_ctx->list_spin_lock);
+ str_info->data_blk.condition = false;
+ str_info->data_blk.ret_code = 0;
+ str_info->data_blk.on = true;
+ str_info->data_blk.data = dec_info;
+ sst_post_message(&sst_drv_ctx->ipc_post_msg_wq);
+ retval = sst_wait_interruptible(sst_drv_ctx, &str_info->data_blk);
+ return retval;
+}
+
+static int sst_prepare_input_buffers_rar(struct stream_info *str_info,
+ struct snd_sst_dbufs *dbufs,
+ int *input_index, int *in_copied,
+ int *input_index_valid_size, int *new_entry_flag)
+{
+ int retval = 0;
+#ifdef CONFIG_MRST_RAR_HANDLER
+ int i;
+
+ if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) {
+ struct RAR_buffer rar_buffers;
+ __u32 info;
+ retval = copy_from_user((void *) &info,
+ dbufs->ibufs->buff_entry[i].buffer,
+ sizeof(__u32));
+ if (retval) {
+ pr_err("sst:cpy from user fail\n");
+ return -EAGAIN;
+ }
+ rar_buffers.info.type = dbufs->ibufs->type;
+ rar_buffers.info.size = dbufs->ibufs->buff_entry[i].size;
+ rar_buffers.info.handle = info;
+ pr_debug("rar in DnR(input buffer function)=0x%x size=0x%x",
+ rar_buffers.info.handle,
+ rar_buffers.info.size);
+ retval = sst_get_RAR(&rar_buffers, 1);
+ if (retval) {
+ pr_debug("SST ERR: RAR API failed\n");
+ return retval;
+ }
+ str_info->decode_ibuf =
+ (void *) ((unsigned long) rar_buffers.bus_address);
+ pr_debug("RAR buf addr in DnR (input buffer function)0x%lu",
+ (unsigned long) str_info->decode_ibuf);
+ pr_debug("rar in DnR decode funtion/output b_add rar =0x%lu",
+ (unsigned long) rar_buffers.bus_address);
+ *input_index = i + 1;
+ str_info->decode_isize = dbufs->ibufs->buff_entry[i].size;
+ str_info->decode_ibuf_type = dbufs->ibufs->type;
+ *in_copied = str_info->decode_isize;
+ }
+#endif
+ return retval;
+}
+/*This function is used to prepare the kernel input buffers with contents
+before sending for decode*/
+static int sst_prepare_input_buffers(struct stream_info *str_info,
+ struct snd_sst_dbufs *dbufs,
+ int *input_index, int *in_copied,
+ int *input_index_valid_size, int *new_entry_flag)
+{
+ int i, cpy_size, retval = 0;
+
+ pr_debug("sst:input_index = %d, input entries = %d\n",
+ *input_index, dbufs->ibufs->entries);
+ for (i = *input_index; i < dbufs->ibufs->entries; i++) {
+#ifdef CONFIG_MRST_RAR_HANDLER
+ retval = sst_prepare_input_buffers_rar(str_info,
+ dbufs, input_index, in_copied,
+ input_index_valid_size, new_entry_flag);
+ if (retval) {
+ pr_err("sst: In prepare input buffers for RAR\n");
+ return -EIO;
+ }
+#endif
+ *input_index = i;
+ if (*input_index_valid_size == 0)
+ *input_index_valid_size =
+ dbufs->ibufs->buff_entry[i].size;
+ pr_debug("sst:inout addr = %p, size = %d\n",
+ dbufs->ibufs->buff_entry[i].buffer,
+ *input_index_valid_size);
+ pr_debug("sst:decode_isize = %d, in_copied %d\n",
+ str_info->decode_isize, *in_copied);
+ if (*input_index_valid_size <=
+ (str_info->decode_isize - *in_copied))
+ cpy_size = *input_index_valid_size;
+ else
+ cpy_size = str_info->decode_isize - *in_copied;
+
+ pr_debug("sst:cpy size = %d\n", cpy_size);
+ if (!dbufs->ibufs->buff_entry[i].buffer) {
+ pr_err("sst: i/p buffer is null\n");
+ return -EINVAL;
+ }
+ pr_debug("sst:Try copy To %p, From %p, size %d\n",
+ str_info->decode_ibuf + *in_copied,
+ dbufs->ibufs->buff_entry[i].buffer, cpy_size);
+
+ retval =
+ copy_from_user((void *)(str_info->decode_ibuf + *in_copied),
+ (void *) dbufs->ibufs->buff_entry[i].buffer,
+ cpy_size);
+ if (retval) {
+ pr_err("sst: copy from user failed\n");
+ return -EIO;
+ }
+ *in_copied += cpy_size;
+ *input_index_valid_size -= cpy_size;
+ pr_debug("sst:in buff size = %d, in_copied = %d\n",
+ *input_index_valid_size, *in_copied);
+ if (*input_index_valid_size != 0) {
+ pr_debug("sst:more input buffers left\n");
+ dbufs->ibufs->buff_entry[i].buffer += cpy_size;
+ break;
+ }
+ if (*in_copied == str_info->decode_isize &&
+ *input_index_valid_size == 0 &&
+ (i+1) <= dbufs->ibufs->entries) {
+ pr_debug("sst:all input buffers copied\n");
+ *new_entry_flag = true;
+ *input_index = i + 1;
+ break;
+ }
+ }
+ return retval;
+}
+
+/* This function is used to copy the decoded data from kernel buffers to
+the user output buffers with contents after decode*/
+static int sst_prepare_output_buffers(struct stream_info *str_info,
+ struct snd_sst_dbufs *dbufs,
+ int *output_index, int output_size,
+ int *out_copied)
+
+{
+ int i, cpy_size, retval = 0;
+ pr_debug("sst:output_index = %d, output entries = %d\n",
+ *output_index,
+ dbufs->obufs->entries);
+ for (i = *output_index; i < dbufs->obufs->entries; i++) {
+ *output_index = i;
+ pr_debug("sst:output addr = %p, size = %d\n",
+ dbufs->obufs->buff_entry[i].buffer,
+ dbufs->obufs->buff_entry[i].size);
+ pr_debug("sst:output_size = %d, out_copied = %d\n",
+ output_size, *out_copied);
+ if (dbufs->obufs->buff_entry[i].size <
+ (output_size - *out_copied))
+ cpy_size = dbufs->obufs->buff_entry[i].size;
+ else
+ cpy_size = output_size - *out_copied;
+ pr_debug("sst:cpy size = %d\n", cpy_size);
+ pr_debug("sst:Try copy To: %p, From %p, size %d\n",
+ dbufs->obufs->buff_entry[i].buffer,
+ sst_drv_ctx->mmap_mem + *out_copied,
+ cpy_size);
+ retval = copy_to_user(dbufs->obufs->buff_entry[i].buffer,
+ sst_drv_ctx->mmap_mem + *out_copied,
+ cpy_size);
+ if (retval) {
+ pr_err("sst: copy to user failed\n");
+ return -EIO;
+ } else
+ pr_debug("sst:copy to user passed\n");
+ *out_copied += cpy_size;
+ dbufs->obufs->buff_entry[i].size -= cpy_size;
+ pr_debug("sst:o/p buff size %d, out_copied %d\n",
+ dbufs->obufs->buff_entry[i].size, *out_copied);
+ if (dbufs->obufs->buff_entry[i].size != 0) {
+ *output_index = i;
+ dbufs->obufs->buff_entry[i].buffer += cpy_size;
+ break;
+ } else if (*out_copied == output_size) {
+ *output_index = i + 1;
+ break;
+ }
+ }
+ return retval;
+}
+
+/**
+ * sst_decode - Send msg for decoding frames
+ *
+ * @str_id: ID of stream
+ * @dbufs: param that holds the user input and output buffers and size
+ *
+ * This function is called to decode data from the firmware
+ */
+int sst_decode(int str_id, struct snd_sst_dbufs *dbufs)
+{
+ int retval = 0, i;
+ unsigned long long total_input = 0 , total_output = 0;
+ unsigned int cum_input_given = 0 , cum_output_given = 0;
+ int copy_in_done = false, copy_out_done = false;
+ int input_index = 0, output_index = 0;
+ int input_index_valid_size = 0;
+ int in_copied, out_copied;
+ int new_entry_flag;
+ u64 output_size;
+ struct stream_info *str_info;
+ struct snd_sst_decode_info dec_info;
+ unsigned long long input_bytes, output_bytes;
+
+ sst_drv_ctx->scard_ops->power_down_pmic();
+ pr_debug("sst: Powering_down_PMIC...\n");
+
+ retval = sst_validate_strid(str_id);
+ if (retval)
+ return retval;
+
+ str_info = &sst_drv_ctx->streams[str_id];
+ if (str_info->status != STREAM_INIT) {
+ pr_err("sst: invalid stream state = %d\n",
+ str_info->status);
+ return -EINVAL;
+ }
+
+ str_info->prev = str_info->status;
+ str_info->status = STREAM_DECODE;
+
+ for (i = 0; i < dbufs->ibufs->entries; i++)
+ cum_input_given += dbufs->ibufs->buff_entry[i].size;
+ for (i = 0; i < dbufs->obufs->entries; i++)
+ cum_output_given += dbufs->obufs->buff_entry[i].size;
+
+ /* input and output buffer allocation */
+ retval = sst_allocate_decode_buf(str_info, dbufs,
+ cum_input_given, cum_output_given);
+ if (retval) {
+ pr_err("sst: mem allocation failed, abort!!!\n");
+ retval = -ENOMEM;
+ goto finish;
+ }
+
+ str_info->decode_isize = str_info->idecode_alloc;
+ str_info->decode_ibuf_type = dbufs->ibufs->type;
+ str_info->decode_obuf_type = dbufs->obufs->type;
+
+ while ((copy_out_done == false) && (copy_in_done == false)) {
+ in_copied = 0;
+ new_entry_flag = false;
+ retval = sst_prepare_input_buffers(str_info,\
+ dbufs, &input_index, &in_copied,
+ &input_index_valid_size, &new_entry_flag);
+ if (retval) {
+ pr_err("sst: prepare in buffers failed\n");
+ goto finish;
+ }
+
+ if (str_info->ops != STREAM_OPS_PLAYBACK_DRM)
+ str_info->decode_obuf = sst_drv_ctx->mmap_mem;
+
+#ifdef CONFIG_MRST_RAR_HANDLER
+ else {
+ if (dbufs->obufs->type == SST_BUF_RAR) {
+ struct RAR_buffer rar_buffers;
+ __u32 info;
+
+ pr_debug("DRM");
+ retval = copy_from_user((void *) &info,
+ dbufs->obufs->
+ buff_entry[output_index].buffer,
+ sizeof(__u32));
+
+ rar_buffers.info.size = dbufs->obufs->
+ buff_entry[output_index].size;
+ rar_buffers.info.handle = info;
+ retval = sst_get_RAR(&rar_buffers, 1);
+ if (retval)
+ return retval;
+
+ str_info->decode_obuf = (void *)((unsigned long)
+ rar_buffers.bus_address);
+ str_info->decode_osize = dbufs->obufs->
+ buff_entry[output_index].size;
+ str_info->decode_obuf_type = dbufs->obufs->type;
+ pr_debug("sst:DRM handling\n");
+ pr_debug("o/p_add=0x%lu Size=0x%x",
+ (unsigned long) str_info->decode_obuf,
+ str_info->decode_osize);
+ } else {
+ str_info->decode_obuf = sst_drv_ctx->mmap_mem;
+ str_info->decode_osize = dbufs->obufs->
+ buff_entry[output_index].size;
+
+ }
+ }
+#endif
+ if (str_info->ops != STREAM_OPS_PLAYBACK_DRM) {
+ if (str_info->decode_isize > in_copied) {
+ str_info->decode_isize = in_copied;
+ pr_debug("sst:i/p size = %d\n",
+ str_info->decode_isize);
+ }
+ }
+
+
+ retval = sst_send_decode_mess(str_id, str_info, &dec_info);
+ if (retval || dec_info.input_bytes_consumed == 0) {
+ pr_err(
+ "SST ERR: mess failed or no input consumed\n");
+ goto finish;
+ }
+ input_bytes = dec_info.input_bytes_consumed;
+ output_bytes = dec_info.output_bytes_produced;
+
+ pr_debug("sst:in_copied=%d, con=%lld, prod=%lld\n",
+ in_copied, input_bytes, output_bytes);
+ if (dbufs->obufs->type == SST_BUF_RAR) {
+ output_index += 1;
+ if (output_index == dbufs->obufs->entries) {
+ copy_in_done = true;
+ pr_debug("sst:all i/p cpy done\n");
+ }
+ total_output += output_bytes;
+ } else {
+ out_copied = 0;
+ output_size = output_bytes;
+ retval = sst_prepare_output_buffers(str_info, dbufs,
+ &output_index, output_size, &out_copied);
+ if (retval) {
+ pr_err("sst:prep out buff fail\n");
+ goto finish;
+ }
+ if (str_info->ops != STREAM_OPS_PLAYBACK_DRM) {
+ if (in_copied != input_bytes) {
+ int bytes_left = in_copied -
+ input_bytes;
+ pr_debug("sst:bytes %d\n",
+ bytes_left);
+ if (new_entry_flag == true)
+ input_index--;
+ while (bytes_left) {
+ struct snd_sst_buffs *ibufs;
+ struct snd_sst_buff_entry
+ *buff_entry;
+ unsigned int size_sent;
+
+ ibufs = dbufs->ibufs;
+ buff_entry =
+ &ibufs->buff_entry[input_index];
+ size_sent = buff_entry->size -\
+ input_index_valid_size;
+ if (bytes_left == size_sent) {
+ bytes_left = 0;
+ } else if (bytes_left <
+ size_sent) {
+ buff_entry->buffer +=
+ (size_sent -
+ bytes_left);
+ buff_entry->size -=
+ (size_sent -
+ bytes_left);
+ bytes_left = 0;
+ } else {
+ bytes_left -= size_sent;
+ input_index--;
+ input_index_valid_size =
+ 0;
+ }
+ }
+
+ }
+ }
+
+ total_output += out_copied;
+ if (str_info->decode_osize != out_copied) {
+ str_info->decode_osize -= out_copied;
+ pr_debug("sst:output size modified = %d\n",
+ str_info->decode_osize);
+ }
+ }
+ total_input += input_bytes;
+
+ if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) {
+ if (total_input == cum_input_given)
+ copy_in_done = true;
+ copy_out_done = true;
+
+ } else {
+ if (total_output == cum_output_given) {
+ copy_out_done = true;
+ pr_debug("sst:all o/p cpy done\n");
+ }
+
+ if (total_input == cum_input_given) {
+ copy_in_done = true;
+ pr_debug("sst:all i/p cpy done\n");
+ }
+ }
+
+ pr_debug("sst:copy_out = %d, copy_in = %d\n",
+ copy_out_done, copy_in_done);
+ }
+
+finish:
+ dbufs->input_bytes_consumed = total_input;
+ dbufs->output_bytes_produced = total_output;
+ str_info->status = str_info->prev;
+ str_info->prev = STREAM_DECODE;
+ str_info->decode_ibuf = NULL;
+ kfree(str_info->decode_ibuf);
+ return retval;
+}
diff --git a/drivers/staging/intel_sst/intelmid.c b/drivers/staging/intel_sst/intelmid.c
new file mode 100644
index 0000000..63138b3
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid.c
@@ -0,0 +1,1233 @@
+/*
+ * intelmid.c - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Harsha Priya <priya.harsha(a)intel.com>
+ * Vinod Koul <vinod.koul(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID sound card chipset
+ */
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <sound/control.h>
+#include <asm/mrst.h>
+#include <sound/pcm.h>
+#include "jack.h"
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intelmid_snd_control.h"
+#include "intelmid.h"
+
+MODULE_AUTHOR("Vinod Koul <vinod.koul(a)intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha(a)intel.com>");
+MODULE_AUTHOR("Dharageswari R <dharageswari.r(a)intel.com>");
+MODULE_AUTHOR("KP Jeeja <jeeja.kp(a)intel.com>");
+MODULE_DESCRIPTION("Intel MAD Sound card driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{Intel,Intel_MAD}");
+
+
+static int card_index = SNDRV_DEFAULT_IDX1;/* Index 0-MAX */
+static char *card_id = SNDRV_DEFAULT_STR1; /* ID for this card */
+
+module_param(card_index, int, 0444);
+MODULE_PARM_DESC(card_index, "Index value for INTELMAD soundcard.");
+module_param(card_id, charp, 0444);
+MODULE_PARM_DESC(card_id, "ID string for INTELMAD soundcard.");
+
+int sst_card_vendor_id;
+int intelmid_audio_interrupt_enable;/*checkpatch fix*/
+
+/* Data path functionalities */
+static struct snd_pcm_hardware snd_intelmad_stream = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP|
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 |
+ SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 |
+ SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32),
+ .rates = (SNDRV_PCM_RATE_8000|
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000),
+ .rate_min = MIN_RATE,
+
+ .rate_max = MAX_RATE,
+ .channels_min = MIN_CHANNEL,
+ .channels_max = MAX_CHANNEL_AMIC,
+ .buffer_bytes_max = MAX_BUFFER,
+ .period_bytes_min = MIN_PERIOD_BYTES,
+ .period_bytes_max = MAX_PERIOD_BYTES,
+ .periods_min = MIN_PERIODS,
+ .periods_max = MAX_PERIODS,
+ .fifo_size = FIFO_SIZE,
+};
+
+
+/**
+ * snd_intelmad_pcm_trigger - stream activities are handled here
+ *
+ * @substream:substream for which the stream function is called
+ * @cmd:the stream commamd that requested from upper layer
+ *
+ * This function is called whenever an a stream activity is invoked
+ */
+static int snd_intelmad_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int ret_val = 0;
+ struct snd_intelmad *intelmaddata;
+ struct mad_stream_pvt *stream;
+ /*struct stream_buffer buffer_to_sst;*/
+
+
+
+ WARN_ON(!substream);
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ stream = substream->runtime->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+ WARN_ON(!intelmaddata->sstdrv_ops->scard_ops);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ pr_debug("sst: Trigger Start\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_START,
+ &stream->stream_info.str_id);
+ if (ret_val)
+ return ret_val;
+ stream->stream_status = RUNNING;
+ stream->substream = substream;
+ stream->stream_status = RUNNING;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pr_debug("sst: in stop\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_DROP,
+ &stream->stream_info.str_id);
+ if (ret_val)
+ return ret_val;
+ stream->stream_status = DROPPED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ pr_debug("sst: in pause\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_PAUSE,
+ &stream->stream_info.str_id);
+ if (ret_val)
+ return ret_val;
+ stream->stream_status = PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pr_debug("sst: in pause release\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_RESUME,
+ &stream->stream_info.str_id);
+ if (ret_val)
+ return ret_val;
+ stream->stream_status = RUNNING;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_pcm_prepare- internal preparation before starting a stream
+*
+* @substream: substream for which the function is called
+*
+* This function is called when a stream is started for internal preparation.
+*/
+static int snd_intelmad_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct mad_stream_pvt *stream;
+ int ret_val = 0;
+ struct snd_intelmad *intelmaddata;
+
+ pr_debug("sst: pcm_prepare called\n");
+
+ WARN_ON(!substream);
+ stream = substream->runtime->private_data;
+ intelmaddata = snd_pcm_substream_chip(substream);
+ pr_debug("sst: pb cnt = %d cap cnt = %d\n",\
+ intelmaddata->playback_cnt,
+ intelmaddata->capture_cnt);
+
+ if (stream->stream_info.str_id) {
+ pr_debug("sst: Prepare called for already set stream\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_DROP,
+ &stream->stream_info.str_id);
+ return ret_val;
+ }
+
+ ret_val = snd_intelmad_alloc_stream(substream);
+ if (ret_val < 0)
+ return ret_val;
+ stream->dbg_cum_bytes = 0;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ intelmaddata->playback_cnt++;
+ else
+ intelmaddata->capture_cnt++;
+ /* return back the stream id */
+ snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+ "%d", stream->stream_info.str_id);
+ pr_debug("sst: stream id to user = %s\n",
+ substream->pcm->id);
+
+ ret_val = snd_intelmad_init_stream(substream);
+ if (ret_val)
+ return ret_val;
+ substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER;
+ return ret_val;
+}
+
+static int snd_intelmad_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int ret_val;
+
+ pr_debug("sst: snd_intelmad_hw_params called\n");
+ ret_val = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ memset(substream->runtime->dma_area, 0,
+ params_buffer_bytes(hw_params));
+
+ return ret_val;
+}
+
+static int snd_intelmad_hw_free(struct snd_pcm_substream *substream)
+{
+ pr_debug("sst: snd_intelmad_hw_free called\n");
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/**
+ * snd_intelmad_pcm_pointer- to send the current buffer pointer processed by hw
+ *
+ * @substream: substream for which the function is called
+ *
+ * This function is called by ALSA framework to get the current hw buffer ptr
+ * when a period is elapsed
+ */
+static snd_pcm_uframes_t snd_intelmad_pcm_pointer
+ (struct snd_pcm_substream *substream)
+{
+ /* struct snd_pcm_runtime *runtime = substream->runtime; */
+ struct mad_stream_pvt *stream;
+ struct snd_intelmad *intelmaddata;
+ int ret_val;
+
+ WARN_ON(!substream);
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ stream = substream->runtime->private_data;
+ if (stream->stream_status == INIT)
+ return 0;
+
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_BUFFER_POINTER,
+ &stream->stream_info);
+ if (ret_val) {
+ pr_err("sst: error code = 0x%x\n", ret_val);
+ return ret_val;
+ }
+ pr_debug("sst: samples reported out 0x%llx\n",
+ stream->stream_info.buffer_ptr);
+ pr_debug("sst: Frame bits:: %d period_count :: %d\n",
+ (int)substream->runtime->frame_bits,
+ (int)substream->runtime->period_size);
+
+ return stream->stream_info.buffer_ptr;
+
+}
+
+/**
+ * snd_intelmad_close- to free parameteres when stream is stopped
+ *
+ * @substream: substream for which the function is called
+ *
+ * This function is called by ALSA framework when stream is stopped
+ */
+static int snd_intelmad_close(struct snd_pcm_substream *substream)
+{
+ struct snd_intelmad *intelmaddata;
+ struct mad_stream_pvt *stream;
+ int ret_val = 0;
+
+ WARN_ON(!substream);
+
+ stream = substream->runtime->private_data;
+
+ pr_debug("sst: snd_intelmad_close called\n");
+ intelmaddata = snd_pcm_substream_chip(substream);
+
+ pr_debug("sst: str id = %d\n", stream->stream_info.str_id);
+ if (stream->stream_info.str_id) {
+ /* SST API to actually stop/free the stream */
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_FREE,
+ &stream->stream_info.str_id);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ intelmaddata->playback_cnt--;
+ else
+ intelmaddata->capture_cnt--;
+ }
+ pr_debug("sst: snd_intelmad_close : pb cnt = %d cap cnt = %d\n",
+ intelmaddata->playback_cnt, intelmaddata->capture_cnt);
+ kfree(substream->runtime->private_data);
+ return ret_val;
+}
+
+/**
+ * snd_intelmad_open- to set runtime parameters during stream start
+ *
+ * @substream: substream for which the function is called
+ * @type: audio device type
+ *
+ * This function is called by ALSA framework when stream is started
+ */
+static int snd_intelmad_open(struct snd_pcm_substream *substream,
+ enum snd_sst_audio_device_type type)
+{
+ struct snd_intelmad *intelmaddata;
+ struct snd_pcm_runtime *runtime;
+ struct mad_stream_pvt *stream;
+
+ WARN_ON(!substream);
+
+ pr_debug("sst: snd_intelmad_open called\n");
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+ /* set the runtime hw parameter with local snd_pcm_hardware struct */
+ runtime->hw = snd_intelmad_stream;
+ if (intelmaddata->cpu_id == CPU_CHIP_PENWELL) {
+ runtime->hw = snd_intelmad_stream;
+ runtime->hw.rates = SNDRV_PCM_RATE_48000;
+ runtime->hw.rate_min = MAX_RATE;
+ runtime->hw.formats = (SNDRV_PCM_FMTBIT_S24 |
+ SNDRV_PCM_FMTBIT_U24);
+ if (intelmaddata->sstdrv_ops->scard_ops->input_dev_id == AMIC)
+ runtime->hw.channels_max = MAX_CHANNEL_AMIC;
+ else
+ runtime->hw.channels_max = MAX_CHANNEL_DMIC;
+
+ }
+ /* setup the internal datastruture stream pointers based on it being
+ playback or capture stream */
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+ stream->stream_info.str_id = 0;
+ stream->device = type;
+ stream->stream_status = INIT;
+ runtime->private_data = stream;
+ return snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int snd_intelmad_headset_open(struct snd_pcm_substream *substream)
+{
+ return snd_intelmad_open(substream, SND_SST_DEVICE_HEADSET);
+}
+
+static int snd_intelmad_ihf_open(struct snd_pcm_substream *substream)
+{
+ return snd_intelmad_open(substream, SND_SST_DEVICE_IHF);
+}
+
+static int snd_intelmad_vibra_open(struct snd_pcm_substream *substream)
+{
+ return snd_intelmad_open(substream, SND_SST_DEVICE_VIBRA);
+}
+
+static int snd_intelmad_haptic_open(struct snd_pcm_substream *substream)
+{
+ return snd_intelmad_open(substream, SND_SST_DEVICE_HAPTIC);
+}
+
+static struct snd_pcm_ops snd_intelmad_headset_ops = {
+ .open = snd_intelmad_headset_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intelmad_ihf_ops = {
+ .open = snd_intelmad_ihf_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intelmad_vibra_ops = {
+ .open = snd_intelmad_vibra_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intelmad_haptic_ops = {
+ .open = snd_intelmad_haptic_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intelmad_capture_ops = {
+ .open = snd_intelmad_headset_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+};
+
+
+/**
+ * snd_intelmad_intr_handler- interrupt handler
+ *
+ * @irq : irq number of the interrupt received
+ * @dev: device context
+ *
+ * This function is called when an interrupt is raised at the sound card
+ */
+static irqreturn_t snd_intelmad_intr_handler(int irq, void *dev)
+{
+ struct snd_intelmad *intelmaddata =
+ (struct snd_intelmad *)dev;
+ u8 intsts;
+
+ memcpy_fromio(&intsts,
+ ((void *)(intelmaddata->int_base)),
+ sizeof(u8));
+ intelmaddata->mad_jack_msg.intsts = intsts;
+ intelmaddata->mad_jack_msg.intelmaddata = intelmaddata;
+
+ queue_work(intelmaddata->mad_jack_wq, &intelmaddata->mad_jack_msg.wq);
+
+ return IRQ_HANDLED;
+}
+
+void sst_mad_send_jack_report(struct snd_jack *jack,
+ int buttonpressevent , int status)
+{
+
+ if (!jack) {
+ pr_debug("sst: MAD error jack empty\n");
+
+ } else {
+ pr_debug("sst: MAD send jack report for = %d!!!\n", status);
+ pr_debug("sst: MAD send jack report %d\n", jack->type);
+ snd_jack_report(jack, status);
+
+ /*button pressed and released */
+ if (buttonpressevent)
+ snd_jack_report(jack, 0);
+ pr_debug("sst: MAD sending jack report Done !!!\n");
+ }
+
+
+
+}
+
+void sst_mad_jackdetection_fs(u8 intsts , struct snd_intelmad *intelmaddata)
+{
+ struct snd_jack *jack = NULL;
+ unsigned int present = 0, jack_event_flag = 0, buttonpressflag = 0;
+ struct sc_reg_access sc_access[] = {
+ {0x187, 0x00, MASK7},
+ {0x188, 0x10, MASK4},
+ {0x18b, 0x10, MASK4},
+ };
+
+ struct sc_reg_access sc_access_write[] = {
+ {0x198, 0x00, 0x0},
+ };
+
+ if (intsts & 0x4) {
+
+ if (!(intelmid_audio_interrupt_enable)) {
+ pr_debug("sst: Audio interrupt enable\n");
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 3);
+
+ sst_sc_reg_access(sc_access_write, PMIC_WRITE, 1);
+ intelmid_audio_interrupt_enable = 1;
+ intelmaddata->jack[0].jack_status = 0;
+ intelmaddata->jack[1].jack_status = 0;
+
+ }
+ /* send headphone detect */
+ pr_debug("sst: MAD headphone %d\n", intsts & 0x4);
+ jack = &intelmaddata->jack[0].jack;
+ present = !(intelmaddata->jack[0].jack_status);
+ intelmaddata->jack[0].jack_status = present;
+ jack_event_flag = 1;
+
+ }
+
+ if (intsts & 0x2) {
+ /* send short push */
+ pr_debug("sst: MAD short push %d\n", intsts & 0x2);
+ jack = &intelmaddata->jack[2].jack;
+ present = 1;
+ jack_event_flag = 1;
+ buttonpressflag = 1;
+ }
+ if (intsts & 0x1) {
+ /* send long push */
+ pr_debug("sst: MAD long push %d\n", intsts & 0x1);
+ jack = &intelmaddata->jack[3].jack;
+ present = 1;
+ jack_event_flag = 1;
+ buttonpressflag = 1;
+ }
+ if (intsts & 0x8) {
+ if (!(intelmid_audio_interrupt_enable)) {
+ pr_debug("sst: Audio interrupt enable\n");
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 3);
+
+ sst_sc_reg_access(sc_access_write, PMIC_WRITE, 1);
+ intelmid_audio_interrupt_enable = 1;
+ intelmaddata->jack[0].jack_status = 0;
+ intelmaddata->jack[1].jack_status = 0;
+ }
+ /* send headset detect */
+ pr_debug("sst: MAD headset = %d\n", intsts & 0x8);
+ jack = &intelmaddata->jack[1].jack;
+ present = !(intelmaddata->jack[1].jack_status);
+ intelmaddata->jack[1].jack_status = present;
+ jack_event_flag = 1;
+ }
+
+ if (jack_event_flag)
+ sst_mad_send_jack_report(jack, buttonpressflag, present);
+}
+
+
+void sst_mad_jackdetection_mx(u8 intsts, struct snd_intelmad *intelmaddata)
+{
+ u8 value = 0, jack_prev_state = 0;
+ struct snd_jack *jack = NULL;
+ unsigned int present = 0, jack_event_flag = 0, buttonpressflag = 0;
+ time_t timediff;
+ struct sc_reg_access sc_access_read = {0,};
+ struct snd_pmic_ops *scard_ops;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ pr_debug("sst: previous value: %x\n", intelmaddata->jack_prev_state);
+
+ if (!(intelmid_audio_interrupt_enable)) {
+ pr_debug("sst: Audio interrupt enable\n");
+ intelmaddata->jack_prev_state = 0xC0;
+ intelmid_audio_interrupt_enable = 1;
+ }
+
+ if (intsts & 0x2) {
+ jack_prev_state = intelmaddata->jack_prev_state;
+ if (intelmaddata->pmic_status == PMIC_INIT) {
+ sc_access_read.reg_addr = 0x201;
+ sst_sc_reg_access(&sc_access_read, PMIC_READ, 1);
+ value = (sc_access_read.value);
+ pr_debug("sst: value returned = 0x%x\n", value);
+ }
+
+ if (jack_prev_state == 0xc0 && value == 0x40) {
+ /*headset detected. */
+ pr_debug("sst: MAD headset inserted\n");
+ jack = &intelmaddata->jack[1].jack;
+ present = 1;
+ jack_event_flag = 1;
+ intelmaddata->jack[1].jack_status = 1;
+
+ }
+
+ if (jack_prev_state == 0xc0 && value == 0x00) {
+ /* headphone detected. */
+ pr_debug("sst: MAD headphone inserted\n");
+ jack = &intelmaddata->jack[0].jack;
+ present = 1;
+ jack_event_flag = 1;
+
+ }
+
+ if (jack_prev_state == 0x40 && value == 0xc0) {
+ /*headset removed*/
+ pr_debug("sst: Jack headset status %d\n",
+ intelmaddata->jack[1].jack_status);
+ pr_debug("sst: MAD headset removed\n");
+ jack = &intelmaddata->jack[1].jack;
+ present = 0;
+ jack_event_flag = 1;
+ intelmaddata->jack[1].jack_status = 0;
+ }
+
+ if (jack_prev_state == 0x00 && value == 0xc0) {
+ /* headphone detected. */
+ pr_debug("sst: Jack headphone status %d\n",
+ intelmaddata->jack[0].jack_status);
+ pr_debug("sst: headphone removed\n");
+ jack = &intelmaddata->jack[0].jack;
+ present = 0;
+ jack_event_flag = 1;
+ }
+
+ if (jack_prev_state == 0x40 && value == 0x00) {
+ /*button pressed*/
+ do_gettimeofday(&intelmaddata->jack[1].buttonpressed);
+ pr_debug("sst: MAD button press detected n");
+ }
+
+
+ if (jack_prev_state == 0x00 && value == 0x40) {
+ if (intelmaddata->jack[1].jack_status) {
+ /*button pressed*/
+ do_gettimeofday(
+ &intelmaddata->jack[1].buttonreleased);
+ /*button pressed */
+ pr_debug("sst: Button Released detected\n");
+ timediff = intelmaddata->jack[1].
+ buttonreleased.tv_sec - intelmaddata->
+ jack[1].buttonpressed.tv_sec;
+ buttonpressflag = 1;
+ if (timediff > 1) {
+ pr_debug("sst: long press detected\n");
+ /* send headphone detect/undetect */
+ jack = &intelmaddata->jack[3].jack;
+ present = 1;
+ jack_event_flag = 1;
+ } else {
+ pr_debug("sst: short press detected\n");
+ /* send headphone detect/undetect */
+ jack = &intelmaddata->jack[2].jack;
+ present = 1;
+ jack_event_flag = 1;
+ }
+ }
+
+ }
+ intelmaddata->jack_prev_state = value ;
+
+ }
+ if (is_aava() && jack) {
+ if (present) {
+ pr_debug("sst: Jack... YES\n");
+ scard_ops->set_output_dev(STEREO_HEADPHONE);
+
+ } else {
+ pr_debug("sst: Jack... NO\n");
+ scard_ops->set_output_dev(INTERNAL_SPKR);
+
+ }
+ }
+
+ if (jack_event_flag)
+ sst_mad_send_jack_report(jack, buttonpressflag, present);
+}
+
+
+void sst_mad_jackdetection_nec(u8 intsts, struct snd_intelmad *intelmaddata)
+{
+ u8 value = 0;
+ struct snd_jack *jack = NULL;
+ unsigned int present = 0, jack_event_flag = 0, buttonpressflag = 0;
+ struct sc_reg_access sc_access_read = {0,};
+
+ if (intelmaddata->pmic_status == PMIC_INIT) {
+ sc_access_read.reg_addr = 0x132;
+ sst_sc_reg_access(&sc_access_read, PMIC_READ, 1);
+ value = (sc_access_read.value);
+ pr_debug("sst: value returned = 0x%x\n", value);
+ }
+ if (intsts & 0x1) {
+ pr_debug("sst: headset detected\n");
+ /* send headset detect/undetect */
+ jack = &intelmaddata->jack[1].jack;
+ present = (value == 0x1) ? 1 : 0;
+ jack_event_flag = 1;
+ }
+ if (intsts & 0x2) {
+ pr_debug("sst: headphone detected\n");
+ /* send headphone detect/undetect */
+ jack = &intelmaddata->jack[0].jack;
+ present = (value == 0x2) ? 1 : 0;
+ jack_event_flag = 1;
+ }
+ if (intsts & 0x4) {
+ pr_debug("sst: short push detected\n");
+ /* send short push */
+ jack = &intelmaddata->jack[2].jack;
+ present = 1;
+ jack_event_flag = 1;
+ buttonpressflag = 1;
+ }
+ if (intsts & 0x8) {
+ pr_debug("sst: long push detected\n");
+ /* send long push */
+ jack = &intelmaddata->jack[3].jack;
+ present = 1;
+ jack_event_flag = 1;
+ buttonpressflag = 1;
+ }
+
+ if (jack_event_flag)
+ sst_mad_send_jack_report(jack, buttonpressflag, present);
+
+
+}
+
+void sst_process_mad_jack_detection(struct work_struct *work)
+{
+ u8 intsts;
+ struct mad_jack_msg_wq *mad_jack_detect =
+ container_of(work, struct mad_jack_msg_wq, wq);
+
+ struct snd_intelmad *intelmaddata =
+ mad_jack_detect->intelmaddata;
+
+ intsts = mad_jack_detect->intsts;
+
+ switch (intelmaddata->sstdrv_ops->vendor_id) {
+ case SND_FS:
+ sst_mad_jackdetection_fs(intsts , intelmaddata);
+ break;
+ case SND_MX:
+ sst_mad_jackdetection_mx(intsts , intelmaddata);
+ break;
+ case SND_NC:
+ sst_mad_jackdetection_nec(intsts , intelmaddata);
+ break;
+ }
+}
+
+
+static int __devinit snd_intelmad_register_irq(
+ struct snd_intelmad *intelmaddata)
+{
+ int ret_val;
+ u32 regbase = AUDINT_BASE, regsize = 8;
+ char *drv_name;
+
+ pr_debug("sst: irq reg done, regbase 0x%x, regsize 0x%x\n",
+ regbase, regsize);
+ intelmaddata->int_base = ioremap_nocache(regbase, regsize);
+ if (!intelmaddata->int_base)
+ pr_err("sst: Mapping of cache failed\n");
+ pr_debug("sst: irq = 0x%x\n", intelmaddata->irq);
+ if (intelmaddata->cpu_id == CPU_CHIP_PENWELL)
+ drv_name = DRIVER_NAME_MFLD;
+ else
+ drv_name = DRIVER_NAME_MRST;
+ ret_val = request_irq(intelmaddata->irq,
+ snd_intelmad_intr_handler,
+ IRQF_SHARED, drv_name,
+ intelmaddata);
+ if (ret_val)
+ pr_err("sst: cannot register IRQ\n");
+ return ret_val;
+}
+
+static int __devinit snd_intelmad_sst_register(
+ struct snd_intelmad *intelmaddata)
+{
+ int ret_val = 0;
+ struct snd_pmic_ops *intelmad_vendor_ops[MAX_VENDORS] = {
+ &snd_pmic_ops_fs,
+ &snd_pmic_ops_mx,
+ &snd_pmic_ops_nc,
+ &snd_msic_ops
+ };
+
+ struct sc_reg_access vendor_addr = {0x00, 0x00, 0x00};
+
+ if (intelmaddata->cpu_id == CPU_CHIP_LINCROFT) {
+ ret_val = sst_sc_reg_access(&vendor_addr, PMIC_READ, 1);
+ if (ret_val)
+ return ret_val;
+ sst_card_vendor_id = (vendor_addr.value & (MASK2|MASK1|MASK0));
+ pr_debug("sst: orginal n extrated vendor id = 0x%x %d\n",
+ vendor_addr.value, sst_card_vendor_id);
+ if (sst_card_vendor_id < 0 || sst_card_vendor_id > 2) {
+ pr_err("sst: vendor card not supported!!\n");
+ return -EIO;
+ }
+ } else
+ sst_card_vendor_id = 0x3;
+
+ intelmaddata->sstdrv_ops->module_name = SST_CARD_NAMES;
+ intelmaddata->sstdrv_ops->vendor_id = sst_card_vendor_id;
+ BUG_ON(!intelmad_vendor_ops[sst_card_vendor_id]);
+ intelmaddata->sstdrv_ops->scard_ops =
+ intelmad_vendor_ops[sst_card_vendor_id];
+
+ if (intelmaddata->cpu_id == CPU_CHIP_PENWELL) {
+ intelmaddata->sstdrv_ops->scard_ops->pb_on = 0;
+ intelmaddata->sstdrv_ops->scard_ops->cap_on = 0;
+ intelmaddata->sstdrv_ops->scard_ops->input_dev_id = DMIC;
+ intelmaddata->sstdrv_ops->scard_ops->output_dev_id =
+ STEREO_HEADPHONE;
+ }
+
+ /* registering with SST driver to get access to SST APIs to use */
+ ret_val = register_sst_card(intelmaddata->sstdrv_ops);
+ if (ret_val) {
+ pr_err("sst: sst card registration failed\n");
+ return ret_val;
+ }
+
+ sst_card_vendor_id = intelmaddata->sstdrv_ops->vendor_id;
+ intelmaddata->pmic_status = PMIC_UNINIT;
+ return ret_val;
+}
+
+/* Driver Init/exit functionalities */
+/**
+ * snd_intelmad_pcm_new - to setup pcm for the card
+ *
+ * @card: pointer to the sound card structure
+ * @intelmaddata: pointer to internal context
+ * @pb: playback count for this card
+ * @cap: capture count for this card
+ * @index: device index
+ *
+ * This function is called from probe function to set up pcm params
+ * and functions
+ */
+static int __devinit snd_intelmad_pcm_new(struct snd_card *card,
+ struct snd_intelmad *intelmaddata,
+ unsigned int pb, unsigned int cap, unsigned int index)
+{
+ int ret_val = 0;
+ struct snd_pcm *pcm;
+ char name[32] = INTEL_MAD;
+ struct snd_pcm_ops *pb_ops = NULL, *cap_ops = NULL;
+
+ pr_debug("sst: called for pb %d, cp %d, idx %d\n", pb, cap, index);
+ ret_val = snd_pcm_new(card, name, index, pb, cap, &pcm);
+ if (ret_val)
+ return ret_val;
+ /* setup the ops for playback and capture streams */
+ switch (index) {
+ case 0:
+ pb_ops = &snd_intelmad_headset_ops;
+ cap_ops = &snd_intelmad_capture_ops;
+ break;
+ case 1:
+ pb_ops = &snd_intelmad_ihf_ops;
+ cap_ops = &snd_intelmad_capture_ops;
+ break;
+ case 2:
+ pb_ops = &snd_intelmad_vibra_ops;
+ cap_ops = &snd_intelmad_capture_ops;
+ break;
+ case 3:
+ pb_ops = &snd_intelmad_haptic_ops;
+ cap_ops = &snd_intelmad_capture_ops;
+ break;
+ }
+ if (pb)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, pb_ops);
+ if (cap)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, cap_ops);
+ /* setup private data which can be retrieved when required */
+ pcm->private_data = intelmaddata;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, card->shortname, strlen(card->shortname));
+ /* allocate dma pages for ALSA stream operations */
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ MIN_BUFFER, MAX_BUFFER);
+ return ret_val;
+}
+
+static int __devinit snd_intelmad_pcm(struct snd_card *card,
+ struct snd_intelmad *intelmaddata)
+{
+ int ret_val = 0;
+
+ WARN_ON(!card);
+ WARN_ON(!intelmaddata);
+ pr_debug("sst: snd_intelmad_pcm called\n");
+ ret_val = snd_intelmad_pcm_new(card, intelmaddata, 1, 1, 0);
+ if (intelmaddata->cpu_id == CPU_CHIP_LINCROFT)
+ return ret_val;
+ ret_val = snd_intelmad_pcm_new(card, intelmaddata, 1, 0, 1);
+ if (ret_val)
+ return ret_val;
+ ret_val = snd_intelmad_pcm_new(card, intelmaddata, 1, 0, 2);
+ if (ret_val)
+ return ret_val;
+ return snd_intelmad_pcm_new(card, intelmaddata, 1, 0, 3);
+}
+
+/**
+ * snd_intelmad_jack- to setup jack settings of the card
+ *
+ * @intelmaddata: pointer to internal context
+ *
+ * This function is called send jack events
+ */
+static int snd_intelmad_jack(struct snd_intelmad *intelmaddata)
+{
+ struct snd_jack *jack;
+ int retval;
+
+ pr_debug("sst: snd_intelmad_jack called\n");
+ jack = &intelmaddata->jack[0].jack;
+ retval = snd_jack_new(intelmaddata->card, "Headphone",
+ SND_JACK_HEADPHONE, &jack);
+ if (retval < 0)
+ return retval;
+ snd_jack_report(jack, 0);
+
+ jack->private_data = jack;
+ intelmaddata->jack[0].jack = *jack;
+
+
+ jack = &intelmaddata->jack[1].jack;
+ retval = snd_jack_new(intelmaddata->card, "Headset",
+ SND_JACK_HEADSET, &jack);
+ if (retval < 0)
+ return retval;
+
+
+
+ jack->private_data = jack;
+ intelmaddata->jack[1].jack = *jack;
+
+
+ jack = &intelmaddata->jack[2].jack;
+ retval = snd_jack_new(intelmaddata->card, "Short Press",
+ SND_JACK_HS_SHORT_PRESS, &jack);
+ if (retval < 0)
+ return retval;
+
+
+ jack->private_data = jack;
+ intelmaddata->jack[2].jack = *jack;
+
+
+ jack = &intelmaddata->jack[3].jack;
+ retval = snd_jack_new(intelmaddata->card, "Long Press",
+ SND_JACK_HS_LONG_PRESS, &jack);
+ if (retval < 0)
+ return retval;
+
+
+ jack->private_data = jack;
+ intelmaddata->jack[3].jack = *jack;
+
+ return retval;
+}
+
+/**
+ * snd_intelmad_mixer- to setup mixer settings of the card
+ *
+ * @intelmaddata: pointer to internal context
+ *
+ * This function is called from probe function to set up mixer controls
+ */
+static int __devinit snd_intelmad_mixer(struct snd_intelmad *intelmaddata)
+{
+ struct snd_card *card;
+ unsigned int idx;
+ int ret_val = 0, max_controls = 0;
+ char *mixername = "IntelMAD Controls";
+ struct snd_kcontrol_new *controls;
+
+ WARN_ON(!intelmaddata);
+
+ card = intelmaddata->card;
+ strncpy(card->mixername, mixername, sizeof(card->mixername)-1);
+ /* add all widget controls and expose the same */
+ if (intelmaddata->cpu_id == CPU_CHIP_PENWELL) {
+ max_controls = MAX_CTRL_MFLD;
+ controls = snd_intelmad_controls_mfld;
+ } else {
+ max_controls = MAX_CTRL_MRST;
+ controls = snd_intelmad_controls_mrst;
+ }
+ for (idx = 0; idx < max_controls; idx++) {
+ ret_val = snd_ctl_add(card,
+ snd_ctl_new1(&controls[idx],
+ intelmaddata));
+ pr_debug("sst: mixer[idx]=%d added\n", idx);
+ if (ret_val) {
+ pr_err("sst: in adding of control index = %d\n", idx);
+ break;
+ }
+ }
+ return ret_val;
+}
+
+static int snd_intelmad_dev_free(struct snd_device *device)
+{
+ struct snd_intelmad *intelmaddata;
+
+ WARN_ON(!device);
+
+ intelmaddata = device->device_data;
+
+ pr_debug("sst: snd_intelmad_dev_free called\n");
+ snd_card_free(intelmaddata->card);
+ /*genl_unregister_family(&audio_event_genl_family);*/
+ unregister_sst_card(intelmaddata->sstdrv_ops);
+
+ /* free allocated memory for internal context */
+ destroy_workqueue(intelmaddata->mad_jack_wq);
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ return 0;
+}
+
+static int __devinit snd_intelmad_create(
+ struct snd_intelmad *intelmaddata,
+ struct snd_card *card)
+{
+ int ret_val;
+ static struct snd_device_ops ops = {
+ .dev_free = snd_intelmad_dev_free,
+ };
+
+ WARN_ON(!intelmaddata);
+ WARN_ON(!card);
+ /* ALSA api to register for the device */
+ ret_val = snd_device_new(card, SNDRV_DEV_LOWLEVEL, intelmaddata, &ops);
+ return ret_val;
+}
+
+/**
+* snd_intelmad_probe- function registred for init
+* @pdev : pointer to the device struture
+* This function is called when the device is initialized
+*/
+int __devinit snd_intelmad_probe(struct platform_device *pdev)
+{
+ struct snd_card *card;
+ int ret_val;
+ struct snd_intelmad *intelmaddata;
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ unsigned int cpu_id = (unsigned int)id->driver_data;
+
+ pr_debug("sst: probe for %s cpu_id %d\n", pdev->name, cpu_id);
+ if (!strcmp(pdev->name, DRIVER_NAME_MRST))
+ pr_debug("sst: detected MRST\n");
+ else if (!strcmp(pdev->name, DRIVER_NAME_MFLD))
+ pr_debug("sst: detected MFLD\n");
+ else {
+ pr_err("sst: detected unknown device abort!!\n");
+ return -EIO;
+ }
+ if ((cpu_id < CPU_CHIP_LINCROFT) || (cpu_id > CPU_CHIP_PENWELL)) {
+ pr_err("sst: detected unknown cpu_id abort!!\n");
+ return -EIO;
+ }
+ /* allocate memory for saving internal context and working */
+ intelmaddata = kzalloc(sizeof(*intelmaddata), GFP_KERNEL);
+ if (!intelmaddata) {
+ pr_debug("sst: mem alloctn fail\n");
+ return -ENOMEM;
+ }
+
+ /* allocate memory for LPE API set */
+ intelmaddata->sstdrv_ops = kzalloc(sizeof(struct intel_sst_card_ops),
+ GFP_KERNEL);
+ if (!intelmaddata->sstdrv_ops) {
+ pr_err("sst: mem allocation for ops fail\n");
+ kfree(intelmaddata);
+ return -ENOMEM;
+ }
+
+ intelmaddata->cpu_id = cpu_id;
+ /* create a card instance with ALSA framework */
+ ret_val = snd_card_create(card_index, card_id, THIS_MODULE, 0, &card);
+ if (ret_val) {
+ pr_err("sst: snd_card_create fail\n");
+ goto free_allocs;
+ }
+
+ intelmaddata->pdev = pdev;
+ intelmaddata->irq = platform_get_irq(pdev, 0);
+ platform_set_drvdata(pdev, intelmaddata);
+ intelmaddata->card = card;
+ intelmaddata->card_id = card_id;
+ intelmaddata->card_index = card_index;
+ intelmaddata->master_mute = UNMUTE;
+ intelmaddata->playback_cnt = intelmaddata->capture_cnt = 0;
+ strncpy(card->driver, INTEL_MAD, strlen(INTEL_MAD));
+ strncpy(card->shortname, INTEL_MAD, strlen(INTEL_MAD));
+
+ intelmaddata->sstdrv_ops->module_name = SST_CARD_NAMES;
+ /* registering with LPE driver to get access to SST APIs to use */
+ ret_val = snd_intelmad_sst_register(intelmaddata);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_sst_register failed\n");
+ goto free_allocs;
+ }
+
+ intelmaddata->pmic_status = PMIC_INIT;
+
+ ret_val = snd_intelmad_pcm(card, intelmaddata);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_pcm failed\n");
+ goto free_allocs;
+ }
+
+ ret_val = snd_intelmad_mixer(intelmaddata);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_mixer failed\n");
+ goto free_allocs;
+ }
+
+ ret_val = snd_intelmad_jack(intelmaddata);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_jack failed\n");
+ goto free_allocs;
+ }
+
+ /*create work queue for jack interrupt*/
+ INIT_WORK(&intelmaddata->mad_jack_msg.wq,
+ sst_process_mad_jack_detection);
+
+ intelmaddata->mad_jack_wq = create_workqueue("sst_mad_jack_wq");
+ if (!intelmaddata->mad_jack_wq)
+ goto free_mad_jack_wq;
+
+ ret_val = snd_intelmad_register_irq(intelmaddata);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_register_irq fail\n");
+ goto free_allocs;
+ }
+
+ /* internal function call to register device with ALSA */
+ ret_val = snd_intelmad_create(intelmaddata, card);
+ if (ret_val) {
+ pr_err("sst: snd_intelmad_create failed\n");
+ goto free_allocs;
+ }
+ card->private_data = &intelmaddata;
+ snd_card_set_dev(card, &pdev->dev);
+ ret_val = snd_card_register(card);
+ if (ret_val) {
+ pr_err("sst: snd_card_register failed\n");
+ goto free_allocs;
+ }
+
+ pr_debug("sst:snd_intelmad_probe complete\n");
+ return ret_val;
+
+free_mad_jack_wq:
+ destroy_workqueue(intelmaddata->mad_jack_wq);
+free_allocs:
+ pr_err("sst: probe failed\n");
+ snd_card_free(card);
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ return ret_val;
+}
+
+
+static int snd_intelmad_remove(struct platform_device *pdev)
+{
+ struct snd_intelmad *intelmaddata = platform_get_drvdata(pdev);
+
+ if (intelmaddata) {
+ snd_card_free(intelmaddata->card);
+ unregister_sst_card(intelmaddata->sstdrv_ops);
+ /* free allocated memory for internal context */
+ destroy_workqueue(intelmaddata->mad_jack_wq);
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ }
+ return 0;
+}
+
+/*********************************************************************
+ * Driver initialization and exit
+ *********************************************************************/
+static const struct platform_device_id snd_intelmad_ids[] = {
+ {DRIVER_NAME_MRST, CPU_CHIP_LINCROFT},
+ {DRIVER_NAME_MFLD, CPU_CHIP_PENWELL},
+ {"", 0},
+
+};
+
+static struct platform_driver snd_intelmad_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "intel_mid_sound_card",
+ },
+ .id_table = snd_intelmad_ids,
+ .probe = snd_intelmad_probe,
+ .remove = __devexit_p(snd_intelmad_remove),
+};
+
+/*
+ * alsa_card_intelmad_init- driver init function
+ *
+ * This function is called when driver module is inserted
+ */
+static int __init alsa_card_intelmad_init(void)
+{
+ pr_debug("sst: mad_init called\n");
+ return platform_driver_register(&snd_intelmad_driver);
+}
+
+/**
+ * alsa_card_intelmad_exit- driver exit function
+ *
+ * This function is called when driver module is removed
+ */
+static void __exit alsa_card_intelmad_exit(void)
+{
+ pr_debug("sst:mad_exit called\n");
+ return platform_driver_unregister(&snd_intelmad_driver);
+}
+
+module_init(alsa_card_intelmad_init)
+module_exit(alsa_card_intelmad_exit)
+
diff --git a/drivers/staging/intel_sst/intelmid.h b/drivers/staging/intel_sst/intelmid.h
new file mode 100644
index 0000000..81e7448
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid.h
@@ -0,0 +1,186 @@
+/*
+ * intelmid.h - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Harsha Priya <priya.harsha(a)intel.com>
+ * Vinod Koul <vinod.koul(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver header for Intel MAD chipset
+ */
+#ifndef __INTELMID_H
+#define __INTELMID_H
+
+#include <linux/time.h>
+
+#define DRIVER_NAME_MFLD "msic_audio"
+#define DRIVER_NAME_MRST "pmic_audio"
+#define DRIVER_NAME "intelmid_audio"
+#define PMIC_SOUND_IRQ_TYPE_MASK (1 << 15)
+#define AUDINT_BASE (0xFFFFEFF8 + (6 * sizeof(u8)))
+#define REG_IRQ
+/* values #defined */
+/* will differ for different hw - to be taken from config */
+#define MAX_DEVICES 1
+#define MIN_RATE 8000
+#define MAX_RATE 48000
+#define MAX_BUFFER (800*1024) /* for PCM */
+#define MIN_BUFFER (800*1024)
+#define MAX_PERIODS (1024*2)
+#define MIN_PERIODS 1
+#define MAX_PERIOD_BYTES MAX_BUFFER
+#define MIN_PERIOD_BYTES 32
+/*#define MIN_PERIOD_BYTES 160*/
+#define MAX_MUTE 1
+#define MIN_MUTE 0
+#define MONO_CNTL 1
+#define STEREO_CNTL 2
+#define MIN_CHANNEL 1
+#define MAX_CHANNEL_AMIC 2
+#define MAX_CHANNEL_DMIC 4
+#define FIFO_SIZE 0 /* fifo not being used */
+#define INTEL_MAD "Intel MAD"
+#define MAX_CTRL_MRST 7
+#define MAX_CTRL_MFLD 2
+#define MAX_CTRL 7
+#define MAX_VENDORS 4
+/* TODO +6 db */
+#define MAX_VOL 64
+/* TODO -57 db */
+#define MIN_VOL 0
+#define PLAYBACK_COUNT 1
+#define CAPTURE_COUNT 1
+
+extern int sst_card_vendor_id;
+
+struct mad_jack {
+ struct snd_jack jack;
+ int jack_status;
+ struct timeval buttonpressed;
+ struct timeval buttonreleased;
+};
+
+struct mad_jack_msg_wq {
+ u8 intsts;
+ struct snd_intelmad *intelmaddata;
+ struct work_struct wq;
+
+};
+
+/**
+ * struct snd_intelmad - intelmad driver structure
+ *
+ * @card: ptr to the card details
+ * @card_index: sound card index
+ * @card_id: sound card id detected
+ * @sstdrv_ops: ptr to sst driver ops
+ * @pdev: ptr to platfrom device
+ * @irq: interrupt number detected
+ * @pmic_status: Device status of sound card
+ * @int_base: ptr to MMIO interrupt region
+ * @output_sel: device slected as o/p
+ * @input_sel: device slected as i/p
+ * @master_mute: master mute status
+ * @jack: jack status
+ * @playback_cnt: active pb streams
+ * @capture_cnt: active cp streams
+ * @mad_jack_msg: wq struct for jack interrupt processing
+ * @mad_jack_wq: wq for jack interrupt processing
+ * @jack_prev_state: Previos state of jack detected
+ * @cpu_id: current cpu id loaded for
+ */
+struct snd_intelmad {
+ struct snd_card *card; /* ptr to the card details */
+ int card_index;/* card index */
+ char *card_id; /* card id */
+ struct intel_sst_card_ops *sstdrv_ops;/* ptr to sst driver ops */
+ struct platform_device *pdev;
+ int irq;
+ int pmic_status;
+ void __iomem *int_base;
+ int output_sel;
+ int input_sel;
+ int master_mute;
+ struct mad_jack jack[4];
+ int playback_cnt;
+ int capture_cnt;
+ struct mad_jack_msg_wq mad_jack_msg;
+ struct workqueue_struct *mad_jack_wq;
+ u8 jack_prev_state;
+ unsigned int cpu_id;
+};
+
+struct snd_control_val {
+ int playback_vol_max;
+ int playback_vol_min;
+ int capture_vol_max;
+ int capture_vol_min;
+};
+
+struct mad_stream_pvt {
+ int stream_status;
+ int stream_ops;
+ struct snd_pcm_substream *substream;
+ struct pcm_stream_info stream_info;
+ ssize_t dbg_cum_bytes;
+ enum snd_sst_device_type device;
+};
+
+enum mad_drv_status {
+ INIT = 1,
+ STARTED,
+ RUNNING,
+ PAUSED,
+ DROPPED,
+};
+
+enum mad_pmic_status {
+ PMIC_UNINIT = 1,
+ PMIC_INIT,
+};
+enum _widget_ctrl {
+ OUTPUT_SEL = 1,
+ INPUT_SEL,
+ PLAYBACK_VOL,
+ PLAYBACK_MUTE,
+ CAPTURE_VOL,
+ CAPTURE_MUTE,
+ MASTER_MUTE
+};
+
+void period_elapsed(void *mad_substream);
+int snd_intelmad_alloc_stream(struct snd_pcm_substream *substream);
+int snd_intelmad_init_stream(struct snd_pcm_substream *substream);
+
+int sst_sc_reg_access(struct sc_reg_access *sc_access,
+ int type, int num_val);
+#define CPU_CHIP_LINCROFT 1 /* System running lincroft */
+#define CPU_CHIP_PENWELL 2 /* System running penwell */
+
+extern struct snd_control_val intelmad_ctrl_val[];
+extern struct snd_kcontrol_new snd_intelmad_controls_mrst[];
+extern struct snd_kcontrol_new snd_intelmad_controls_mfld[];
+extern struct snd_pmic_ops *intelmad_vendor_ops[];
+
+/* This is an enabler hook as the platform detection logic isn't yet
+ present and depends on some firmware and DMI support to detect AAVA
+ devices. It will vanish once the AAVA platform support is merged */
+#define is_aava() 0
+
+#endif /* __INTELMID_H */
diff --git a/drivers/staging/intel_sst/intelmid_ctrl.c b/drivers/staging/intel_sst/intelmid_ctrl.c
new file mode 100644
index 0000000..03b4ece
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_ctrl.c
@@ -0,0 +1,629 @@
+/*
+ * intelmid_ctrl.c - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Harsha Priya <priya.harsha(a)intel.com>
+ * Vinod Koul <vinod.koul(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver handling mixer controls for Intel MAD chipset
+ */
+#include <sound/core.h>
+#include <sound/control.h>
+#include "jack.h"
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intelmid_snd_control.h"
+#include "intelmid.h"
+
+static char *out_names_mrst[] = {"Headphones",
+ "Internal speakers"};
+static char *in_names_mrst[] = {"AMIC",
+ "DMIC",
+ "HS_MIC"};
+static char *out_names_mfld[] = {"Headset ",
+ "EarPiece "};
+static char *in_names_mfld[] = {"AMIC",
+ "DMIC"};
+
+struct snd_control_val intelmad_ctrl_val[MAX_VENDORS] = {
+ {
+ .playback_vol_max = 63,
+ .playback_vol_min = 0,
+ .capture_vol_max = 63,
+ .capture_vol_min = 0,
+ },
+ {
+ .playback_vol_max = 0,
+ .playback_vol_min = -31,
+ .capture_vol_max = 0,
+ .capture_vol_min = -20,
+ },
+ {
+ .playback_vol_max = 0,
+ .playback_vol_min = -126,
+ .capture_vol_max = 0,
+ .capture_vol_min = -31,
+ },
+};
+
+/* control path functionalities */
+
+static inline int snd_intelmad_volume_info(struct snd_ctl_elem_info *uinfo,
+ int control_type, int max, int min)
+{
+ WARN_ON(!uinfo);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = control_type;
+ uinfo->value.integer.min = min;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+
+/**
+* snd_intelmad_mute_info - provides information about the mute controls
+*
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+*
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_mute_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ WARN_ON(!uinfo);
+ WARN_ON(!kcontrol);
+
+ /* set up the mute as a boolean mono control with min-max values */
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = MONO_CNTL;
+ uinfo->value.integer.min = MIN_MUTE;
+ uinfo->value.integer.max = MAX_MUTE;
+ return 0;
+}
+
+/**
+* snd_intelmad_capture_volume_info - provides info about the volume control
+*
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+*
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_capture_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, MONO_CNTL,
+ intelmad_ctrl_val[sst_card_vendor_id].capture_vol_max,
+ intelmad_ctrl_val[sst_card_vendor_id].capture_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_playback_volume_info - provides info about the volume control
+*
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+*
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_playback_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ intelmad_ctrl_val[sst_card_vendor_id].playback_vol_max,
+ intelmad_ctrl_val[sst_card_vendor_id].playback_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_device_info_mrst - provides information about the devices available
+*
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the devices's info need
+* to be filled
+*
+* This function is called when a mixer application requests for device's info
+*/
+static int snd_intelmad_device_info_mrst(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ WARN_ON(!kcontrol);
+ WARN_ON(!uinfo);
+
+ /* setup device select as drop down controls with different values */
+ if (kcontrol->id.numid == OUTPUT_SEL)
+ uinfo->value.enumerated.items = ARRAY_SIZE(out_names_mrst);
+ else
+ uinfo->value.enumerated.items = ARRAY_SIZE(in_names_mrst);
+ uinfo->count = MONO_CNTL;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = 1;
+ if (kcontrol->id.numid == OUTPUT_SEL)
+ strncpy(uinfo->value.enumerated.name,
+ out_names_mrst[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name)-1);
+ else
+ strncpy(uinfo->value.enumerated.name,
+ in_names_mrst[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name)-1);
+ return 0;
+}
+
+static int snd_intelmad_device_info_mfld(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ WARN_ON(!kcontrol);
+ WARN_ON(!uinfo);
+ /* setup device select as drop down controls with different values */
+ if (kcontrol->id.numid == OUTPUT_SEL)
+ uinfo->value.enumerated.items = ARRAY_SIZE(out_names_mfld);
+ else
+ uinfo->value.enumerated.items = ARRAY_SIZE(in_names_mfld);
+ uinfo->count = MONO_CNTL;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = 1;
+ if (kcontrol->id.numid == OUTPUT_SEL)
+ strncpy(uinfo->value.enumerated.name,
+ out_names_mfld[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name)-1);
+ else
+ strncpy(uinfo->value.enumerated.name,
+ in_names_mfld[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name)-1);
+ return 0;
+}
+
+/**
+* snd_intelmad_volume_get - gets the current volume for the control
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info need
+* to be filled
+*
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ int ret_val = 0, cntl_list[2] = {0,};
+ int value = 0;
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+
+ pr_debug("sst: snd_intelmad_volume_get called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case PLAYBACK_VOL:
+ cntl_list[0] = PMIC_SND_RIGHT_PB_VOL;
+ cntl_list[1] = PMIC_SND_LEFT_PB_VOL;
+ break;
+
+ case CAPTURE_VOL:
+ cntl_list[0] = PMIC_SND_CAPTURE_VOL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->get_vol(cntl_list[0], &value);
+ uval->value.integer.value[0] = value;
+
+ if (ret_val)
+ return ret_val;
+
+ if (kcontrol->id.numid == PLAYBACK_VOL) {
+ ret_val = scard_ops->get_vol(cntl_list[1], &value);
+ uval->value.integer.value[1] = value;
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_mute_get - gets the current mute status for the control
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info need
+* to be filled
+*
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+
+ int cntl_list = 0, ret_val = 0;
+ u8 value = 0;
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+
+ pr_debug("sst: Mute_get called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case PLAYBACK_MUTE:
+ if (intelmaddata->output_sel == STEREO_HEADPHONE)
+ cntl_list = PMIC_SND_LEFT_HP_MUTE;
+ else if ((intelmaddata->output_sel == INTERNAL_SPKR) ||
+ (intelmaddata->output_sel == MONO_EARPIECE))
+ cntl_list = PMIC_SND_LEFT_SPEAKER_MUTE;
+ break;
+
+ case CAPTURE_MUTE:
+ if (intelmaddata->input_sel == DMIC)
+ cntl_list = PMIC_SND_DMIC_MUTE;
+ else if (intelmaddata->input_sel == AMIC)
+ cntl_list = PMIC_SND_AMIC_MUTE;
+ else if (intelmaddata->input_sel == HS_MIC)
+ cntl_list = PMIC_SND_HP_MIC_MUTE;
+ break;
+ case MASTER_MUTE:
+ uval->value.integer.value[0] = intelmaddata->master_mute;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->get_mute(cntl_list, &value);
+ uval->value.integer.value[0] = value;
+ return ret_val;
+}
+
+/**
+* snd_intelmad_volume_set - sets the volume control's info
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+*
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_volume_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+
+ int ret_val, cntl_list[2] = {0,};
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+
+ pr_debug("sst: volume set called:%ld %ld\n",
+ uval->value.integer.value[0],
+ uval->value.integer.value[1]);
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case PLAYBACK_VOL:
+ cntl_list[0] = PMIC_SND_LEFT_PB_VOL;
+ cntl_list[1] = PMIC_SND_RIGHT_PB_VOL;
+ break;
+
+ case CAPTURE_VOL:
+ cntl_list[0] = PMIC_SND_CAPTURE_VOL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->set_vol(cntl_list[0],
+ uval->value.integer.value[0]);
+ if (ret_val)
+ return ret_val;
+
+ if (kcontrol->id.numid == PLAYBACK_VOL)
+ ret_val = scard_ops->set_vol(cntl_list[1],
+ uval->value.integer.value[1]);
+ return ret_val;
+}
+
+/**
+* snd_intelmad_mute_set - sets the mute control's info
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+*
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_mute_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ int cntl_list[2] = {0,}, ret_val;
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+
+ pr_debug("sst: snd_intelmad_mute_set called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ kcontrol->private_value = uval->value.integer.value[0];
+
+ switch (kcontrol->id.numid) {
+ case PLAYBACK_MUTE:
+ if (intelmaddata->output_sel == STEREO_HEADPHONE) {
+ cntl_list[0] = PMIC_SND_LEFT_HP_MUTE;
+ cntl_list[1] = PMIC_SND_RIGHT_HP_MUTE;
+ } else if ((intelmaddata->output_sel == INTERNAL_SPKR) ||
+ (intelmaddata->output_sel == MONO_EARPIECE)) {
+ cntl_list[0] = PMIC_SND_LEFT_SPEAKER_MUTE;
+ cntl_list[1] = PMIC_SND_RIGHT_SPEAKER_MUTE;
+ }
+ break;
+
+ case CAPTURE_MUTE:/*based on sel device mute the i/p dev*/
+ if (intelmaddata->input_sel == DMIC)
+ cntl_list[0] = PMIC_SND_DMIC_MUTE;
+ else if (intelmaddata->input_sel == AMIC)
+ cntl_list[0] = PMIC_SND_AMIC_MUTE;
+ else if (intelmaddata->input_sel == HS_MIC)
+ cntl_list[0] = PMIC_SND_HP_MIC_MUTE;
+ break;
+ case MASTER_MUTE:
+ cntl_list[0] = PMIC_SND_MUTE_ALL;
+ intelmaddata->master_mute = uval->value.integer.value[0];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->set_mute(cntl_list[0],
+ uval->value.integer.value[0]);
+ if (ret_val)
+ return ret_val;
+
+ if (kcontrol->id.numid == PLAYBACK_MUTE)
+ ret_val = scard_ops->set_mute(cntl_list[1],
+ uval->value.integer.value[0]);
+ return ret_val;
+}
+
+/**
+* snd_intelmad_device_get - get the device select control's info
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* to be filled
+*
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_device_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+ pr_debug("sst: device_get called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+ if (intelmaddata->cpu_id == CPU_CHIP_PENWELL) {
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+ if (kcontrol->id.numid == OUTPUT_SEL)
+ uval->value.enumerated.item[0] =
+ scard_ops->output_dev_id;
+ else if (kcontrol->id.numid == INPUT_SEL)
+ uval->value.enumerated.item[0] =
+ scard_ops->input_dev_id;
+ else
+ return -EINVAL;
+ } else
+ uval->value.enumerated.item[0] = kcontrol->private_value;
+ return 0;
+}
+
+/**
+* snd_intelmad_device_set - set the device select control's info
+*
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+*
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_device_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_intelmad *intelmaddata;
+ struct snd_pmic_ops *scard_ops;
+ int ret_val = 0, vendor, status;
+
+ pr_debug("sst: snd_intelmad_device_set called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+ status = -1;
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ /* store value with driver */
+ kcontrol->private_value = uval->value.enumerated.item[0];
+
+ switch (kcontrol->id.numid) {
+ case OUTPUT_SEL:
+ ret_val = scard_ops->set_output_dev(
+ uval->value.enumerated.item[0]);
+ intelmaddata->output_sel = uval->value.enumerated.item[0];
+ break;
+ case INPUT_SEL:
+ vendor = intelmaddata->sstdrv_ops->vendor_id;
+ if ((vendor == SND_MX) || (vendor == SND_FS)) {
+ if (uval->value.enumerated.item[0] == HS_MIC) {
+ status = 1;
+ intelmaddata->sstdrv_ops->
+ control_set(SST_ENABLE_RX_TIME_SLOT, &status);
+ } else {
+ status = 0;
+ intelmaddata->sstdrv_ops->
+ control_set(SST_ENABLE_RX_TIME_SLOT, &status);
+ }
+ }
+ ret_val = scard_ops->set_input_dev(
+ uval->value.enumerated.item[0]);
+ intelmaddata->input_sel = uval->value.enumerated.item[0];
+ break;
+ default:
+ return -EINVAL;
+ }
+ kcontrol->private_value = uval->value.enumerated.item[0];
+ return ret_val;
+}
+
+struct snd_kcontrol_new snd_intelmad_controls_mrst[MAX_CTRL] __devinitdata = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info_mrst,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info_mrst,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_playback_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_capture_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+};
+
+struct snd_kcontrol_new
+snd_intelmad_controls_mfld[MAX_CTRL_MFLD] __devinitdata = {
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info_mfld,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info_mfld,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+};
+
diff --git a/drivers/staging/intel_sst/intelmid_msic_control.c b/drivers/staging/intel_sst/intelmid_msic_control.c
new file mode 100644
index 0000000..4d1755e
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_msic_control.c
@@ -0,0 +1,410 @@
+/*
+ * intelmid_vm_control.c - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the control operations of msic vendors
+ */
+
+#include <linux/pci.h>
+#include <linux/file.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intelmid_snd_control.h"
+
+static int msic_init_card(void)
+{
+ struct sc_reg_access sc_access[] = {
+ /* dmic configuration */
+ {0x241, 0x85, 0},
+ {0x242, 0x02, 0},
+ /* audio paths config */
+ {0x24C, 0x10, 0},
+ {0x24D, 0x32, 0},
+ /* PCM2 interface slots */
+ /* preconfigured slots for 0-5 both tx, rx */
+ {0x272, 0x10, 0},
+ {0x273, 0x32, 0},
+ {0x274, 0xFF, 0},
+ {0x275, 0x10, 0},
+ {0x276, 0x32, 0},
+ {0x277, 0x54, 0},
+ /*Sinc5 decimator*/
+ {0x24E, 0x28, 0},
+ /*TI vibra w/a settings*/
+ {0x384, 0x80, 0},
+ {0x385, 0x80, 0},
+ /*vibra settings*/
+ {0x267, 0x00, 0},
+ {0x26A, 0x10, 0},
+ {0x261, 0x00, 0},
+ {0x264, 0x10, 0},
+ /* pcm port setting */
+ {0x278, 0x00, 0},
+ {0x27B, 0x01, 0},
+ {0x27C, 0x0a, 0},
+ /* Set vol HSLRVOLCTRL, IHFVOL */
+ {0x259, 0x04, 0},
+ {0x25A, 0x04, 0},
+ {0x25B, 0x04, 0},
+ {0x25C, 0x04, 0},
+ /* HSEPRXCTRL Enable the headset left and right FIR filters */
+ {0x250, 0x30, 0},
+ /* HSMIXER */
+ {0x256, 0x11, 0},
+ /* amic configuration */
+ {0x249, 0x09, 0x0},
+ {0x24A, 0x09, 0x0},
+ /* unmask ocaudio/accdet interrupts */
+ {0x1d, 0x00, 0x00},
+ {0x1e, 0x00, 0x00},
+ };
+ snd_msic_ops.card_status = SND_CARD_INIT_DONE;
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 30);
+ snd_msic_ops.pb_on = 0;
+ snd_msic_ops.cap_on = 0;
+ snd_msic_ops.input_dev_id = DMIC; /*def dev*/
+ snd_msic_ops.output_dev_id = STEREO_HEADPHONE;
+ pr_debug("sst: msic init complete!!\n");
+ return 0;
+}
+
+static int msic_power_up_pb(unsigned int device)
+{
+ struct sc_reg_access sc_access1[] = {
+ /* turn on the audio power supplies */
+ {0x0DB, 0x05, 0},
+ /* VHSP */
+ {0x0DC, 0xFF, 0},
+ /* VHSN */
+ {0x0DD, 0x3F, 0},
+ /* turn on PLL */
+ {0x240, 0x21, 0},
+ };
+ struct sc_reg_access sc_access2[] = {
+ /* disable driver */
+ {0x25D, 0x0, 0x43},
+ /* DAC CONFIG ; both HP, LP on */
+ {0x257, 0x03, 0x03},
+ };
+ struct sc_reg_access sc_access3[] = {
+ /* HSEPRXCTRL Enable the headset left and right FIR filters */
+ {0x250, 0x30, 0},
+ /* HSMIXER */
+ {0x256, 0x11, 0},
+ };
+ struct sc_reg_access sc_access4[] = {
+ /* enable driver */
+ {0x25D, 0x3, 0x3},
+ /* unmute the headset */
+ { 0x259, 0x80, 0x80},
+ { 0x25A, 0x80, 0x80},
+ };
+ struct sc_reg_access sc_access_vihf[] = {
+ /* VIHF ON */
+ {0x0C9, 0x2D, 0x00},
+ };
+ struct sc_reg_access sc_access22[] = {
+ /* disable driver */
+ {0x25D, 0x00, 0x0C},
+ /*Filer DAC enable*/
+ {0x251, 0x03, 0x03},
+ {0x257, 0x0C, 0x0C},
+ };
+ struct sc_reg_access sc_access32[] = {
+ /*enable drv*/
+ {0x25D, 0x0C, 0x0c},
+ };
+ struct sc_reg_access sc_access42[] = {
+ /*unmute headset*/
+ {0x25B, 0x80, 0x80},
+ {0x25C, 0x80, 0x80},
+ };
+ struct sc_reg_access sc_access23[] = {
+ /* disable driver */
+ {0x25D, 0x0, 0x43},
+ /* DAC CONFIG ; both HP, LP on */
+ {0x257, 0x03, 0x03},
+ };
+ struct sc_reg_access sc_access43[] = {
+ /* enable driver */
+ {0x25D, 0x40, 0x40},
+ /* unmute the headset */
+ { 0x259, 0x80, 0x80},
+ { 0x25A, 0x80, 0x80},
+ };
+ struct sc_reg_access sc_access_vib[] = {
+ /* enable driver, ADC */
+ {0x25D, 0x10, 0x10},
+ {0x264, 0x02, 0x02},
+ };
+ struct sc_reg_access sc_access_hap[] = {
+ /* enable driver, ADC */
+ {0x25D, 0x20, 0x20},
+ {0x26A, 0x02, 0x02},
+ };
+ struct sc_reg_access sc_access_pcm2[] = {
+ /* enable pcm 2 */
+ {0x27C, 0x1, 0x1},
+ };
+ int retval = 0;
+
+ if (snd_msic_ops.card_status == SND_CARD_UN_INIT) {
+ retval = msic_init_card();
+ if (retval)
+ return retval;
+ }
+
+ pr_debug("sst: powering up pb.... Device %d\n", device);
+ sst_sc_reg_access(sc_access1, PMIC_WRITE, 4);
+ switch (device) {
+ case SND_SST_DEVICE_HEADSET:
+ if (snd_msic_ops.output_dev_id == STEREO_HEADPHONE) {
+ sst_sc_reg_access(sc_access2, PMIC_READ_MODIFY, 2);
+ sst_sc_reg_access(sc_access3, PMIC_WRITE, 2);
+ sst_sc_reg_access(sc_access4, PMIC_READ_MODIFY, 3);
+ } else {
+ sst_sc_reg_access(sc_access23, PMIC_READ_MODIFY, 2);
+ sst_sc_reg_access(sc_access3, PMIC_WRITE, 2);
+ sst_sc_reg_access(sc_access43, PMIC_READ_MODIFY, 3);
+ }
+ snd_msic_ops.pb_on = 1;
+ break;
+
+ case SND_SST_DEVICE_IHF:
+ sst_sc_reg_access(sc_access_vihf, PMIC_WRITE, 1);
+ sst_sc_reg_access(sc_access22, PMIC_READ_MODIFY, 3);
+ sst_sc_reg_access(sc_access32, PMIC_READ_MODIFY, 1);
+ sst_sc_reg_access(sc_access42, PMIC_READ_MODIFY, 2);
+ break;
+
+ case SND_SST_DEVICE_VIBRA:
+ sst_sc_reg_access(sc_access_vib, PMIC_READ_MODIFY, 2);
+ break;
+
+ case SND_SST_DEVICE_HAPTIC:
+ sst_sc_reg_access(sc_access_hap, PMIC_READ_MODIFY, 2);
+ break;
+
+ default:
+ pr_warn("sst: Wrong Device %d, selected %d\n",
+ device, snd_msic_ops.output_dev_id);
+ }
+ return sst_sc_reg_access(sc_access_pcm2, PMIC_READ_MODIFY, 1);
+}
+
+static int msic_power_up_cp(unsigned int device)
+{
+ struct sc_reg_access sc_access[] = {
+ /* turn on the audio power supplies */
+ {0x0DB, 0x05, 0},
+ /* VHSP */
+ {0x0DC, 0xFF, 0},
+ /* VHSN */
+ {0x0DD, 0x3F, 0},
+ /* turn on PLL */
+ {0x240, 0x21, 0},
+
+ /* Turn on DMIC supply */
+ {0x247, 0xA0, 0x0},
+ {0x240, 0x21, 0x0},
+ {0x24C, 0x10, 0x0},
+
+ /* mic demux enable */
+ {0x245, 0x3F, 0x0},
+ {0x246, 0x7, 0x0},
+
+ };
+ struct sc_reg_access sc_access_amic[] = {
+ /* turn on the audio power supplies */
+ {0x0DB, 0x05, 0},
+ /* VHSP */
+ {0x0DC, 0xFF, 0},
+ /* VHSN */
+ {0x0DD, 0x3F, 0},
+ /* turn on PLL */
+ {0x240, 0x21, 0},
+ /*ADC EN*/
+ {0x248, 0x05, 0x0},
+ {0x24C, 0x76, 0x0},
+ /*MIC EN*/
+ {0x249, 0x09, 0x0},
+ {0x24A, 0x09, 0x0},
+ /* Turn on AMIC supply */
+ {0x247, 0xFC, 0x0},
+
+ };
+ struct sc_reg_access sc_access2[] = {
+ /* enable pcm 2 */
+ {0x27C, 0x1, 0x1},
+ };
+ struct sc_reg_access sc_access3[] = {
+ /*wait for mic to stabalize before turning on audio channels*/
+ {0x24F, 0x3C, 0x0},
+ };
+ int retval = 0;
+
+ if (snd_msic_ops.card_status == SND_CARD_UN_INIT) {
+ retval = msic_init_card();
+ if (retval)
+ return retval;
+ }
+
+ pr_debug("sst: powering up cp....%d\n", snd_msic_ops.input_dev_id);
+ sst_sc_reg_access(sc_access2, PMIC_READ_MODIFY, 1);
+ snd_msic_ops.cap_on = 1;
+ if (snd_msic_ops.input_dev_id == AMIC)
+ sst_sc_reg_access(sc_access_amic, PMIC_WRITE, 9);
+ else
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 9);
+ return sst_sc_reg_access(sc_access3, PMIC_WRITE, 1);
+
+}
+
+static int msic_power_down(void)
+{
+ int retval = 0;
+
+ pr_debug("sst: powering dn msic\n");
+ snd_msic_ops.pb_on = 0;
+ snd_msic_ops.cap_on = 0;
+ return retval;
+}
+
+static int msic_power_down_pb(void)
+{
+ int retval = 0;
+
+ pr_debug("sst: powering dn pb....\n");
+ snd_msic_ops.pb_on = 0;
+ return retval;
+}
+
+static int msic_power_down_cp(void)
+{
+ int retval = 0;
+
+ pr_debug("sst: powering dn cp....\n");
+ snd_msic_ops.cap_on = 0;
+ return retval;
+}
+
+static int msic_set_selected_output_dev(u8 value)
+{
+ int retval = 0;
+
+ pr_debug("sst: msic set selected output:%d\n", value);
+ snd_msic_ops.output_dev_id = value;
+ if (snd_msic_ops.pb_on)
+ msic_power_up_pb(SND_SST_DEVICE_HEADSET);
+ return retval;
+}
+
+static int msic_set_selected_input_dev(u8 value)
+{
+
+ struct sc_reg_access sc_access_dmic[] = {
+ {0x24C, 0x10, 0x0},
+ };
+ struct sc_reg_access sc_access_amic[] = {
+ {0x24C, 0x76, 0x0},
+
+ };
+ int retval = 0;
+
+ pr_debug("sst: msic_set_selected_input_dev:%d\n", value);
+ snd_msic_ops.input_dev_id = value;
+ switch (value) {
+ case AMIC:
+ pr_debug("sst: Selecting AMIC1\n");
+ retval = sst_sc_reg_access(sc_access_amic, PMIC_WRITE, 1);
+ break;
+ case DMIC:
+ pr_debug("sst: Selecting DMIC1\n");
+ retval = sst_sc_reg_access(sc_access_dmic, PMIC_WRITE, 1);
+ break;
+ default:
+ return -EINVAL;
+
+ }
+ if (snd_msic_ops.cap_on)
+ retval = msic_power_up_cp(SND_SST_DEVICE_CAPTURE);
+ return retval;
+}
+
+static int msic_set_pcm_voice_params(void)
+{
+ return 0;
+}
+
+static int msic_set_pcm_audio_params(int sfreq, int word_size, int num_channel)
+{
+ return 0;
+}
+
+static int msic_set_audio_port(int status)
+{
+ return 0;
+}
+
+static int msic_set_voice_port(int status)
+{
+ return 0;
+}
+
+static int msic_set_mute(int dev_id, u8 value)
+{
+ return 0;
+}
+
+static int msic_set_vol(int dev_id, int value)
+{
+ return 0;
+}
+
+static int msic_get_mute(int dev_id, u8 *value)
+{
+ return 0;
+}
+
+static int msic_get_vol(int dev_id, int *value)
+{
+ return 0;
+}
+
+struct snd_pmic_ops snd_msic_ops = {
+ .set_input_dev = msic_set_selected_input_dev,
+ .set_output_dev = msic_set_selected_output_dev,
+ .set_mute = msic_set_mute,
+ .get_mute = msic_get_mute,
+ .set_vol = msic_set_vol,
+ .get_vol = msic_get_vol,
+ .init_card = msic_init_card,
+ .set_pcm_audio_params = msic_set_pcm_audio_params,
+ .set_pcm_voice_params = msic_set_pcm_voice_params,
+ .set_voice_port = msic_set_voice_port,
+ .set_audio_port = msic_set_audio_port,
+ .power_up_pmic_pb = msic_power_up_pb,
+ .power_up_pmic_cp = msic_power_up_cp,
+ .power_down_pmic_pb = msic_power_down_pb,
+ .power_down_pmic_cp = msic_power_down_cp,
+ .power_down_pmic = msic_power_down,
+};
diff --git a/drivers/staging/intel_sst/intelmid_pvt.c b/drivers/staging/intel_sst/intelmid_pvt.c
new file mode 100644
index 0000000..9ed9475
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_pvt.c
@@ -0,0 +1,174 @@
+/*
+ * intelmid_pvt.h - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Harsha Priya <priya.harsha(a)intel.com>
+ * Vinod Koul <vinod.koul(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID sound card chipset - holding private functions
+ */
+#include <linux/io.h>
+#include <asm/intel_scu_ipc.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include "jack.h"
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intelmid_snd_control.h"
+#include "intelmid.h"
+
+
+void period_elapsed(void *mad_substream)
+{
+ struct snd_pcm_substream *substream = mad_substream;
+ struct mad_stream_pvt *stream;
+
+
+
+ if (!substream || !substream->runtime)
+ return;
+ stream = substream->runtime->private_data;
+ if (!stream)
+ return;
+
+ if (stream->stream_status != RUNNING)
+ return;
+ pr_debug("sst: calling period elapsed\n");
+ snd_pcm_period_elapsed(substream);
+ return;
+}
+
+
+int snd_intelmad_alloc_stream(struct snd_pcm_substream *substream)
+{
+ struct snd_intelmad *intelmaddata = snd_pcm_substream_chip(substream);
+ struct mad_stream_pvt *stream = substream->runtime->private_data;
+ struct snd_sst_stream_params param = {{{0,},},};
+ struct snd_sst_params str_params = {0};
+ int ret_val;
+
+ /* set codec params and inform SST driver the same */
+
+ param.uc.pcm_params.codec = SST_CODEC_TYPE_PCM;
+ param.uc.pcm_params.num_chan = (u8) substream->runtime->channels;
+ param.uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits;
+ param.uc.pcm_params.reserved = 0;
+ param.uc.pcm_params.sfreq = substream->runtime->rate;
+ param.uc.pcm_params.ring_buffer_size =
+ snd_pcm_lib_buffer_bytes(substream);
+ param.uc.pcm_params.period_count = substream->runtime->period_size;
+ param.uc.pcm_params.ring_buffer_addr =
+ virt_to_phys(substream->runtime->dma_area);
+ pr_debug("sst: period_cnt = %d\n", param.uc.pcm_params.period_count);
+ pr_debug("sst: sfreq= %d, wd_sz = %d\n",
+ param.uc.pcm_params.sfreq, param.uc.pcm_params.pcm_wd_sz);
+
+ str_params.sparams = param;
+ str_params.codec = SST_CODEC_TYPE_PCM;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ str_params.ops = STREAM_OPS_PLAYBACK;
+ pr_debug("sst: Playbck stream,Device %d\n", stream->device);
+ } else {
+ str_params.ops = STREAM_OPS_CAPTURE;
+ stream->device = SND_SST_DEVICE_CAPTURE;
+ pr_debug("sst: Capture stream,Device %d\n", stream->device);
+ }
+ str_params.device_type = stream->device;
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_ALLOC,
+ &str_params);
+ pr_debug("sst: SST_SND_PLAY/CAPTURE ret_val = %x\n",
+ ret_val);
+ if (ret_val < 0)
+ return ret_val;
+
+ stream->stream_info.str_id = ret_val;
+ stream->stream_status = INIT;
+ stream->stream_info.buffer_ptr = 0;
+ pr_debug("sst: str id : %d\n", stream->stream_info.str_id);
+
+ return ret_val;
+}
+
+int snd_intelmad_init_stream(struct snd_pcm_substream *substream)
+{
+ struct mad_stream_pvt *stream = substream->runtime->private_data;
+ struct snd_intelmad *intelmaddata = snd_pcm_substream_chip(substream);
+ int ret_val;
+
+ pr_debug("sst: setting buffer ptr param\n");
+ stream->stream_info.period_elapsed = period_elapsed;
+ stream->stream_info.mad_substream = substream;
+ stream->stream_info.buffer_ptr = 0;
+ stream->stream_info.sfreq = substream->runtime->rate;
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_STREAM_INIT,
+ &stream->stream_info);
+ if (ret_val)
+ pr_err("sst: control_set ret error %d\n", ret_val);
+ return ret_val;
+
+}
+
+
+/**
+ * sst_sc_reg_access - IPC read/write wrapper
+ *
+ * @sc_access: array of data, addresses and mask
+ * @type: operation type
+ * @num_val: number of reg to opertae on
+ *
+ * Reads/writes/read-modify operations on registers accessed through SCU (sound
+ * card and few SST DSP regsisters that are not accissible to IA)
+ */
+int sst_sc_reg_access(struct sc_reg_access *sc_access,
+ int type, int num_val)
+{
+ int i, retval = 0;
+ if (type == PMIC_WRITE) {
+ for (i = 0; i < num_val; i++) {
+ retval = intel_scu_ipc_iowrite8(sc_access[i].reg_addr,
+ sc_access[i].value);
+ if (retval) {
+ pr_err("sst: IPC write failed!!! %d\n", retval);
+ return retval;
+ }
+ }
+ } else if (type == PMIC_READ) {
+ for (i = 0; i < num_val; i++) {
+ retval = intel_scu_ipc_ioread8(sc_access[i].reg_addr,
+ &(sc_access[i].value));
+ if (retval) {
+ pr_err("sst: IPC read failed!!!!!%d\n", retval);
+ return retval;
+ }
+ }
+ } else {
+ for (i = 0; i < num_val; i++) {
+ retval = intel_scu_ipc_update_register(
+ sc_access[i].reg_addr, sc_access[i].value,
+ sc_access[i].mask);
+ if (retval) {
+ pr_err("sst: IPC Modify failed!!!%d\n", retval);
+ return retval;
+ }
+ }
+ }
+ return retval;
+}
diff --git a/drivers/staging/intel_sst/intelmid_snd_control.h b/drivers/staging/intel_sst/intelmid_snd_control.h
new file mode 100644
index 0000000..a4565f3
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_snd_control.h
@@ -0,0 +1,114 @@
+#ifndef __INTELMID_SND_CTRL_H__
+#define __INTELMID_SND_CTRL_H__
+/*
+ * intelmid_snd_control.h - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file defines all snd control functions
+ */
+
+/*
+Mask bits
+*/
+#define MASK0 0x01 /* 0000 0001 */
+#define MASK1 0x02 /* 0000 0010 */
+#define MASK2 0x04 /* 0000 0100 */
+#define MASK3 0x08 /* 0000 1000 */
+#define MASK4 0x10 /* 0001 0000 */
+#define MASK5 0x20 /* 0010 0000 */
+#define MASK6 0x40 /* 0100 0000 */
+#define MASK7 0x80 /* 1000 0000 */
+/*
+value bits
+*/
+#define VALUE0 0x01 /* 0000 0001 */
+#define VALUE1 0x02 /* 0000 0010 */
+#define VALUE2 0x04 /* 0000 0100 */
+#define VALUE3 0x08 /* 0000 1000 */
+#define VALUE4 0x10 /* 0001 0000 */
+#define VALUE5 0x20 /* 0010 0000 */
+#define VALUE6 0x40 /* 0100 0000 */
+#define VALUE7 0x80 /* 1000 0000 */
+
+#define MUTE 0 /* ALSA Passes 0 for mute */
+#define UNMUTE 1 /* ALSA Passes 1 for unmute */
+
+#define MAX_VOL_PMIC_VENDOR0 0x3f /* max vol in dB for stereo & voice DAC */
+#define MIN_VOL_PMIC_VENDOR0 0 /* min vol in dB for stereo & voice DAC */
+/* Head phone volume control */
+#define MAX_HP_VOL_PMIC_VENDOR1 6 /* max volume in dB for HP */
+#define MIN_HP_VOL_PMIC_VENDOR1 (-84) /* min volume in dB for HP */
+#define MAX_HP_VOL_INDX_PMIC_VENDOR1 40 /* Number of HP volume control values */
+
+/* Mono Earpiece Volume control */
+#define MAX_EP_VOL_PMIC_VENDOR1 0 /* max volume in dB for EP */
+#define MIN_EP_VOL_PMIC_VENDOR1 (-75) /* min volume in dB for EP */
+#define MAX_EP_VOL_INDX_PMIC_VENDOR1 32 /* Number of EP volume control values */
+
+int sst_sc_reg_access(struct sc_reg_access *sc_access,
+ int type, int num_val);
+extern struct snd_pmic_ops snd_pmic_ops_fs;
+extern struct snd_pmic_ops snd_pmic_ops_mx;
+extern struct snd_pmic_ops snd_pmic_ops_nc;
+extern struct snd_pmic_ops snd_msic_ops;
+
+/* device */
+enum SND_INPUT_DEVICE {
+ AMIC,
+ DMIC,
+ HS_MIC,
+ IN_UNDEFINED
+};
+
+enum SND_OUTPUT_DEVICE {
+ STEREO_HEADPHONE,
+ MONO_EARPIECE,
+
+ INTERNAL_SPKR,
+ RECEIVER,
+ OUT_UNDEFINED
+};
+
+enum pmic_controls {
+ PMIC_SND_HP_MIC_MUTE = 0x0001,
+ PMIC_SND_AMIC_MUTE = 0x0002,
+ PMIC_SND_DMIC_MUTE = 0x0003,
+ PMIC_SND_CAPTURE_VOL = 0x0004,
+/* Output controls */
+ PMIC_SND_LEFT_PB_VOL = 0x0010,
+ PMIC_SND_RIGHT_PB_VOL = 0x0011,
+ PMIC_SND_LEFT_HP_MUTE = 0x0012,
+ PMIC_SND_RIGHT_HP_MUTE = 0x0013,
+ PMIC_SND_LEFT_SPEAKER_MUTE = 0x0014,
+ PMIC_SND_RIGHT_SPEAKER_MUTE = 0x0015,
+ PMIC_SND_RECEIVER_VOL = 0x0016,
+ PMIC_SND_RECEIVER_MUTE = 0x0017,
+/* Other controls */
+ PMIC_SND_MUTE_ALL = 0x0020,
+ PMIC_MAX_CONTROLS = 0x0020,
+};
+
+#endif /* __INTELMID_SND_CTRL_H__ */
+
+
diff --git a/drivers/staging/intel_sst/intelmid_v0_control.c b/drivers/staging/intel_sst/intelmid_v0_control.c
new file mode 100644
index 0000000..f586d62
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_v0_control.c
@@ -0,0 +1,771 @@
+/*
+ * intel_sst_v0_control.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corporation
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the control operations of vendor 1
+ */
+
+#include <linux/pci.h>
+#include <linux/file.h>
+#include "intel_sst.h"
+#include "intelmid_snd_control.h"
+
+
+enum _reg_v1 {
+ VOICEPORT1 = 0x180,
+ VOICEPORT2 = 0x181,
+ AUDIOPORT1 = 0x182,
+ AUDIOPORT2 = 0x183,
+ MISCVOICECTRL = 0x184,
+ MISCAUDCTRL = 0x185,
+ DMICCTRL1 = 0x186,
+ AUDIOBIAS = 0x187,
+ MICCTRL = 0x188,
+ MICLICTRL1 = 0x189,
+ MICLICTRL2 = 0x18A,
+ MICLICTRL3 = 0x18B,
+ VOICEDACCTRL1 = 0x18C,
+ STEREOADCCTRL = 0x18D,
+ AUD15 = 0x18E,
+ AUD16 = 0x18F,
+ AUD17 = 0x190,
+ AUD18 = 0x191,
+ RMIXOUTSEL = 0x192,
+ ANALOGLBR = 0x193,
+ ANALOGLBL = 0x194,
+ POWERCTRL1 = 0x195,
+ POWERCTRL2 = 0x196,
+ HEADSETDETECTINT = 0x197,
+ HEADSETDETECTINTMASK = 0x198,
+ TRIMENABLE = 0x199,
+};
+
+int rev_id = 0x20;
+
+/****
+ * fs_init_card - initialize the sound card
+ *
+ * This initilizes the audio paths to know values in case of this sound card
+ */
+static int fs_init_card(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {0x180, 0x00, 0x0},
+ {0x181, 0x00, 0x0},
+ {0x182, 0xF8, 0x0},
+ {0x183, 0x08, 0x0},
+ {0x184, 0x00, 0x0},
+ {0x185, 0x40, 0x0},
+ {0x186, 0x06, 0x0},
+ {0x187, 0x80, 0x0},
+ {0x188, 0x40, 0x0},
+ {0x189, 0x39, 0x0},
+ {0x18a, 0x39, 0x0},
+ {0x18b, 0x1F, 0x0},
+ {0x18c, 0x00, 0x0},
+ {0x18d, 0x00, 0x0},
+ {0x18e, 0x39, 0x0},
+ {0x18f, 0x39, 0x0},
+ {0x190, 0x39, 0x0},
+ {0x191, 0x11, 0x0},
+ {0x192, 0x0E, 0x0},
+ {0x193, 0x00, 0x0},
+ {0x194, 0x00, 0x0},
+ {0x195, 0x00, 0x0},
+ {0x196, 0x7C, 0x0},
+ {0x197, 0x00, 0x0},
+ {0x198, 0x0B, 0x0},
+ {0x199, 0x00, 0x0},
+ {0x037, 0x3F, 0x0},
+ };
+
+ snd_pmic_ops_fs.card_status = SND_CARD_INIT_DONE;
+ snd_pmic_ops_fs.master_mute = UNMUTE;
+ snd_pmic_ops_fs.mute_status = UNMUTE;
+ snd_pmic_ops_fs.num_channel = 2;
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 27);
+}
+
+static int fs_enable_audiodac(int value)
+{
+ struct sc_reg_access sc_access[3];
+ sc_access[0].reg_addr = AUD16;
+ sc_access[1].reg_addr = AUD17;
+ sc_access[2].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask = sc_access[2].mask = MASK7;
+
+ if (snd_pmic_ops_fs.mute_status == MUTE)
+ return 0;
+ if (value == MUTE) {
+ sc_access[0].value = sc_access[1].value =
+ sc_access[2].value = 0x80;
+
+ } else {
+ sc_access[0].value = sc_access[1].value =
+ sc_access[2].value = 0x0;
+ }
+ if (snd_pmic_ops_fs.num_channel == 1)
+ sc_access[1].value = sc_access[2].value = 0x80;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 3);
+
+}
+
+static int fs_power_up_pb(unsigned int port)
+{
+ struct sc_reg_access sc_access[] = {
+ {AUDIOBIAS, 0x00, MASK7},
+ {POWERCTRL1, 0xC6, 0xC6},
+ {POWERCTRL2, 0x30, 0x30},
+
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ retval = fs_enable_audiodac(MUTE);
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 3);
+
+ if (retval)
+ return retval;
+
+ pr_debug("sst: in fs power up pb\n");
+ return fs_enable_audiodac(UNMUTE);
+}
+
+static int fs_power_down_pb(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {POWERCTRL1, 0x00, 0xC6},
+ {POWERCTRL2, 0x00, 0x30},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ retval = fs_enable_audiodac(MUTE);
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+ if (retval)
+ return retval;
+
+ pr_debug("sst: in fsl power down pb\n");
+ return fs_enable_audiodac(UNMUTE);
+}
+
+static int fs_power_up_cp(unsigned int port)
+{
+ struct sc_reg_access sc_access[] = {
+ {POWERCTRL2, 0x32, 0x32}, /*NOTE power up A ADC only as*/
+ {AUDIOBIAS, 0x00, MASK7},
+ /*as turning on V ADC causes noise*/
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+}
+
+static int fs_power_down_cp(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {POWERCTRL2, 0x00, 0x03},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+}
+
+static int fs_power_down(void)
+{
+ int retval = 0;
+ struct sc_reg_access sc_access[] = {
+ {AUDIOBIAS, MASK7, MASK7},
+ };
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+}
+
+static int fs_set_pcm_voice_params(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {0x180, 0xA0, 0},
+ {0x181, 0x04, 0},
+ {0x182, 0x0, 0},
+ {0x183, 0x0, 0},
+ {0x184, 0x18, 0},
+ {0x185, 0x40, 0},
+ {0x186, 0x06, 0},
+ {0x187, 0x0, 0},
+ {0x188, 0x10, 0},
+ {0x189, 0x39, 0},
+ {0x18a, 0x39, 0},
+ {0x18b, 0x02, 0},
+ {0x18c, 0x0, 0},
+ {0x18d, 0x0, 0},
+ {0x18e, 0x39, 0},
+ {0x18f, 0x0, 0},
+ {0x190, 0x0, 0},
+ {0x191, 0x20, 0},
+ {0x192, 0x20, 0},
+ {0x193, 0x0, 0},
+ {0x194, 0x0, 0},
+ {0x195, 0x06, 0},
+ {0x196, 0x25, 0},
+ {0x197, 0x0, 0},
+ {0x198, 0xF, 0},
+ {0x199, 0x0, 0},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 26);
+}
+
+static int fs_set_audio_port(int status)
+{
+ struct sc_reg_access sc_access[2];
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ if (status == DEACTIVATE) {
+ /* Deactivate audio port-tristate and power */
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6|MASK7;
+ sc_access[0].reg_addr = AUDIOPORT1;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = MASK4|MASK5;
+ sc_access[1].reg_addr = POWERCTRL2;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+ } else if (status == ACTIVATE) {
+ /* activate audio port */
+ sc_access[0].value = 0xC0;
+ sc_access[0].mask = MASK6|MASK7;
+ sc_access[0].reg_addr = AUDIOPORT1;
+ sc_access[1].value = 0x30;
+ sc_access[1].mask = MASK4|MASK5;
+ sc_access[1].reg_addr = POWERCTRL2;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+ } else
+ return -EINVAL;
+}
+
+static int fs_set_voice_port(int status)
+{
+ struct sc_reg_access sc_access[2];
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ if (status == DEACTIVATE) {
+ /* Deactivate audio port-tristate and power */
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6|MASK7;
+ sc_access[0].reg_addr = VOICEPORT1;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = MASK0|MASK1;
+ sc_access[1].reg_addr = POWERCTRL2;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+ } else if (status == ACTIVATE) {
+ /* activate audio port */
+ sc_access[0].value = 0xC0;
+ sc_access[0].mask = MASK6|MASK7;
+ sc_access[0].reg_addr = VOICEPORT1;
+ sc_access[1].value = 0x03;
+ sc_access[1].mask = MASK0|MASK1;
+ sc_access[1].reg_addr = POWERCTRL2;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+ } else
+ return -EINVAL;
+}
+
+static int fs_set_pcm_audio_params(int sfreq, int word_size, int num_channel)
+{
+ u8 config1 = 0;
+ struct sc_reg_access sc_access[4];
+ int retval = 0, num_value = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+ switch (sfreq) {
+ case 8000:
+ config1 = 0x00;
+ break;
+ case 11025:
+ config1 = 0x01;
+ break;
+ case 12000:
+ config1 = 0x02;
+ break;
+ case 16000:
+ config1 = 0x03;
+ break;
+ case 22050:
+ config1 = 0x04;
+ break;
+ case 24000:
+ config1 = 0x05;
+ break;
+ case 26000:
+ config1 = 0x06;
+ break;
+ case 32000:
+ config1 = 0x07;
+ break;
+ case 44100:
+ config1 = 0x08;
+ break;
+ case 48000:
+ config1 = 0x09;
+ break;
+ }
+ snd_pmic_ops_fs.num_channel = num_channel;
+ if (snd_pmic_ops_fs.num_channel == 1) {
+ sc_access[0].reg_addr = AUD17;
+ sc_access[1].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ sc_access[0].value = sc_access[1].value = 0x80;
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+ } else {
+ sc_access[0].reg_addr = AUD17;
+ sc_access[1].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ sc_access[0].value = sc_access[1].value = 0x00;
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+ }
+ pr_debug("sst: sfreq:%d,Register value = %x\n", sfreq, config1);
+
+ if (word_size == 24) {
+ sc_access[0].reg_addr = AUDIOPORT1;
+ sc_access[0].mask = MASK0|MASK1|MASK2|MASK3;
+ sc_access[0].value = 0xFB;
+
+
+ sc_access[1].reg_addr = AUDIOPORT2;
+ sc_access[1].value = config1 | 0x10;
+ sc_access[1].mask = MASK0 | MASK1 | MASK2 | MASK3
+ | MASK4 | MASK5 | MASK6;
+
+ sc_access[2].reg_addr = MISCAUDCTRL;
+ sc_access[2].value = 0x02;
+ sc_access[2].mask = 0x02;
+
+ num_value = 3 ;
+
+ } else {
+
+ sc_access[0].reg_addr = AUDIOPORT2;
+ sc_access[0].value = config1;
+ sc_access[0].mask = MASK0|MASK1|MASK2|MASK3;
+
+ sc_access[1].reg_addr = MISCAUDCTRL;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = 0x02;
+ num_value = 2;
+ }
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, num_value);
+
+}
+
+static int fs_set_selected_input_dev(u8 value)
+{
+ struct sc_reg_access sc_access_dmic[] = {
+ {MICCTRL, 0x81, 0xf7},
+ {MICLICTRL3, 0x00, 0xE0},
+ };
+ struct sc_reg_access sc_access_mic[] = {
+ {MICCTRL, 0x40, MASK2|MASK4|MASK5|MASK6|MASK7},
+ {MICLICTRL3, 0x00, 0xE0},
+ };
+ struct sc_reg_access sc_access_hsmic[] = {
+ {MICCTRL, 0x10, MASK2|MASK4|MASK5|MASK6|MASK7},
+ {MICLICTRL3, 0x00, 0xE0},
+ };
+
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+ switch (value) {
+ case AMIC:
+ pr_debug("sst: Selecting amic not supported in mono cfg\n");
+ return sst_sc_reg_access(sc_access_mic, PMIC_READ_MODIFY, 2);
+ break;
+
+ case HS_MIC:
+ pr_debug("sst: Selecting hsmic\n");
+ return sst_sc_reg_access(sc_access_hsmic,
+ PMIC_READ_MODIFY, 2);
+ break;
+
+ case DMIC:
+ pr_debug("sst: Selecting dmic\n");
+ return sst_sc_reg_access(sc_access_dmic, PMIC_READ_MODIFY, 2);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+}
+
+static int fs_set_selected_output_dev(u8 value)
+{
+ struct sc_reg_access sc_access_hp[] = {
+ {0x191, 0x11, 0x0},
+ {0x192, 0x0E, 0x0},
+ };
+ struct sc_reg_access sc_access_is[] = {
+ {0x191, 0x17, 0xFF},
+ {0x192, 0x08, 0xFF},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+ switch (value) {
+ case STEREO_HEADPHONE:
+ pr_debug("SST DBG:Selecting headphone\n");
+ return sst_sc_reg_access(sc_access_hp, PMIC_WRITE, 2);
+ break;
+ case MONO_EARPIECE:
+ case INTERNAL_SPKR:
+ pr_debug("SST DBG:Selecting internal spkr\n");
+ return sst_sc_reg_access(sc_access_is, PMIC_READ_MODIFY, 2);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+}
+
+static int fs_set_mute(int dev_id, u8 value)
+{
+ struct sc_reg_access sc_access[6] = {{0,},};
+ int reg_num = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+
+ pr_debug("sst: dev_id:0x%x value:0x%x\n", dev_id, value);
+ switch (dev_id) {
+ case PMIC_SND_DMIC_MUTE:
+ sc_access[0].reg_addr = MICCTRL;
+ sc_access[1].reg_addr = MICLICTRL1;
+ sc_access[2].reg_addr = MICLICTRL2;
+ sc_access[0].mask = MASK5;
+ sc_access[1].mask = sc_access[2].mask = MASK6;
+ if (value == MUTE) {
+ sc_access[0].value = 0x20;
+ sc_access[2].value = sc_access[1].value = 0x40;
+ } else
+ sc_access[0].value = sc_access[1].value
+ = sc_access[2].value = 0x0;
+ reg_num = 3;
+ break;
+ case PMIC_SND_HP_MIC_MUTE:
+ case PMIC_SND_AMIC_MUTE:
+ sc_access[0].reg_addr = MICLICTRL1;
+ sc_access[1].reg_addr = MICLICTRL2;
+ sc_access[0].mask = sc_access[1].mask = MASK6;
+ if (value == MUTE)
+ sc_access[0].value = sc_access[1].value = 0x40;
+ else
+ sc_access[0].value = sc_access[1].value = 0x0;
+ reg_num = 2;
+ break;
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ case PMIC_SND_LEFT_HP_MUTE:
+ sc_access[0].reg_addr = AUD16;
+ sc_access[1].reg_addr = AUD15;
+
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ if (value == MUTE)
+ sc_access[0].value = sc_access[1].value = 0x80;
+ else
+ sc_access[0].value = sc_access[1].value = 0x0;
+ reg_num = 2;
+ snd_pmic_ops_fs.mute_status = value;
+ break;
+ case PMIC_SND_RIGHT_HP_MUTE:
+ case PMIC_SND_RIGHT_SPEAKER_MUTE:
+ sc_access[0].reg_addr = AUD17;
+ sc_access[1].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ if (value == MUTE)
+ sc_access[0].value = sc_access[1].value = 0x80;
+ else
+ sc_access[0].value = sc_access[1].value = 0x0;
+ snd_pmic_ops_fs.mute_status = value;
+ if (snd_pmic_ops_fs.num_channel == 1)
+ sc_access[0].value = sc_access[1].value = 0x80;
+ reg_num = 2;
+ break;
+ case PMIC_SND_MUTE_ALL:
+ sc_access[0].reg_addr = AUD16;
+ sc_access[1].reg_addr = AUD17;
+ sc_access[2].reg_addr = AUD15;
+ sc_access[3].reg_addr = MICCTRL;
+ sc_access[4].reg_addr = MICLICTRL1;
+ sc_access[5].reg_addr = MICLICTRL2;
+ sc_access[0].mask = sc_access[1].mask =
+ sc_access[2].mask = MASK7;
+ sc_access[3].mask = MASK5;
+ sc_access[4].mask = sc_access[5].mask = MASK6;
+
+ if (value == MUTE) {
+ sc_access[0].value =
+ sc_access[1].value = sc_access[2].value = 0x80;
+ sc_access[3].value = 0x20;
+ sc_access[4].value = sc_access[5].value = 0x40;
+
+ } else {
+ sc_access[0].value = sc_access[1].value =
+ sc_access[2].value = sc_access[3].value =
+ sc_access[4].value = sc_access[5].value = 0x0;
+ }
+ if (snd_pmic_ops_fs.num_channel == 1)
+ sc_access[1].value = sc_access[2].value = 0x80;
+ reg_num = 6;
+ snd_pmic_ops_fs.mute_status = value;
+ snd_pmic_ops_fs.master_mute = value;
+ break;
+
+ }
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, reg_num);
+}
+
+static int fs_set_vol(int dev_id, int value)
+{
+ struct sc_reg_access sc_acces, sc_access[4] = {{0},};
+ int reg_num = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+ switch (dev_id) {
+ case PMIC_SND_LEFT_PB_VOL:
+ pr_debug("sst: PMIC_SND_LEFT_PB_VOL:%d\n", value);
+ sc_access[0].value = sc_access[1].value = value;
+ sc_access[0].reg_addr = AUD16;
+ sc_access[1].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ reg_num = 2;
+ break;
+
+ case PMIC_SND_RIGHT_PB_VOL:
+ pr_debug("sst: PMIC_SND_RIGHT_PB_VOL:%d\n", value);
+ sc_access[0].value = sc_access[1].value = value;
+ sc_access[0].reg_addr = AUD17;
+ sc_access[1].reg_addr = AUD15;
+ sc_access[0].mask = sc_access[1].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ if (snd_pmic_ops_fs.num_channel == 1) {
+ sc_access[0].value = sc_access[1].value = 0x80;
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ }
+ reg_num = 2;
+ break;
+ case PMIC_SND_CAPTURE_VOL:
+ pr_debug("sst: PMIC_SND_CAPTURE_VOL:%d\n", value);
+ sc_access[0].reg_addr = MICLICTRL1;
+ sc_access[1].reg_addr = MICLICTRL2;
+ sc_access[2].reg_addr = DMICCTRL1;
+ sc_access[2].value = value;
+ sc_access[0].value = sc_access[1].value = value;
+ sc_acces.reg_addr = MICLICTRL3;
+ sc_acces.value = value;
+ sc_acces.mask = (MASK0|MASK1|MASK2|MASK3|MASK5|MASK6|MASK7);
+ retval = sst_sc_reg_access(&sc_acces, PMIC_READ_MODIFY, 1);
+ sc_access[0].mask = sc_access[1].mask =
+ sc_access[2].mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ reg_num = 3;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, reg_num);
+}
+
+static int fs_get_mute(int dev_id, u8 *value)
+{
+ struct sc_reg_access sc_access[6] = {{0,},};
+
+ int retval = 0, temp_value = 0, mask = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+ switch (dev_id) {
+
+ case PMIC_SND_AMIC_MUTE:
+ case PMIC_SND_HP_MIC_MUTE:
+ sc_access[0].reg_addr = MICLICTRL1;
+ mask = MASK6;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, 1);
+ if (sc_access[0].value & mask)
+ *value = MUTE;
+ else
+ *value = UNMUTE;
+ break;
+ case PMIC_SND_DMIC_MUTE:
+ sc_access[0].reg_addr = MICCTRL;
+ mask = MASK5;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, 1);
+ temp_value = (sc_access[0].value & mask);
+ if (temp_value == 0)
+ *value = UNMUTE;
+ else
+ *value = MUTE;
+ break;
+
+ case PMIC_SND_LEFT_HP_MUTE:
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ sc_access[0].reg_addr = AUD16;
+ mask = MASK7;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, 1);
+ temp_value = sc_access[0].value & mask;
+ if (temp_value == 0)
+ *value = UNMUTE;
+ else
+ *value = MUTE;
+ break;
+ case PMIC_SND_RIGHT_HP_MUTE:
+ case PMIC_SND_RIGHT_SPEAKER_MUTE:
+ sc_access[0].reg_addr = AUD17;
+ mask = MASK7;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, 1);
+ temp_value = sc_access[0].value & mask;
+ if (temp_value == 0)
+ *value = UNMUTE;
+ else
+ *value = MUTE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return retval;
+}
+
+static int fs_get_vol(int dev_id, int *value)
+{
+ struct sc_reg_access sc_access = {0,};
+ int retval = 0, mask = 0;
+
+ if (snd_pmic_ops_fs.card_status == SND_CARD_UN_INIT)
+ retval = fs_init_card();
+ if (retval)
+ return retval;
+
+ switch (dev_id) {
+ case PMIC_SND_CAPTURE_VOL:
+ pr_debug("sst: PMIC_SND_CAPTURE_VOL\n");
+ sc_access.reg_addr = MICLICTRL1;
+ mask = (MASK5|MASK4|MASK3|MASK2|MASK1|MASK0);
+ break;
+ case PMIC_SND_LEFT_PB_VOL:
+ pr_debug("sst: PMIC_SND_LEFT_PB_VOL\n");
+ sc_access.reg_addr = AUD16;
+ mask = (MASK5|MASK4|MASK3|MASK2|MASK1|MASK0);
+ break;
+ case PMIC_SND_RIGHT_PB_VOL:
+ pr_debug("sst: PMIC_SND_RT_PB_VOL\n");
+ sc_access.reg_addr = AUD17;
+ mask = (MASK5|MASK4|MASK3|MASK2|MASK1|MASK0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ retval = sst_sc_reg_access(&sc_access, PMIC_READ, 1);
+ pr_debug("sst: value read = 0x%x\n", sc_access.value);
+ *value = (int) (sc_access.value & mask);
+ pr_debug("sst: value returned = 0x%x\n", *value);
+ return retval;
+}
+
+struct snd_pmic_ops snd_pmic_ops_fs = {
+ .set_input_dev = fs_set_selected_input_dev,
+ .set_output_dev = fs_set_selected_output_dev,
+ .set_mute = fs_set_mute,
+ .get_mute = fs_get_mute,
+ .set_vol = fs_set_vol,
+ .get_vol = fs_get_vol,
+ .init_card = fs_init_card,
+ .set_pcm_audio_params = fs_set_pcm_audio_params,
+ .set_pcm_voice_params = fs_set_pcm_voice_params,
+ .set_voice_port = fs_set_voice_port,
+ .set_audio_port = fs_set_audio_port,
+ .power_up_pmic_pb = fs_power_up_pb,
+ .power_up_pmic_cp = fs_power_up_cp,
+ .power_down_pmic_pb = fs_power_down_pb,
+ .power_down_pmic_cp = fs_power_down_cp,
+ .power_down_pmic = fs_power_down,
+};
diff --git a/drivers/staging/intel_sst/intelmid_v1_control.c b/drivers/staging/intel_sst/intelmid_v1_control.c
new file mode 100644
index 0000000..94d30a9
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_v1_control.c
@@ -0,0 +1,1072 @@
+/* intel_sst_v1_control.c - Intel SST Driver for audio engine
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the control operations of vendor 2
+ */
+
+#include <linux/pci.h>
+#include <linux/file.h>
+#include <asm/mrst.h>
+#include <sound/pcm.h>
+#include "jack.h"
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include "intel_sst.h"
+#include "intel_sst_ioctl.h"
+#include "intelmid.h"
+#include "intelmid_snd_control.h"
+
+#include <linux/gpio.h>
+#define KOSKI_VOICE_CODEC_ENABLE 46
+
+enum _reg_v2 {
+
+ MASTER_CLOCK_PRESCALAR = 0x205,
+ SET_MASTER_AND_LR_CLK1 = 0x20b,
+ SET_MASTER_AND_LR_CLK2 = 0x20c,
+ MASTER_MODE_AND_DATA_DELAY = 0x20d,
+ DIGITAL_INTERFACE_TO_DAI2 = 0x20e,
+ CLK_AND_FS1 = 0x208,
+ CLK_AND_FS2 = 0x209,
+ DAI2_TO_DAC_HP = 0x210,
+ HP_OP_SINGLE_ENDED = 0x224,
+ ENABLE_OPDEV_CTRL = 0x226,
+ ENABLE_DEV_AND_USE_XTAL = 0x227,
+
+ /* Max audio subsystem (PQ49) MAX 8921 */
+ AS_IP_MODE_CTL = 0xF9,
+ AS_LEFT_SPKR_VOL_CTL = 0xFA, /* Mono Earpiece volume control */
+ AS_RIGHT_SPKR_VOL_CTL = 0xFB,
+ AS_LEFT_HP_VOL_CTL = 0xFC,
+ AS_RIGHT_HP_VOL_CTL = 0xFD,
+ AS_OP_MIX_CTL = 0xFE,
+ AS_CONFIG = 0xFF,
+
+ /* Headphone volume control & mute registers */
+ VOL_CTRL_LT = 0x21c,
+ VOL_CTRL_RT = 0x21d,
+
+};
+/**
+ * mx_init_card - initilize the sound card
+ *
+ * This initilizes the audio paths to know values in case of this sound card
+ */
+static int mx_init_card(void)
+{
+ if (is_aava()) {
+
+ struct sc_reg_access sc_access[] = {
+ {0x200, 0x00, 0x0},
+ {0x201, 0xC0, 0x0},
+ {0x202, 0x00, 0x0},
+ {0x203, 0x00, 0x0},
+ {0x204, 0x0e, 0x0},
+ {0x205, 0x20, 0x0},
+ {0x206, 0x00, 0x0},
+ {0x207, 0x00, 0x0},
+ {0x208, 0x00, 0x0},
+ {0x209, 0x51, 0x0},
+ {0x20a, 0x00, 0x0},
+ {0x20b, 0x5a, 0x0},
+ {0x20c, 0xbe, 0x0},
+ {0x20d, 0x90, 0x0},
+ {0x20e, 0x51, 0x0},
+ {0x20f, 0x00, 0x0},
+ {0x210, 0x21, 0x0},
+ {0x211, 0x00, 0x0},
+ {0x212, 0x00, 0x0},
+ {0x213, 0x00, 0x0},
+ {0x214, 0x41, 0x0},
+ {0x215, 0x81, 0x0},
+ {0x216, 0x00, 0x0},
+ {0x217, 0x00, 0x0},
+ {0x218, 0x00, 0x0},
+ {0x219, 0x00, 0x0},
+ {0x21a, 0x00, 0x0},
+ {0x21b, 0x00, 0x0},
+ {0x21c, 0x00, 0x0},
+ {0x21d, 0x00, 0x0},
+ {0x21e, 0x00, 0x0},
+ {0x21f, 0x00, 0x0},
+ {0x220, 0x00, 0x0},
+ {0x221, 0x00, 0x0},
+ {0x222, 0x51, 0x0},
+ {0x223, 0x20, 0x0}, /* Jack detection: 00 -> 01 */
+ {0x224, 0x40, 0x0},
+ {0x225, 0x80, 0x0}, /* JAck detection: 00 -> 80 */
+ {0x226, 0x00, 0x0},
+ {0x227, 0x00, 0x0},
+ {0xf9, 0x40, 0x0},
+ {0xfa, 0x1F, 0x0},
+ {0xfb, 0x1F, 0x0},
+ {0xfc, 0x1F, 0x0},
+ {0xfd, 0x1F, 0x0},
+ {0xfe, 0x00, 0x0},
+ {0xff, 0x00, 0x0}, /* Removed sel_output */
+ };
+ int retval;
+
+ /*init clock sig to voice codec*/
+ retval = gpio_request(KOSKI_VOICE_CODEC_ENABLE,
+ "sound_voice_codec");
+ if (retval) {
+ pr_err("sst: Error enabling voice codec clock\n");
+ } else {
+ gpio_direction_output(KOSKI_VOICE_CODEC_ENABLE, 1);
+ pr_debug("sst: Voice codec clock enabled\n");
+ }
+
+ snd_pmic_ops_mx.card_status = SND_CARD_INIT_DONE;
+ snd_pmic_ops_mx.master_mute = UNMUTE;
+ snd_pmic_ops_mx.mute_status = UNMUTE;
+ snd_pmic_ops_mx.num_channel = 2;
+ pr_debug("**************inside aava\n");
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 47);
+ } else {
+ struct sc_reg_access sc_access[] = {
+ {0x200, 0x80, 0x00},
+ {0x201, 0xC0, 0x00},
+ {0x202, 0x00, 0x00},
+ {0x203, 0x00, 0x00},
+ {0x204, 0x02, 0x00},
+ {0x205, 0x10, 0x00},
+ {0x206, 0x60, 0x00},
+ {0x207, 0x00, 0x00},
+ {0x208, 0x90, 0x00},
+ {0x209, 0x51, 0x00},
+ {0x20a, 0x00, 0x00},
+ {0x20b, 0x10, 0x00},
+ {0x20c, 0x00, 0x00},
+ {0x20d, 0x00, 0x00},
+ {0x20e, 0x21, 0x00},
+ {0x20f, 0x00, 0x00},
+ {0x210, 0x84, 0x00},
+ {0x211, 0xB3, 0x00},
+ {0x212, 0x00, 0x00},
+ {0x213, 0x00, 0x00},
+ {0x214, 0x41, 0x00},
+ {0x215, 0x00, 0x00},
+ {0x216, 0x00, 0x00},
+ {0x217, 0x00, 0x00},
+ {0x218, 0x03, 0x00},
+ {0x219, 0x03, 0x00},
+ {0x21a, 0x00, 0x00},
+ {0x21b, 0x00, 0x00},
+ {0x21c, 0x00, 0x00},
+ {0x21d, 0x00, 0x00},
+ {0x21e, 0x00, 0x00},
+ {0x21f, 0x00, 0x00},
+ {0x220, 0x20, 0x00},
+ {0x221, 0x20, 0x00},
+ {0x222, 0x51, 0x00},
+ {0x223, 0x20, 0x00},
+ {0x224, 0x04, 0x00},
+ {0x225, 0x80, 0x00},
+ {0x226, 0x0F, 0x00},
+ {0x227, 0x08, 0x00},
+ {0xf9, 0x40, 0x00},
+ {0xfa, 0x1f, 0x00},
+ {0xfb, 0x1f, 0x00},
+ {0xfc, 0x1f, 0x00},
+ {0xfd, 0x1f, 0x00},
+ {0xfe, 0x00, 0x00},
+ {0xff, 0x0c, 0x00},
+ };
+ snd_pmic_ops_mx.card_status = SND_CARD_INIT_DONE;
+ snd_pmic_ops_mx.num_channel = 2;
+ snd_pmic_ops_mx.master_mute = UNMUTE;
+ snd_pmic_ops_mx.mute_status = UNMUTE;
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 47);
+ }
+}
+
+static int mx_init_capture_card(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {0x206, 0x5a, 0x0},
+ {0x207, 0xbe, 0x0},
+ {0x208, 0x90, 0x0},
+ {0x209, 0x32, 0x0},
+ {0x20e, 0x22, 0x0},
+ {0x210, 0x84, 0x0},
+ {0x223, 0x20, 0x0},
+ {0x226, 0xC0, 0x0},
+ };
+
+ int retval = 0;
+
+ retval = sst_sc_reg_access(sc_access, PMIC_WRITE, 8);
+ if (0 != retval) {
+ /* pmic communication fails */
+ pr_debug("sst: pmic commn failed\n");
+ return retval;
+ }
+
+ pr_debug("sst: Capture configuration complete!!\n");
+ return 0;
+}
+
+static int mx_init_playback_card(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {0x206, 0x00, 0x0},
+ {0x207, 0x00, 0x0},
+ {0x208, 0x00, 0x0},
+ {0x209, 0x51, 0x0},
+ {0x20e, 0x51, 0x0},
+ {0x210, 0x21, 0x0},
+ {0x223, 0x01, 0x0},
+ };
+ int retval = 0;
+
+ retval = sst_sc_reg_access(sc_access, PMIC_WRITE, 9);
+ if (0 != retval) {
+ /* pmic communication fails */
+ pr_debug("sst: pmic commn failed\n");
+ return retval;
+ }
+
+ pr_debug("sst: Playback configuration complete!!\n");
+ return 0;
+}
+
+static int mx_enable_audiodac(int value)
+{
+ struct sc_reg_access sc_access[3];
+ int mute_val = 0;
+ int mute_val1 = 0;
+ int retval = 0;
+
+ sc_access[0].reg_addr = AS_LEFT_HP_VOL_CTL;
+ sc_access[1].reg_addr = AS_RIGHT_HP_VOL_CTL;
+
+ if (value == UNMUTE) {
+ mute_val = 0x1F;
+ mute_val1 = 0x00;
+ } else {
+ mute_val = 0x00;
+ mute_val1 = 0x40;
+ }
+ sc_access[0].mask = sc_access[1].mask = MASK0|MASK1|MASK2|MASK3|MASK4;
+ sc_access[0].value = sc_access[1].value = (u8)mute_val;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+ if (retval)
+ return retval;
+ pr_debug("sst: mute status = %d", snd_pmic_ops_mx.mute_status);
+ if (snd_pmic_ops_mx.mute_status == MUTE ||
+ snd_pmic_ops_mx.master_mute == MUTE)
+ return retval;
+
+ sc_access[0].reg_addr = VOL_CTRL_LT;
+ sc_access[1].reg_addr = VOL_CTRL_RT;
+ sc_access[0].mask = sc_access[1].mask = MASK6;
+ sc_access[0].value = sc_access[1].value = mute_val1;
+ if (snd_pmic_ops_mx.num_channel == 1)
+ sc_access[1].value = 0x40;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+}
+
+static int mx_power_up_pb(unsigned int port)
+{
+
+ int retval = 0;
+ struct sc_reg_access sc_access[3];
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ if ((is_aava()) && port == 1)
+ mx_init_playback_card();
+ retval = mx_enable_audiodac(MUTE);
+ if (retval)
+ return retval;
+
+ msleep(10);
+
+ sc_access[0].reg_addr = AS_CONFIG;
+ sc_access[0].mask = MASK7;
+ sc_access[0].value = 0x80;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = ENABLE_OPDEV_CTRL;
+ sc_access[0].mask = 0xff;
+ sc_access[0].value = 0x3C;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = ENABLE_DEV_AND_USE_XTAL;
+ sc_access[0].mask = 0x80;
+ sc_access[0].value = 0x80;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ return mx_enable_audiodac(UNMUTE);
+}
+
+static int mx_power_down_pb(void)
+{
+ struct sc_reg_access sc_access[3];
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ retval = mx_enable_audiodac(MUTE);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = ENABLE_OPDEV_CTRL;
+ sc_access[0].mask = MASK3|MASK2;
+ sc_access[0].value = 0x00;
+
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ return mx_enable_audiodac(UNMUTE);
+}
+
+static int mx_power_up_cp(unsigned int port)
+{
+ int retval = 0;
+ struct sc_reg_access sc_access[] = {
+ {ENABLE_DEV_AND_USE_XTAL, 0x80, MASK7},
+ {ENABLE_OPDEV_CTRL, 0x3, 0x3},
+ };
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ if (is_aava()) {
+ retval = mx_init_capture_card();
+ if (retval)
+ return retval;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ } else
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+}
+
+static int mx_power_down_cp(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {ENABLE_OPDEV_CTRL, 0x00, MASK1|MASK0},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+}
+
+static int mx_power_down(void)
+{
+ int retval = 0;
+ struct sc_reg_access sc_access[3];
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ retval = mx_enable_audiodac(MUTE);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = AS_CONFIG;
+ sc_access[0].mask = MASK7;
+ sc_access[0].value = 0x00;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = ENABLE_DEV_AND_USE_XTAL;
+ sc_access[0].mask = MASK7;
+ sc_access[0].value = 0x00;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ sc_access[0].reg_addr = ENABLE_OPDEV_CTRL;
+ sc_access[0].mask = MASK3|MASK2;
+ sc_access[0].value = 0x00;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ if (retval)
+ return retval;
+
+ return mx_enable_audiodac(UNMUTE);
+}
+
+static int mx_set_pcm_voice_params(void)
+{
+ int retval = 0;
+ struct sc_reg_access sc_access[] = {
+ {0x200, 0x80, 0x00},
+ {0x201, 0xC0, 0x00},
+ {0x202, 0x00, 0x00},
+ {0x203, 0x00, 0x00},
+ {0x204, 0x0e, 0x00},
+ {0x205, 0x20, 0x00},
+ {0x206, 0x8f, 0x00},
+ {0x207, 0x21, 0x00},
+ {0x208, 0x18, 0x00},
+ {0x209, 0x32, 0x00},
+ {0x20a, 0x00, 0x00},
+ {0x20b, 0x5A, 0x00},
+ {0x20c, 0xBE, 0x00},/* 0x00 -> 0xBE Koski */
+ {0x20d, 0x00, 0x00}, /* DAI2 'off' */
+ {0x20e, 0x40, 0x00},
+ {0x20f, 0x00, 0x00},
+ {0x210, 0x84, 0x00},
+ {0x211, 0x33, 0x00}, /* Voice filter */
+ {0x212, 0x00, 0x00},
+ {0x213, 0x00, 0x00},
+ {0x214, 0x41, 0x00},
+ {0x215, 0x00, 0x00},
+ {0x216, 0x00, 0x00},
+ {0x217, 0x20, 0x00},
+ {0x218, 0x00, 0x00},
+ {0x219, 0x00, 0x00},
+ {0x21a, 0x40, 0x00},
+ {0x21b, 0x40, 0x00},
+ {0x21c, 0x09, 0x00},
+ {0x21d, 0x09, 0x00},
+ {0x21e, 0x00, 0x00},
+ {0x21f, 0x00, 0x00},
+ {0x220, 0x00, 0x00}, /* Microphone configurations */
+ {0x221, 0x00, 0x00}, /* Microphone configurations */
+ {0x222, 0x50, 0x00}, /* Microphone configurations */
+ {0x223, 0x21, 0x00}, /* Microphone configurations */
+ {0x224, 0x00, 0x00},
+ {0x225, 0x80, 0x00},
+ {0xf9, 0x40, 0x00},
+ {0xfa, 0x19, 0x00},
+ {0xfb, 0x19, 0x00},
+ {0xfc, 0x12, 0x00},
+ {0xfd, 0x12, 0x00},
+ {0xfe, 0x00, 0x00},
+ };
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ pr_debug("sst: SST DBG mx_set_pcm_voice_params called\n");
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 44);
+}
+
+static int mx_set_pcm_audio_params(int sfreq, int word_size, int num_channel)
+{
+ int retval = 0;
+
+ if (!is_aava()) {
+ int config1 = 0, config2 = 0, filter = 0xB3;
+ struct sc_reg_access sc_access[5];
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ switch (sfreq) {
+ case 8000:
+ config1 = 0x10;
+ config2 = 0x00;
+ filter = 0x33;
+ break;
+ case 11025:
+ config1 = 0x16;
+ config2 = 0x0d;
+ break;
+ case 12000:
+ config1 = 0x18;
+ config2 = 0x00;
+ break;
+ case 16000:
+ config1 = 0x20;
+ config2 = 0x00;
+ break;
+ case 22050:
+ config1 = 0x2c;
+ config2 = 0x1a;
+ break;
+ case 24000:
+ config1 = 0x30;
+ config2 = 0x00;
+ break;
+ case 32000:
+ config1 = 0x40;
+ config2 = 0x00;
+ break;
+ case 44100:
+ config1 = 0x58;
+ config2 = 0x33;
+ break;
+ case 48000:
+ config1 = 0x60;
+ config2 = 0x00;
+ break;
+ }
+
+ snd_pmic_ops_mx.num_channel = num_channel;
+ /*mute the right channel if MONO*/
+ if (snd_pmic_ops_mx.num_channel == 1) {
+
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ sc_access[0].value = 0x40;
+ sc_access[0].mask = MASK6;
+
+ sc_access[1].reg_addr = 0x224;
+ sc_access[1].value = 0x05;
+ sc_access[1].mask = MASK0|MASK1|MASK2;
+
+ retval = sst_sc_reg_access(sc_access,
+ PMIC_READ_MODIFY, 2);
+ if (retval)
+ return retval;
+ } else {
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6;
+
+ sc_access[1].reg_addr = 0x224;
+ sc_access[1].value = 0x04;
+ sc_access[1].mask = MASK0|MASK1|MASK2;
+
+ retval = sst_sc_reg_access(sc_access,
+ PMIC_READ_MODIFY, 2);
+ if (retval)
+ return retval;
+ }
+ sc_access[0].reg_addr = 0x206;
+ sc_access[0].value = config1;
+ sc_access[1].reg_addr = 0x207;
+ sc_access[1].value = config2;
+
+ if (word_size == 16) {
+ sc_access[2].value = 0x51;
+ sc_access[3].value = 0x31;
+ } else if (word_size == 24) {
+ sc_access[2].value = 0x52;
+ sc_access[3].value = 0x92;
+ }
+
+ sc_access[2].reg_addr = 0x209;
+ sc_access[3].reg_addr = 0x20e;
+
+ sc_access[4].reg_addr = 0x211;
+ sc_access[4].value = filter;
+
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 5);
+ } else {
+ int config1 = 0, config2 = 0, filter = 0x00;
+ struct sc_reg_access sc_access[5];
+
+ pr_debug("sst: mx_set_pcm_audio_params - inside AAVA\n");
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ switch (sfreq) {
+ case 8000:
+ config1 = 0x20;
+ config2 = 0x0f;
+ filter = 0x33;
+ break;
+ case 11025:
+ config1 = 0x14;
+ config2 = 0xd8;
+ break;
+ case 12000:
+ config1 = 0x16;
+ config2 = 0xaf;
+ break;
+ case 16000:
+ config1 = 0x1e;
+ config2 = 0x3f;
+ break;
+ case 22050:
+ config1 = 0x29;
+ config2 = 0xaf;
+ break;
+ case 24000:
+ config1 = 0x2d;
+ config2 = 0x5f;
+ break;
+ case 32000:
+ config1 = 0x3c;
+ config2 = 0x7f;
+ break;
+ case 44100:
+ config1 = 0x53;
+ config2 = 0x5f;
+ break;
+ case 48000:
+ config1 = 0x5a;
+ config2 = 0xbe;
+ break;
+ }
+
+ snd_pmic_ops_mx.num_channel = num_channel;
+ /*mute the right channel if MONO*/
+ sc_access[0].reg_addr = 0x20b;
+ sc_access[0].value = config1;
+ sc_access[1].reg_addr = 0x20c;
+ sc_access[1].value = config2;
+ if (word_size == 16) {
+ sc_access[2].value = 0x51;
+ sc_access[3].value = 0x51;
+ } else if (word_size == 24) {
+ sc_access[2].value = 0x52;
+ sc_access[3].value = 0x92;
+
+ }
+
+ sc_access[2].reg_addr = 0x209;
+ sc_access[3].reg_addr = 0x20e;
+ sc_access[4].reg_addr = 0x211;
+ sc_access[4].value = filter;
+
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, 5);
+ }
+ return 0;
+}
+
+static int mx_set_selected_output_dev(u8 dev_id)
+{
+ struct sc_reg_access sc_access[2];
+ int num_reg = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+ pr_debug("sst: mx_set_selected_output_dev dev_id:0x%x\n", dev_id);
+ snd_pmic_ops_mx.output_dev_id = dev_id;
+ switch (dev_id) {
+ case STEREO_HEADPHONE:
+ sc_access[0].reg_addr = 0xFF;
+ sc_access[0].value = 0x8C;
+ sc_access[0].mask =
+ MASK2|MASK3|MASK5|MASK6|MASK4;
+
+ num_reg = 1;
+ break;
+ case MONO_EARPIECE:
+ case INTERNAL_SPKR:
+ sc_access[0].reg_addr = 0xFF;
+ sc_access[0].value = 0xb0;
+ sc_access[0].mask = MASK2|MASK3|MASK5|MASK6|MASK4;
+
+ num_reg = 1;
+ break;
+ case RECEIVER:
+ pr_debug("sst: RECEIVER Koski selected\n");
+
+ /* configuration - AS enable, receiver enable */
+ sc_access[0].reg_addr = 0xFF;
+ sc_access[0].value = 0x81;
+ sc_access[0].mask = 0xff;
+
+ num_reg = 1;
+ break;
+ default:
+ pr_err("sst: Not a valid output dev\n");
+ return 0;
+ }
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, num_reg);
+}
+
+
+static int mx_set_voice_port(int status)
+{
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ if (status == ACTIVATE)
+ retval = mx_set_pcm_voice_params();
+
+ return retval;
+}
+
+static int mx_set_audio_port(int status)
+{
+ int retval = 0;
+ if (is_aava()) {
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT)
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ if (status == ACTIVATE) {
+ mx_init_card();
+ mx_set_selected_output_dev
+ (snd_pmic_ops_mx.output_dev_id);
+ }
+ }
+ return retval;
+
+}
+
+static int mx_set_selected_input_dev(u8 dev_id)
+{
+ struct sc_reg_access sc_access[2];
+ int num_reg = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ snd_pmic_ops_mx.input_dev_id = dev_id;
+ pr_debug("sst: mx_set_selected_input_dev dev_id:0x%x\n", dev_id);
+
+ switch (dev_id) {
+ case AMIC:
+ sc_access[0].reg_addr = 0x223;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK7|MASK6|MASK5|MASK4|MASK0;
+ sc_access[1].reg_addr = 0x222;
+ sc_access[1].value = 0x50;
+ sc_access[1].mask = MASK7|MASK6|MASK5|MASK4;
+ num_reg = 2;
+ break;
+
+ case HS_MIC:
+ sc_access[0].reg_addr = 0x223;
+ sc_access[0].value = 0x20;
+ sc_access[0].mask = MASK7|MASK6|MASK5|MASK4|MASK0;
+ sc_access[1].reg_addr = 0x222;
+ sc_access[1].value = 0x51;
+ sc_access[1].mask = MASK7|MASK6|MASK5|MASK4;
+ num_reg = 2;
+ break;
+ case DMIC:
+ sc_access[1].reg_addr = 0x222;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = MASK7|MASK6|MASK5|MASK4|MASK0;
+ sc_access[0].reg_addr = 0x223;
+ sc_access[0].value = 0x20;
+ sc_access[0].mask = MASK7|MASK6|MASK5|MASK4|MASK0;
+ num_reg = 2;
+ break;
+ }
+ return sst_sc_reg_access(sc_access, PMIC_WRITE, num_reg);
+}
+
+static int mx_set_mute(int dev_id, u8 value)
+{
+ struct sc_reg_access sc_access[5];
+ int num_reg = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+
+
+ pr_debug("sst: set_mute dev_id:0x%x , value:%d\n", dev_id, value);
+
+ switch (dev_id) {
+ case PMIC_SND_DMIC_MUTE:
+ case PMIC_SND_AMIC_MUTE:
+ case PMIC_SND_HP_MIC_MUTE:
+ sc_access[0].reg_addr = 0x220;
+ sc_access[1].reg_addr = 0x221;
+ sc_access[2].reg_addr = 0x223;
+ if (value == MUTE) {
+ sc_access[0].value = 0x00;
+ sc_access[1].value = 0x00;
+ if (snd_pmic_ops_mx.input_dev_id == DMIC)
+ sc_access[2].value = 0x00;
+ else
+ sc_access[2].value = 0x20;
+ } else {
+ sc_access[0].value = 0x20;
+ sc_access[1].value = 0x20;
+ if (snd_pmic_ops_mx.input_dev_id == DMIC)
+ sc_access[2].value = 0x20;
+ else
+ sc_access[2].value = 0x00;
+ }
+ sc_access[0].mask = MASK5|MASK6;
+ sc_access[1].mask = MASK5|MASK6;
+ sc_access[2].mask = MASK5|MASK6;
+ num_reg = 3;
+ break;
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ case PMIC_SND_LEFT_HP_MUTE:
+ sc_access[0].reg_addr = VOL_CTRL_LT;
+ if (value == MUTE)
+ sc_access[0].value = 0x40;
+ else
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6;
+ num_reg = 1;
+ snd_pmic_ops_mx.mute_status = value;
+ break;
+ case PMIC_SND_RIGHT_SPEAKER_MUTE:
+ case PMIC_SND_RIGHT_HP_MUTE:
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ if (snd_pmic_ops_mx.num_channel == 1)
+ value = MUTE;
+ if (value == MUTE)
+ sc_access[0].value = 0x40;
+ else
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6;
+ num_reg = 1;
+ snd_pmic_ops_mx.mute_status = value;
+ break;
+ case PMIC_SND_MUTE_ALL:
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ sc_access[1].reg_addr = VOL_CTRL_LT;
+ sc_access[2].reg_addr = 0x220;
+ sc_access[3].reg_addr = 0x221;
+ sc_access[4].reg_addr = 0x223;
+ snd_pmic_ops_mx.master_mute = value;
+ if (value == MUTE) {
+ sc_access[0].value = sc_access[1].value = 0x40;
+ sc_access[2].value = 0x00;
+ sc_access[3].value = 0x00;
+ if (snd_pmic_ops_mx.input_dev_id == DMIC)
+ sc_access[4].value = 0x00;
+ else
+ sc_access[4].value = 0x20;
+
+ } else {
+ sc_access[0].value = sc_access[1].value = 0x00;
+ sc_access[2].value = sc_access[3].value = 0x20;
+ sc_access[4].value = 0x20;
+ if (snd_pmic_ops_mx.input_dev_id == DMIC)
+ sc_access[4].value = 0x20;
+ else
+ sc_access[4].value = 0x00;
+
+
+ }
+ if (snd_pmic_ops_mx.num_channel == 1)
+ sc_access[0].value = 0x40;
+ sc_access[0].mask = sc_access[1].mask = MASK6;
+ sc_access[2].mask = MASK5|MASK6;
+ sc_access[3].mask = MASK5|MASK6|MASK2|MASK4;
+ sc_access[4].mask = MASK5|MASK6|MASK4;
+
+ num_reg = 5;
+ break;
+ case PMIC_SND_RECEIVER_MUTE:
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ if (value == MUTE)
+ sc_access[0].value = 0x40;
+ else
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK6;
+ num_reg = 1;
+ break;
+ }
+
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, num_reg);
+}
+
+static int mx_set_vol(int dev_id, int value)
+{
+ struct sc_reg_access sc_access[2] = {{0},};
+ int num_reg = 0;
+ int retval = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ pr_debug("sst: set_vol dev_id:0x%x ,value:%d\n", dev_id, value);
+ switch (dev_id) {
+ case PMIC_SND_RECEIVER_VOL:
+ return 0;
+ break;
+ case PMIC_SND_CAPTURE_VOL:
+ sc_access[0].reg_addr = 0x220;
+ sc_access[1].reg_addr = 0x221;
+ sc_access[0].value = sc_access[1].value = -value;
+ sc_access[0].mask = sc_access[1].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4);
+ num_reg = 2;
+ break;
+ case PMIC_SND_LEFT_PB_VOL:
+ sc_access[0].value = -value;
+ sc_access[0].reg_addr = VOL_CTRL_LT;
+ sc_access[0].mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ num_reg = 1;
+ break;
+ case PMIC_SND_RIGHT_PB_VOL:
+ sc_access[0].value = -value;
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ sc_access[0].mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ if (snd_pmic_ops_mx.num_channel == 1) {
+ sc_access[0].value = 0x40;
+ sc_access[0].mask = MASK6;
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ }
+ num_reg = 1;
+ break;
+ }
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, num_reg);
+}
+
+static int mx_get_mute(int dev_id, u8 *value)
+{
+ struct sc_reg_access sc_access[4] = {{0},};
+ int retval = 0, num_reg = 0, mask = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ switch (dev_id) {
+ case PMIC_SND_DMIC_MUTE:
+ case PMIC_SND_AMIC_MUTE:
+ case PMIC_SND_HP_MIC_MUTE:
+ sc_access[0].reg_addr = 0x220;
+ mask = MASK5|MASK6;
+ num_reg = 1;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, num_reg);
+ if (retval)
+ return retval;
+ *value = sc_access[0].value & mask;
+ if (*value)
+ *value = UNMUTE;
+ else
+ *value = MUTE;
+ return retval;
+ case PMIC_SND_LEFT_HP_MUTE:
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ sc_access[0].reg_addr = VOL_CTRL_LT;
+ num_reg = 1;
+ mask = MASK6;
+ break;
+ case PMIC_SND_RIGHT_HP_MUTE:
+ case PMIC_SND_RIGHT_SPEAKER_MUTE:
+ sc_access[0].reg_addr = VOL_CTRL_RT;
+ num_reg = 1;
+ mask = MASK6;
+ break;
+ }
+ retval = sst_sc_reg_access(sc_access, PMIC_READ, num_reg);
+ if (retval)
+ return retval;
+ *value = sc_access[0].value & mask;
+ if (*value)
+ *value = MUTE;
+ else
+ *value = UNMUTE;
+ return retval;
+}
+
+static int mx_get_vol(int dev_id, int *value)
+{
+ struct sc_reg_access sc_access = {0,};
+ int retval = 0, mask = 0, num_reg = 0;
+
+ if (snd_pmic_ops_mx.card_status == SND_CARD_UN_INIT) {
+ retval = mx_init_card();
+ if (retval)
+ return retval;
+ }
+ switch (dev_id) {
+ case PMIC_SND_CAPTURE_VOL:
+ sc_access.reg_addr = 0x220;
+ mask = MASK0|MASK1|MASK2|MASK3|MASK4;
+ num_reg = 1;
+ break;
+ case PMIC_SND_LEFT_PB_VOL:
+ sc_access.reg_addr = VOL_CTRL_LT;
+ mask = MASK0|MASK1|MASK2|MASK3|MASK4|MASK5;
+ num_reg = 1;
+ break;
+ case PMIC_SND_RIGHT_PB_VOL:
+ sc_access.reg_addr = VOL_CTRL_RT;
+ mask = MASK0|MASK1|MASK2|MASK3|MASK4|MASK5;
+ num_reg = 1;
+ break;
+ }
+ retval = sst_sc_reg_access(&sc_access, PMIC_READ, num_reg);
+ if (retval)
+ return retval;
+ *value = -(sc_access.value & mask);
+ pr_debug("sst: get volume value extracted %d\n", *value);
+ return retval;
+}
+
+struct snd_pmic_ops snd_pmic_ops_mx = {
+ .set_input_dev = mx_set_selected_input_dev,
+ .set_output_dev = mx_set_selected_output_dev,
+ .set_mute = mx_set_mute,
+ .get_mute = mx_get_mute,
+ .set_vol = mx_set_vol,
+ .get_vol = mx_get_vol,
+ .init_card = mx_init_card,
+ .set_pcm_audio_params = mx_set_pcm_audio_params,
+ .set_pcm_voice_params = mx_set_pcm_voice_params,
+ .set_voice_port = mx_set_voice_port,
+ .set_audio_port = mx_set_audio_port,
+ .power_up_pmic_pb = mx_power_up_pb,
+ .power_up_pmic_cp = mx_power_up_cp,
+ .power_down_pmic_pb = mx_power_down_pb,
+ .power_down_pmic_cp = mx_power_down_cp,
+ .power_down_pmic = mx_power_down,
+};
+
diff --git a/drivers/staging/intel_sst/intelmid_v2_control.c b/drivers/staging/intel_sst/intelmid_v2_control.c
new file mode 100644
index 0000000..3a7de76
--- /dev/null
+++ b/drivers/staging/intel_sst/intelmid_v2_control.c
@@ -0,0 +1,1001 @@
+/*
+ * intelmid_v2_control.c - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-10 Intel Corp
+ * Authors: Vinod Koul <vinod.koul(a)intel.com>
+ * Harsha Priya <priya.harsha(a)intel.com>
+ * KP Jeeja <jeeja.kp(a)intel.com>
+ * Dharageswari R <dharageswari.r(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file contains the control operations of vendor 3
+ */
+
+#include <linux/pci.h>
+#include <linux/file.h>
+#include "intel_sst.h"
+#include "intelmid_snd_control.h"
+
+enum reg_v3 {
+ VAUDIOCNT = 0x51,
+ VOICEPORT1 = 0x100,
+ VOICEPORT2 = 0x101,
+ AUDIOPORT1 = 0x102,
+ AUDIOPORT2 = 0x103,
+ ADCSAMPLERATE = 0x104,
+ DMICCTRL1 = 0x105,
+ DMICCTRL2 = 0x106,
+ MICCTRL = 0x107,
+ MICSELVOL = 0x108,
+ LILSEL = 0x109,
+ LIRSEL = 0x10a,
+ VOICEVOL = 0x10b,
+ AUDIOLVOL = 0x10c,
+ AUDIORVOL = 0x10d,
+ LMUTE = 0x10e,
+ RMUTE = 0x10f,
+ POWERCTRL1 = 0x110,
+ POWERCTRL2 = 0x111,
+ DRVPOWERCTRL = 0x112,
+ VREFPLL = 0x113,
+ PCMBUFCTRL = 0x114,
+ SOFTMUTE = 0x115,
+ DTMFPATH = 0x116,
+ DTMFVOL = 0x117,
+ DTMFFREQ = 0x118,
+ DTMFHFREQ = 0x119,
+ DTMFLFREQ = 0x11a,
+ DTMFCTRL = 0x11b,
+ DTMFASON = 0x11c,
+ DTMFASOFF = 0x11d,
+ DTMFASINUM = 0x11e,
+ CLASSDVOL = 0x11f,
+ VOICEDACAVOL = 0x120,
+ AUDDACAVOL = 0x121,
+ LOMUTEVOL = 0x122,
+ HPLVOL = 0x123,
+ HPRVOL = 0x124,
+ MONOVOL = 0x125,
+ LINEOUTMIXVOL = 0x126,
+ EPMIXVOL = 0x127,
+ LINEOUTLSEL = 0x128,
+ LINEOUTRSEL = 0x129,
+ EPMIXOUTSEL = 0x12a,
+ HPLMIXSEL = 0x12b,
+ HPRMIXSEL = 0x12c,
+ LOANTIPOP = 0x12d,
+};
+
+/****
+ * nc_init_card - initilize the sound card
+ *
+ * This initilizes the audio paths to know values in case of this sound card
+ */
+static int nc_init_card(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {VAUDIOCNT, 0x25, 0},
+ {VOICEPORT1, 0x00, 0},
+ {VOICEPORT2, 0x00, 0},
+ {AUDIOPORT1, 0x98, 0},
+ {AUDIOPORT2, 0x09, 0},
+ {AUDIOLVOL, 0x00, 0},
+ {AUDIORVOL, 0x00, 0},
+ {LMUTE, 0x03, 0},
+ {RMUTE, 0x03, 0},
+ {POWERCTRL1, 0x00, 0},
+ {POWERCTRL2, 0x00, 0},
+ {DRVPOWERCTRL, 0x00, 0},
+ {VREFPLL, 0x10, 0},
+ {HPLMIXSEL, 0xee, 0},
+ {HPRMIXSEL, 0xf6, 0},
+ {PCMBUFCTRL, 0x0, 0},
+ {VOICEVOL, 0x0e, 0},
+ {HPLVOL, 0x06, 0},
+ {HPRVOL, 0x06, 0},
+ {MICCTRL, 0x41, 0x00},
+ {ADCSAMPLERATE, 0x8B, 0x00},
+ {MICSELVOL, 0x5B, 0x00},
+ {LILSEL, 0x06, 0},
+ {LIRSEL, 0x46, 0},
+ {LOANTIPOP, 0x00, 0},
+ {DMICCTRL1, 0x40, 0},
+ };
+ snd_pmic_ops_nc.card_status = SND_CARD_INIT_DONE;
+ snd_pmic_ops_nc.master_mute = UNMUTE;
+ snd_pmic_ops_nc.mute_status = UNMUTE;
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 26);
+ pr_debug("sst: init complete!!\n");
+ return 0;
+}
+
+static int nc_enable_audiodac(int value)
+{
+ struct sc_reg_access sc_access[3];
+ int mute_val = 0;
+
+ if (snd_pmic_ops_nc.mute_status == MUTE)
+ return 0;
+
+ if (((snd_pmic_ops_nc.output_dev_id == MONO_EARPIECE) ||
+ (snd_pmic_ops_nc.output_dev_id == INTERNAL_SPKR)) &&
+ (value == UNMUTE))
+ return 0;
+ if (value == UNMUTE) {
+ /* unmute the system, set the 7th bit to zero */
+ mute_val = 0x00;
+ } else {
+ /* MUTE:Set the seventh bit */
+ mute_val = 0x04;
+
+ }
+ sc_access[0].reg_addr = LMUTE;
+ sc_access[1].reg_addr = RMUTE;
+ sc_access[0].mask = sc_access[1].mask = MASK2;
+ sc_access[0].value = sc_access[1].value = mute_val;
+
+ if (snd_pmic_ops_nc.num_channel == 1)
+ sc_access[1].value = 0x04;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+}
+
+static int nc_power_up_pb(unsigned int port)
+{
+ struct sc_reg_access sc_access[7];
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+ if (port == 0xFF)
+ return 0;
+ nc_enable_audiodac(MUTE);
+ msleep(30);
+
+ pr_debug("sst: powering up pb....\n");
+
+ sc_access[0].reg_addr = VAUDIOCNT;
+ sc_access[0].value = 0x27;
+ sc_access[0].mask = 0x27;
+ sc_access[1].reg_addr = VREFPLL;
+ if (port == 0) {
+ sc_access[1].value = 0x3A;
+ sc_access[1].mask = 0x3A;
+ } else if (port == 1) {
+ sc_access[1].value = 0x35;
+ sc_access[1].mask = 0x35;
+ }
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+
+
+ sc_access[0].reg_addr = POWERCTRL1;
+ if (port == 0) {
+ sc_access[0].value = 0x40;
+ sc_access[0].mask = 0x40;
+ } else if (port == 1) {
+ sc_access[0].value = 0x01;
+ sc_access[0].mask = 0x01;
+ }
+ sc_access[1].reg_addr = POWERCTRL2;
+ sc_access[1].value = 0x0C;
+ sc_access[1].mask = 0x0C;
+
+ sc_access[2].reg_addr = DRVPOWERCTRL;
+ sc_access[2].value = 0x86;
+ sc_access[2].mask = 0x86;
+
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 3);
+
+ msleep(30);
+
+ return nc_enable_audiodac(UNMUTE);
+
+}
+
+static int nc_power_up_cp(unsigned int port)
+{
+ struct sc_reg_access sc_access[5];
+ int retval = 0;
+
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+
+ pr_debug("sst: powering up cp....\n");
+
+ if (port == 0xFF)
+ return 0;
+ sc_access[0].reg_addr = VAUDIOCNT;
+ sc_access[0].value = 0x27;
+ sc_access[0].mask = 0x27;
+ sc_access[1].reg_addr = VREFPLL;
+ if (port == 0) {
+ sc_access[1].value = 0x3E;
+ sc_access[1].mask = 0x3E;
+ } else if (port == 1) {
+ sc_access[1].value = 0x35;
+ sc_access[1].mask = 0x35;
+ }
+
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+
+ sc_access[0].reg_addr = POWERCTRL1;
+ if (port == 0) {
+ sc_access[0].value = 0xB4;
+ sc_access[0].mask = 0xB4;
+ } else if (port == 1) {
+ sc_access[0].value = 0xBF;
+ sc_access[0].mask = 0xBF;
+ }
+ sc_access[1].reg_addr = POWERCTRL2;
+ if (port == 0) {
+ sc_access[1].value = 0x0C;
+ sc_access[1].mask = 0x0C;
+ } else if (port == 1) {
+ sc_access[1].value = 0x02;
+ sc_access[1].mask = 0x02;
+ }
+
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+}
+
+static int nc_power_down(void)
+{
+ int retval = 0;
+ struct sc_reg_access sc_access[5];
+
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+ nc_enable_audiodac(MUTE);
+
+
+ pr_debug("sst: powering dn nc_power_down ....\n");
+
+ msleep(30);
+
+ sc_access[0].reg_addr = DRVPOWERCTRL;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = 0x00;
+
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 1);
+
+ sc_access[0].reg_addr = POWERCTRL1;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = 0x00;
+
+ sc_access[1].reg_addr = POWERCTRL2;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = 0x00;
+
+
+
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 2);
+
+ msleep(30);
+ sc_access[0].reg_addr = VREFPLL;
+ sc_access[0].value = 0x10;
+ sc_access[0].mask = 0x10;
+
+ sc_access[1].reg_addr = VAUDIOCNT;
+ sc_access[1].value = 0x25;
+ sc_access[1].mask = 0x25;
+
+
+ retval = sst_sc_reg_access(sc_access, PMIC_WRITE, 2);
+
+ msleep(30);
+ return nc_enable_audiodac(UNMUTE);
+}
+
+static int nc_power_down_pb(void)
+{
+
+ int retval = 0;
+ struct sc_reg_access sc_access[5];
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ pr_debug("sst: powering dn pb....\n");
+
+ nc_enable_audiodac(MUTE);
+
+
+ msleep(30);
+
+
+ sc_access[0].reg_addr = DRVPOWERCTRL;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = 0x00;
+
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 1);
+
+ msleep(30);
+
+ sc_access[0].reg_addr = POWERCTRL1;
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = 0x41;
+
+ sc_access[1].reg_addr = POWERCTRL2;
+ sc_access[1].value = 0x00;
+ sc_access[1].mask = 0x0C;
+
+ sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2);
+
+ msleep(30);
+
+ return nc_enable_audiodac(UNMUTE);
+
+
+}
+
+static int nc_power_down_cp(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {POWERCTRL1, 0x00, 0xBE},
+ {POWERCTRL2, 0x00, 0x02},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ pr_debug("sst: powering dn cp....\n");
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+}
+
+static int nc_set_pcm_voice_params(void)
+{
+ struct sc_reg_access sc_access[] = {
+ {0x100, 0xD5, 0},
+ {0x101, 0x08, 0},
+ {0x104, 0x03, 0},
+ {0x107, 0x10, 0},
+ {0x10B, 0x0E, 0},
+ {0x10E, 0x03, 0},
+ {0x10F, 0x03, 0},
+ {0x114, 0x13, 0},
+ {0x115, 0x00, 0},
+ {0x128, 0xFE, 0},
+ {0x129, 0xFE, 0},
+ {0x12A, 0xFE, 0},
+ {0x12B, 0xDE, 0},
+ {0x12C, 0xDE, 0},
+ };
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 14);
+ pr_debug("sst: Voice parameters set successfully!!\n");
+ return 0;
+}
+
+
+static int nc_set_pcm_audio_params(int sfreq, int word_size, int num_channel)
+{
+ int config2 = 0;
+ struct sc_reg_access sc_access;
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ switch (sfreq) {
+ case 8000:
+ config2 = 0x00;
+ break;
+ case 11025:
+ config2 = 0x01;
+ break;
+ case 12000:
+ config2 = 0x02;
+ break;
+ case 16000:
+ config2 = 0x03;
+ break;
+ case 22050:
+ config2 = 0x04;
+ break;
+ case 24000:
+ config2 = 0x05;
+ break;
+ case 32000:
+ config2 = 0x07;
+ break;
+ case 44100:
+ config2 = 0x08;
+ break;
+ case 48000:
+ config2 = 0x09;
+ break;
+ }
+
+ snd_pmic_ops_nc.num_channel = num_channel;
+ if (snd_pmic_ops_nc.num_channel == 1) {
+
+ sc_access.value = 0x07;
+ sc_access.reg_addr = RMUTE;
+ pr_debug("sst: RIGHT_HP_MUTE value%d\n", sc_access.value);
+ sc_access.mask = MASK2;
+ sst_sc_reg_access(&sc_access, PMIC_READ_MODIFY, 1);
+ } else {
+ sc_access.value = 0x00;
+ sc_access.reg_addr = RMUTE;
+ pr_debug("sst: RIGHT_HP_MUTE value %d\n", sc_access.value);
+ sc_access.mask = MASK2;
+ sst_sc_reg_access(&sc_access, PMIC_READ_MODIFY, 1);
+
+
+ }
+
+ pr_debug("sst: word_size = %d\n", word_size);
+
+ if (word_size == 24) {
+ sc_access.reg_addr = AUDIOPORT2;
+ sc_access.value = config2 | 0x10;
+ sc_access.mask = 0x1F;
+ } else {
+ sc_access.value = config2;
+ sc_access.mask = 0x1F;
+ sc_access.reg_addr = AUDIOPORT2;
+ }
+ sst_sc_reg_access(&sc_access, PMIC_READ_MODIFY, 1);
+
+ pr_debug("sst: word_size = %d\n", word_size);
+ sc_access.reg_addr = AUDIOPORT1;
+ sc_access.mask = MASK5|MASK4|MASK1|MASK0;
+ if (word_size == 16)
+ sc_access.value = 0x98;
+ else if (word_size == 24)
+ sc_access.value = 0xAB;
+
+ return sst_sc_reg_access(&sc_access, PMIC_READ_MODIFY, 1);
+
+
+
+}
+
+static int nc_set_selected_output_dev(u8 value)
+{
+ struct sc_reg_access sc_access_HP[] = {
+ {LMUTE, 0x02, 0x06},
+ {RMUTE, 0x02, 0x06}
+ };
+ struct sc_reg_access sc_access_IS[] = {
+ {LMUTE, 0x04, 0x06},
+ {RMUTE, 0x04, 0x06}
+ };
+ int retval = 0;
+
+ snd_pmic_ops_nc.output_dev_id = value;
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+ pr_debug("sst: nc set selected output:%d\n", value);
+ switch (value) {
+ case STEREO_HEADPHONE:
+ retval = sst_sc_reg_access(sc_access_HP, PMIC_WRITE, 2);
+ break;
+ case INTERNAL_SPKR:
+ retval = sst_sc_reg_access(sc_access_IS, PMIC_WRITE, 2);
+ break;
+ default:
+ pr_err("sst: rcvd illegal request: %d\n", value);
+ return -EINVAL;
+ }
+ return retval;
+}
+
+static int nc_audio_init(void)
+{
+ struct sc_reg_access sc_acces, sc_access[] = {
+ {0x100, 0x00, 0},
+ {0x101, 0x00, 0},
+ {0x104, 0x8B, 0},
+ {0x107, 0x11, 0},
+ {0x10B, 0x0E, 0},
+ {0x114, 0x00, 0},
+ {0x115, 0x00, 0},
+ {0x128, 0x00, 0},
+ {0x129, 0x00, 0},
+ {0x12A, 0x00, 0},
+ {0x12B, 0xee, 0},
+ {0x12C, 0xf6, 0},
+ };
+
+ sst_sc_reg_access(sc_access, PMIC_WRITE, 12);
+ pr_debug("sst: Audio Init successfully!!\n");
+
+ /*set output device */
+ nc_set_selected_output_dev(snd_pmic_ops_nc.output_dev_id);
+
+ if (snd_pmic_ops_nc.num_channel == 1) {
+ sc_acces.value = 0x07;
+ sc_acces.reg_addr = RMUTE;
+ pr_debug("sst: RIGHT_HP_MUTE value%d\n", sc_acces.value);
+ sc_acces.mask = MASK2;
+ sst_sc_reg_access(&sc_acces, PMIC_READ_MODIFY, 1);
+ } else {
+ sc_acces.value = 0x00;
+ sc_acces.reg_addr = RMUTE;
+ pr_debug("sst: RIGHT_HP_MUTE value%d\n", sc_acces.value);
+ sc_acces.mask = MASK2;
+ sst_sc_reg_access(&sc_acces, PMIC_READ_MODIFY, 1);
+ }
+
+ return 0;
+}
+
+static int nc_set_audio_port(int status)
+{
+ struct sc_reg_access sc_access[2] = {{0,},};
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ if (status == DEACTIVATE) {
+ /* Deactivate audio port-tristate and power */
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK4|MASK5;
+ sc_access[0].reg_addr = AUDIOPORT1;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ } else if (status == ACTIVATE) {
+ /* activate audio port */
+ nc_audio_init();
+ sc_access[0].value = 0x10;
+ sc_access[0].mask = MASK4|MASK5 ;
+ sc_access[0].reg_addr = AUDIOPORT1;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ } else
+ return -EINVAL;
+
+}
+
+static int nc_set_voice_port(int status)
+{
+ struct sc_reg_access sc_access[2] = {{0,},};
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ if (status == DEACTIVATE) {
+ /* Activate Voice port */
+ sc_access[0].value = 0x00;
+ sc_access[0].mask = MASK4;
+ sc_access[0].reg_addr = VOICEPORT1;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ } else if (status == ACTIVATE) {
+ /* Deactivate voice port */
+ nc_set_pcm_voice_params();
+ sc_access[0].value = 0x10;
+ sc_access[0].mask = MASK4;
+ sc_access[0].reg_addr = VOICEPORT1;
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ } else
+ return -EINVAL;
+}
+
+static int nc_set_mute(int dev_id, u8 value)
+{
+ struct sc_reg_access sc_access[3];
+ u8 mute_val, cap_mute;
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ pr_debug("sst: set device id::%d, value %d\n", dev_id, value);
+
+ switch (dev_id) {
+ case PMIC_SND_MUTE_ALL:
+ pr_debug("sst: PMIC_SND_MUTE_ALL value %d\n", value);
+ snd_pmic_ops_nc.mute_status = value;
+ snd_pmic_ops_nc.master_mute = value;
+ if (value == UNMUTE) {
+ /* unmute the system, set the 7th bit to zero */
+ mute_val = cap_mute = 0x00;
+ } else {
+ /* MUTE:Set the seventh bit */
+ mute_val = 0x80;
+ cap_mute = 0x40;
+ }
+ sc_access[0].reg_addr = AUDIOLVOL;
+ sc_access[1].reg_addr = AUDIORVOL;
+ sc_access[0].mask = sc_access[1].mask = MASK7;
+ sc_access[0].value = sc_access[1].value = mute_val;
+ if (snd_pmic_ops_nc.num_channel == 1)
+ sc_access[1].value = 0x80;
+ if (!sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 2)) {
+ sc_access[0].reg_addr = 0x109;
+ sc_access[1].reg_addr = 0x10a;
+ sc_access[2].reg_addr = 0x105;
+ sc_access[0].mask = sc_access[1].mask =
+ sc_access[2].mask = MASK6;
+ sc_access[0].value = sc_access[1].value =
+ sc_access[2].value = cap_mute;
+
+ if ((snd_pmic_ops_nc.input_dev_id == AMIC) ||
+ (snd_pmic_ops_nc.input_dev_id == DMIC))
+ sc_access[1].value = 0x40;
+ if (snd_pmic_ops_nc.input_dev_id == HS_MIC)
+ sc_access[0].value = 0x40;
+ retval = sst_sc_reg_access(sc_access,
+ PMIC_READ_MODIFY, 3);
+ }
+ break;
+ case PMIC_SND_HP_MIC_MUTE:
+ pr_debug("sst: PMIC_SND_HPMIC_MUTE value %d\n", value);
+ if (value == UNMUTE) {
+ /* unmute the system, set the 6th bit to one */
+ sc_access[0].value = 0x00;
+ } else {
+ /* mute the system, reset the 6th bit to zero */
+ sc_access[0].value = 0x40;
+ }
+ sc_access[0].reg_addr = LIRSEL;
+ sc_access[0].mask = MASK6;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ break;
+ case PMIC_SND_AMIC_MUTE:
+ pr_debug("sst: PMIC_SND_AMIC_MUTE value %d\n", value);
+ if (value == UNMUTE) {
+ /* unmute the system, set the 6th bit to one */
+ sc_access[0].value = 0x00;
+ } else {
+ /* mute the system, reset the 6th bit to zero */
+ sc_access[0].value = 0x40;
+ }
+ sc_access[0].reg_addr = LILSEL;
+ sc_access[0].mask = MASK6;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ break;
+
+ case PMIC_SND_DMIC_MUTE:
+ pr_debug("sst: INPUT_MUTE_DMIC value%d\n", value);
+ if (value == UNMUTE) {
+ /* unmute the system, set the 6th bit to one */
+ sc_access[1].value = 0x00;
+ sc_access[0].value = 0x00;
+ } else {
+ /* mute the system, reset the 6th bit to zero */
+ sc_access[1].value = 0x40;
+ sc_access[0].value = 0x40;
+ }
+ sc_access[0].reg_addr = DMICCTRL1;
+ sc_access[0].mask = MASK6;
+ sc_access[1].reg_addr = LILSEL;
+ sc_access[1].mask = MASK6;
+ retval = sst_sc_reg_access(sc_access,
+ PMIC_READ_MODIFY, 2);
+ break;
+
+ case PMIC_SND_LEFT_HP_MUTE:
+ case PMIC_SND_RIGHT_HP_MUTE:
+ snd_pmic_ops_nc.mute_status = value;
+ if (value == UNMUTE)
+ sc_access[0].value = 0x0;
+ else
+ sc_access[0].value = 0x04;
+
+ if (dev_id == PMIC_SND_LEFT_HP_MUTE) {
+ sc_access[0].reg_addr = LMUTE;
+ pr_debug("sst: LEFT_HP_MUTE value %d\n",
+ sc_access[0].value);
+ } else {
+ if (snd_pmic_ops_nc.num_channel == 1)
+ sc_access[0].value = 0x04;
+ sc_access[0].reg_addr = RMUTE;
+ pr_debug("sst: RIGHT_HP_MUTE value %d\n",
+ sc_access[0].value);
+ }
+ sc_access[0].mask = MASK2;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ break;
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ case PMIC_SND_RIGHT_SPEAKER_MUTE:
+ if (value == UNMUTE)
+ sc_access[0].value = 0x00;
+ else
+ sc_access[0].value = 0x03;
+ sc_access[0].reg_addr = LMUTE;
+ pr_debug("sst: SPEAKER_MUTE %d\n", sc_access[0].value);
+ sc_access[0].mask = MASK1;
+ retval = sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return retval ;
+
+}
+
+static int nc_set_vol(int dev_id, int value)
+{
+ struct sc_reg_access sc_access[3];
+ int retval = 0, entries = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ pr_debug("sst: set volume:%d\n", dev_id);
+ switch (dev_id) {
+ case PMIC_SND_CAPTURE_VOL:
+ pr_debug("sst: PMIC_SND_CAPTURE_VOL:value::%d\n", value);
+ sc_access[0].value = sc_access[1].value =
+ sc_access[2].value = -value;
+ sc_access[0].mask = sc_access[1].mask = sc_access[2].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ sc_access[0].reg_addr = 0x10a;
+ sc_access[1].reg_addr = 0x109;
+ sc_access[2].reg_addr = 0x105;
+ entries = 3;
+ break;
+
+ case PMIC_SND_LEFT_PB_VOL:
+ pr_debug("sst: PMIC_SND_LEFT_HP_VOL %d\n", value);
+ sc_access[0].value = -value;
+ sc_access[0].reg_addr = AUDIOLVOL;
+ sc_access[0].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5|MASK6);
+ entries = 1;
+ break;
+
+ case PMIC_SND_RIGHT_PB_VOL:
+ pr_debug("sst: PMIC_SND_RIGHT_HP_VOL value %d\n", value);
+ if (snd_pmic_ops_nc.num_channel == 1) {
+ sc_access[0].value = 0x04;
+ sc_access[0].reg_addr = RMUTE;
+ sc_access[0].mask = MASK2;
+ } else {
+ sc_access[0].value = -value;
+ sc_access[0].reg_addr = AUDIORVOL;
+ sc_access[0].mask =
+ (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5|MASK6);
+ entries = 1;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, entries);
+}
+
+static int nc_set_selected_input_dev(u8 value)
+{
+ struct sc_reg_access sc_access[6];
+ u8 num_val;
+ int retval = 0;
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+ snd_pmic_ops_nc.input_dev_id = value;
+
+ pr_debug("sst: nc set selected input:%d\n", value);
+
+ switch (value) {
+ case AMIC:
+ pr_debug("sst: Selecting AMIC\n");
+ sc_access[0].reg_addr = 0x107;
+ sc_access[0].value = 0x40;
+ sc_access[0].mask = MASK6|MASK4|MASK3|MASK1|MASK0;
+ sc_access[1].reg_addr = 0x10a;
+ sc_access[1].value = 0x40;
+ sc_access[1].mask = MASK6;
+ sc_access[2].reg_addr = 0x109;
+ sc_access[2].value = 0x00;
+ sc_access[2].mask = MASK6;
+ sc_access[3].reg_addr = 0x105;
+ sc_access[3].value = 0x40;
+ sc_access[3].mask = MASK6;
+ num_val = 4;
+ break;
+
+ case HS_MIC:
+ pr_debug("sst: Selecting HS_MIC\n");
+ sc_access[0].reg_addr = 0x107;
+ sc_access[0].mask = MASK6|MASK4|MASK3|MASK1|MASK0;
+ sc_access[0].value = 0x10;
+ sc_access[1].reg_addr = 0x109;
+ sc_access[1].mask = MASK6;
+ sc_access[1].value = 0x40;
+ sc_access[2].reg_addr = 0x10a;
+ sc_access[2].mask = MASK6;
+ sc_access[2].value = 0x00;
+ sc_access[3].reg_addr = 0x105;
+ sc_access[3].value = 0x40;
+ sc_access[3].mask = MASK6;
+ num_val = 4;
+ break;
+
+ case DMIC:
+ pr_debug("sst: DMIC\n");
+ sc_access[0].reg_addr = 0x107;
+ sc_access[0].mask = MASK6|MASK4|MASK3|MASK1|MASK0;
+ sc_access[0].value = 0x0B;
+ sc_access[1].reg_addr = 0x105;
+ sc_access[1].value = 0x80;
+ sc_access[1].mask = MASK7|MASK6;
+ sc_access[2].reg_addr = 0x10a;
+ sc_access[2].value = 0x40;
+ sc_access[2].mask = MASK6;
+ sc_access[3].reg_addr = 0x109;
+ sc_access[3].mask = MASK6;
+ sc_access[3].value = 0x40;
+ num_val = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return sst_sc_reg_access(sc_access, PMIC_READ_MODIFY, num_val);
+}
+
+static int nc_get_mute(int dev_id, u8 *value)
+{
+ int retval = 0, mask = 0;
+ struct sc_reg_access sc_access = {0,};
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ pr_debug("sst: get mute::%d\n", dev_id);
+
+ switch (dev_id) {
+ case PMIC_SND_AMIC_MUTE:
+ pr_debug("sst: PMIC_SND_INPUT_MUTE_MIC1\n");
+ sc_access.reg_addr = LILSEL;
+ mask = MASK6;
+ break;
+ case PMIC_SND_HP_MIC_MUTE:
+ pr_debug("sst: PMIC_SND_INPUT_MUTE_MIC2\n");
+ sc_access.reg_addr = LIRSEL;
+ mask = MASK6;
+ break;
+ case PMIC_SND_LEFT_HP_MUTE:
+ case PMIC_SND_RIGHT_HP_MUTE:
+ mask = MASK2;
+ pr_debug("sst: PMIC_SN_LEFT/RIGHT_HP_MUTE\n");
+ if (dev_id == PMIC_SND_RIGHT_HP_MUTE)
+ sc_access.reg_addr = RMUTE;
+ else
+ sc_access.reg_addr = LMUTE;
+ break;
+
+ case PMIC_SND_LEFT_SPEAKER_MUTE:
+ pr_debug("sst: PMIC_MONO_EARPIECE_MUTE\n");
+ sc_access.reg_addr = RMUTE;
+ mask = MASK1;
+ break;
+ case PMIC_SND_DMIC_MUTE:
+ pr_debug("sst: PMIC_SND_INPUT_MUTE_DMIC\n");
+ sc_access.reg_addr = 0x105;
+ mask = MASK6;
+ break;
+ default:
+ return -EINVAL;
+
+ }
+ retval = sst_sc_reg_access(&sc_access, PMIC_READ, 1);
+ pr_debug("sst: reg value = %d\n", sc_access.value);
+ if (retval)
+ return retval;
+ *value = (sc_access.value) & mask;
+ pr_debug("sst: masked value = %d\n", *value);
+ if (*value)
+ *value = 0;
+ else
+ *value = 1;
+ pr_debug("sst: value returned = 0x%x\n", *value);
+ return retval;
+}
+
+static int nc_get_vol(int dev_id, int *value)
+{
+ int retval = 0, mask = 0;
+ struct sc_reg_access sc_access = {0,};
+
+ if (snd_pmic_ops_nc.card_status == SND_CARD_UN_INIT)
+ retval = nc_init_card();
+ if (retval)
+ return retval;
+
+ switch (dev_id) {
+ case PMIC_SND_CAPTURE_VOL:
+ pr_debug("sst: PMIC_SND_INPUT_CAPTURE_VOL\n");
+ sc_access.reg_addr = LILSEL;
+ mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5);
+ break;
+
+ case PMIC_SND_RIGHT_PB_VOL:
+ pr_debug("sst: GET_VOLUME_PMIC_LEFT_HP_VOL\n");
+ sc_access.reg_addr = AUDIOLVOL;
+ mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5|MASK6);
+ break;
+
+ case PMIC_SND_LEFT_PB_VOL:
+ pr_debug("sst: GET_VOLUME_PMIC_RIGHT_HP_VOL\n");
+ sc_access.reg_addr = AUDIORVOL;
+ mask = (MASK0|MASK1|MASK2|MASK3|MASK4|MASK5|MASK6);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ retval = sst_sc_reg_access(&sc_access, PMIC_READ, 1);
+ pr_debug("sst: value read = 0x%x\n", sc_access.value);
+ *value = -((sc_access.value) & mask);
+ pr_debug("sst: get vol value returned = %d\n", *value);
+ return retval;
+}
+
+struct snd_pmic_ops snd_pmic_ops_nc = {
+ .set_input_dev = nc_set_selected_input_dev,
+ .set_output_dev = nc_set_selected_output_dev,
+ .set_mute = nc_set_mute,
+ .get_mute = nc_get_mute,
+ .set_vol = nc_set_vol,
+ .get_vol = nc_get_vol,
+ .init_card = nc_init_card,
+ .set_pcm_audio_params = nc_set_pcm_audio_params,
+ .set_pcm_voice_params = nc_set_pcm_voice_params,
+ .set_voice_port = nc_set_voice_port,
+ .set_audio_port = nc_set_audio_port,
+ .power_up_pmic_pb = nc_power_up_pb,
+ .power_up_pmic_cp = nc_power_up_cp,
+ .power_down_pmic_pb = nc_power_down_pb,
+ .power_down_pmic_cp = nc_power_down_cp,
+ .power_down_pmic = nc_power_down,
+};
diff --git a/drivers/staging/intel_sst/jack.h b/drivers/staging/intel_sst/jack.h
new file mode 100644
index 0000000..9a6e483
--- /dev/null
+++ b/drivers/staging/intel_sst/jack.h
@@ -0,0 +1,10 @@
+/* Temporary staging glue */
+
+#include <sound/jack.h>
+
+/* These want adding to jack.h as enum entries once approved */
+
+#define SND_JACK_HS_SHORT_PRESS (SND_JACK_HEADSET | 0x0020)
+#define SND_JACK_HS_LONG_PRESS (SND_JACK_HEADSET | 0x0040)
+
+
3
2
The WM8804 is a high performance consumer mode
S/PDIF transceiver with support for 1 received channel and
1 transmitted channel.
Signed-off-by: Dimitris Papastamos <dp(a)opensource.wolfsonmicro.com>
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wm8804.c | 820 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/wm8804.h | 61 ++++
4 files changed, 887 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/wm8804.c
create mode 100644 sound/soc/codecs/wm8804.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..ff7b922 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -56,6 +56,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8900 if I2C
select SND_SOC_WM8903 if I2C
select SND_SOC_WM8904 if I2C
@@ -237,6 +238,9 @@ config SND_SOC_WM8753
config SND_SOC_WM8776
tristate
+config SND_SOC_WM8804
+ tristate
+
config SND_SOC_WM8900
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..0c5f329 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -41,6 +41,7 @@ snd-soc-wm8741-objs := wm8741.o
snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
snd-soc-wm8776-objs := wm8776.o
+snd-soc-wm8804-objs := wm8804.o
snd-soc-wm8900-objs := wm8900.o
snd-soc-wm8903-objs := wm8903.o
snd-soc-wm8904-objs := wm8904.o
@@ -114,6 +115,7 @@ obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o
obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
+obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o
diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c
new file mode 100644
index 0000000..ca7d9ee
--- /dev/null
+++ b/sound/soc/codecs/wm8804.c
@@ -0,0 +1,820 @@
+/*
+ * wm8804.c -- WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp(a)opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.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 "wm8804.h"
+
+#define WM8804_NUM_SUPPLIES 2
+static const char *wm8804_supply_names[WM8804_NUM_SUPPLIES] = {
+ "PVDD",
+ "DVDD"
+};
+
+static const u8 wm8804_reg_defs[] = {
+ 0x05, /* R0 - RST/DEVID1 */
+ 0x88, /* R1 - DEVID2 */
+ 0x04, /* R2 - DEVREV */
+ 0x21, /* R3 - PLL1 */
+ 0xFD, /* R4 - PLL2 */
+ 0x36, /* R5 - PLL3 */
+ 0x07, /* R6 - PLL4 */
+ 0x16, /* R7 - PLL5 */
+ 0x18, /* R8 - PLL6 */
+ 0xFF, /* R9 - SPDMODE */
+ 0x00, /* R10 - INTMASK */
+ 0x00, /* R11 - INTSTAT */
+ 0x00, /* R12 - SPDSTAT */
+ 0x00, /* R13 - RXCHAN1 */
+ 0x00, /* R14 - RXCHAN2 */
+ 0x00, /* R15 - RXCHAN3 */
+ 0x00, /* R16 - RXCHAN4 */
+ 0x00, /* R17 - RXCHAN5 */
+ 0x00, /* R18 - SPDTX1 */
+ 0x00, /* R19 - SPDTX2 */
+ 0x00, /* R20 - SPDTX3 */
+ 0x71, /* R21 - SPDTX4 */
+ 0x0B, /* R22 - SPDTX5 */
+ 0x70, /* R23 - GPO0 */
+ 0x57, /* R24 - GPO1 */
+ 0x00, /* R25 */
+ 0x42, /* R26 - GPO2 */
+ 0x06, /* R27 - AIFTX */
+ 0x06, /* R28 - AIFRX */
+ 0x80, /* R29 - SPDRX1 */
+ 0x07, /* R30 - PWRDN */
+};
+
+struct wm8804_priv {
+ enum snd_soc_control_type control_type;
+ struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8804_NUM_SUPPLIES];
+ struct snd_soc_codec *codec;
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8804_REGULATOR_EVENT(n) \
+static int wm8804_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8804_priv *wm8804 = container_of(nb, struct wm8804_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8804->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8804_REGULATOR_EVENT(0)
+WM8804_REGULATOR_EVENT(1)
+
+static const char *txsrc_text[] = { "S/PDIF RX", "AIF" };
+static const SOC_ENUM_SINGLE_EXT_DECL(txsrc, txsrc_text);
+
+static const struct snd_kcontrol_new wm8804_snd_controls[] = {
+ SOC_ENUM_EXT("Input Source", txsrc, txsrc_get, txsrc_put),
+ SOC_SINGLE("TX Playback Switch", WM8804_PWRDN, 2, 1, 1),
+ SOC_SINGLE("AIF Playback Switch", WM8804_PWRDN, 4, 1, 1)
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ unsigned int src;
+
+ codec = snd_kcontrol_chip(kcontrol);
+ src = snd_soc_read(codec, WM8804_SPDTX4);
+ if (src & 0x40)
+ ucontrol->value.integer.value[0] = 1;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ return 0;
+}
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ unsigned int src, txpwr;
+
+ codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] != 0
+ && ucontrol->value.integer.value[0] != 1)
+ return -EINVAL;
+
+ src = snd_soc_read(codec, WM8804_SPDTX4);
+ switch ((src & 0x40) >> 6) {
+ case 0:
+ if (!ucontrol->value.integer.value[0])
+ return 0;
+ break;
+ case 1:
+ if (ucontrol->value.integer.value[1])
+ return 0;
+ break;
+ }
+
+ /* save the current power state of the transmitter */
+ txpwr = snd_soc_read(codec, WM8804_PWRDN) & 0x4;
+ /* power down the transmitter */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, 0x4);
+ /* set the tx source */
+ snd_soc_update_bits(codec, WM8804_SPDTX4, 0x40,
+ ucontrol->value.integer.value[0] << 6);
+
+ if (ucontrol->value.integer.value[0]) {
+ /* power down the receiver */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0x2);
+ /* power up the AIF */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0);
+ } else {
+ /* don't power down the AIF -- may be used as an output */
+ /* power up the receiver */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0);
+ }
+
+ /* restore the transmitter's configuration */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, txpwr);
+
+ return 0;
+}
+
+static int wm8804_volatile(unsigned int reg)
+{
+ switch (reg) {
+ case WM8804_RST_DEVID1:
+ case WM8804_DEVID2:
+ case WM8804_DEVREV:
+ case WM8804_INTSTAT:
+ case WM8804_SPDSTAT:
+ case WM8804_RXCHAN1:
+ case WM8804_RXCHAN2:
+ case WM8804_RXCHAN3:
+ case WM8804_RXCHAN4:
+ case WM8804_RXCHAN5:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8804_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8804_RST_DEVID1, 0x0);
+}
+
+static int wm8804_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec;
+ u16 format, master, bcp, lrp;
+
+ codec = dai->codec;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format = 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ format = 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format = 0x1;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ format = 0x3;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown dai format\n");
+ return -EINVAL;
+ }
+
+ /* set data format */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0x3, format);
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x3, format);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ /* set master/slave mode */
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x40, master << 6);
+
+ bcp = lrp = 0;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ bcp = lrp = 1;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ bcp = 1;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ lrp = 1;
+ break;
+ default:
+ dev_err(dai->dev, "Unknown polarity configuration\n");
+ return -EINVAL;
+ }
+
+ /* set frame inversion */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0x10 | 0x20,
+ (bcp << 4) | (lrp << 5));
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0x10 | 0x20,
+ (bcp << 4) | (lrp << 5));
+ return 0;
+}
+
+static int wm8804_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec;
+ u16 blen;
+
+ codec = dai->codec;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ blen = 0x0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ blen = 0x1;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ blen = 0x2;
+ break;
+ default:
+ dev_err(dai->dev, "Unsupported word length: %u\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ /* set word length */
+ snd_soc_update_bits(codec, WM8804_AIFTX, 0xc, blen << 2);
+ snd_soc_update_bits(codec, WM8804_AIFRX, 0xc, blen << 2);
+
+ return 0;
+}
+
+struct pll_div {
+ u32 prescale:1;
+ u32 mclkdiv:1;
+ u32 freqmode:2;
+ u32 n:4;
+ u32 k:22;
+};
+
+/* PLL rate to output rate divisions */
+static struct {
+ unsigned int div;
+ unsigned int freqmode;
+ unsigned int mclkdiv;
+} post_table[] = {
+ { 2, 0, 0 },
+ { 4, 0, 1 },
+ { 4, 1, 0 },
+ { 8, 1, 1 },
+ { 8, 2, 0 },
+ { 16, 2, 1 },
+ { 12, 3, 0 },
+ { 24, 3, 1 }
+};
+
+#define FIXED_PLL_SIZE ((1ULL << 22) * 10)
+static int pll_factors(struct pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned long int K, Ndiv, Nmod, tmp;
+ int i;
+
+ /*
+ * Scale the output frequency up; the PLL should run in the
+ * region of 90-100MHz.
+ */
+ for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+ tmp = target * post_table[i].div;
+ if (tmp >= 90000000 && tmp <= 100000000) {
+ pll_div->freqmode = post_table[i].freqmode;
+ pll_div->mclkdiv = post_table[i].mclkdiv;
+ target *= post_table[i].div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(post_table)) {
+ pr_err("%s: Unable to scale output frequency: %uHz\n",
+ __func__, target);
+ return -EINVAL;
+ }
+
+ pll_div->prescale = 0;
+ Ndiv = target / source;
+ if (Ndiv < 5) {
+ source >>= 1;
+ pll_div->prescale = 1;
+ Ndiv = target / source;
+ }
+
+ if (Ndiv < 5 || Ndiv > 13) {
+ pr_err("%s: WM8804 N value is not within the recommended range: %lu\n",
+ __func__, Ndiv);
+ return -EINVAL;
+ }
+ pll_div->n = Ndiv;
+
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (u64)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xffffffff;
+ if ((K % 10) >= 5)
+ K += 5;
+ K /= 10;
+ pll_div->k = K;
+
+ return 0;
+}
+
+static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id,
+ int source, unsigned int freq_in,
+ unsigned int freq_out)
+{
+ int ret;
+ struct snd_soc_codec *codec;
+ struct pll_div pll_div = { 0 };
+
+ codec = dai->codec;
+ if (freq_in && freq_out) {
+ ret = pll_factors(&pll_div, freq_out, freq_in);
+ if (ret)
+ return ret;
+ }
+
+ /* power down the PLL before reprogramming it */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ /* set PLLN and PRESCALE */
+ snd_soc_update_bits(codec, WM8804_PLL4, 0xf | 0x10,
+ pll_div.n | (pll_div.prescale << 4));
+ /* set mclkdiv and freqmode */
+ snd_soc_update_bits(codec, WM8804_PLL5, 0x3 | 0x8,
+ pll_div.freqmode | (pll_div.mclkdiv << 3));
+ /* set PLLK */
+ snd_soc_write(codec, WM8804_PLL1, pll_div.k & 0xff);
+ snd_soc_write(codec, WM8804_PLL2, (pll_div.k >> 8) & 0xff);
+ snd_soc_write(codec, WM8804_PLL3, pll_div.k >> 16);
+
+ /* power up the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1);
+
+ return 0;
+}
+
+static int wm8804_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+
+ switch (clk_id) {
+ case WM8804_TX_CLKSRC_MCLK:
+ if ((freq >= 10000000 && freq <= 14400000)
+ || (freq >= 16280000 && freq <= 27000000))
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0x80);
+ else {
+ dev_err(dai->dev, "OSCCLOCK is not within the "
+ "recommended range: %uHz\n", freq);
+ return -EINVAL;
+ }
+ break;
+ case WM8804_TX_CLKSRC_PLL:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0);
+ break;
+ case WM8804_CLKOUT_SRC_CLK1:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0);
+ break;
+ case WM8804_CLKOUT_SRC_OSCCLK:
+ snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0x8);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown clock source: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8804_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec;
+
+ codec = dai->codec;
+ switch (div_id) {
+ case WM8804_CLKOUT_DIV:
+ snd_soc_update_bits(codec, WM8804_PLL5, 0x30,
+ (div & 0x3) << 4);
+ break;
+ default:
+ dev_err(dai->dev, "Unknown clock divider: %d\n", div_id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void wm8804_sync_cache(struct snd_soc_codec *codec)
+{
+ short i;
+ u8 *cache;
+
+ if (!codec->cache_sync)
+ return;
+
+ codec->cache_only = 0;
+ cache = codec->reg_cache;
+ for (i = 0; i < codec->driver->reg_cache_size; i++) {
+ if (i == WM8804_RST_DEVID1 || cache[i] == wm8804_reg_defs[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+ codec->cache_sync = 0;
+}
+
+static int wm8804_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct wm8804_priv *wm8804;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* power up the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+ wm8804_sync_cache(codec);
+ }
+ /* power down the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* power down the OSC and the PLL */
+ snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+ regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8804_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8804_resume(struct snd_soc_codec *codec)
+{
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define wm8804_suspend NULL
+#define wm8804_resume NULL
+#endif
+
+static int wm8804_remove(struct snd_soc_codec *codec)
+{
+ struct wm8804_priv *wm8804;
+ int i;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); ++i)
+ regulator_unregister_notifier(wm8804->supplies[i].consumer,
+ &wm8804->disable_nb[i]);
+ regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+ return 0;
+}
+
+static int wm8804_probe(struct snd_soc_codec *codec)
+{
+ struct wm8804_priv *wm8804;
+ int i, id1, id2, ret;
+
+ wm8804 = snd_soc_codec_get_drvdata(codec);
+ wm8804->codec = codec;
+
+ codec->idle_bias_off = 1;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, wm8804->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++)
+ wm8804->supplies[i].supply = wm8804_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ wm8804->disable_nb[0].notifier_call = wm8804_regulator_event_0;
+ wm8804->disable_nb[1].notifier_call = wm8804_regulator_event_1;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) {
+ ret = regulator_register_notifier(wm8804->supplies[i].consumer,
+ &wm8804->disable_nb[i]);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+ wm8804->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
+ id1 = snd_soc_read(codec, WM8804_RST_DEVID1);
+ if (id1 < 0) {
+ dev_err(codec->dev, "Failed to read device ID: %d\n", id1);
+ ret = id1;
+ goto err_reg_enable;
+ }
+
+ id2 = snd_soc_read(codec, WM8804_DEVID2);
+ if (id2 < 0) {
+ dev_err(codec->dev, "Failed to read device ID: %d\n", id2);
+ ret = id2;
+ goto err_reg_enable;
+ }
+
+ id2 = (id2 << 8) | id1;
+
+ if (id2 != ((wm8804_reg_defs[WM8804_DEVID2] << 8)
+ | wm8804_reg_defs[WM8804_RST_DEVID1])) {
+ dev_err(codec->dev, "Invalid device ID: %#x\n", id2);
+ ret = -EINVAL;
+ goto err_reg_enable;
+ }
+
+ ret = wm8804_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_reg_enable;
+ }
+
+ wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ snd_soc_add_controls(codec, wm8804_snd_controls,
+ ARRAY_SIZE(wm8804_snd_controls));
+ return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+ return ret;
+}
+
+static struct snd_soc_dai_ops wm8804_dai_ops = {
+ .hw_params = wm8804_hw_params,
+ .set_fmt = wm8804_set_fmt,
+ .set_sysclk = wm8804_set_sysclk,
+ .set_clkdiv = wm8804_set_clkdiv,
+ .set_pll = wm8804_set_pll
+};
+
+#define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_driver wm8804_dai = {
+ .name = "wm8804-s/pdif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8804_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8804_FORMATS,
+ },
+ .ops = &wm8804_dai_ops,
+ .symmetric_rates = 1
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8804 = {
+ .probe = wm8804_probe,
+ .remove = wm8804_remove,
+ .suspend = wm8804_suspend,
+ .resume = wm8804_resume,
+ .set_bias_level = wm8804_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(wm8804_reg_defs),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = wm8804_reg_defs,
+ .volatile_register = wm8804_volatile
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8804_spi_probe(struct spi_device *spi)
+{
+ struct wm8804_priv *wm8804;
+ int ret;
+
+ wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+ if (IS_ERR(wm8804))
+ return PTR_ERR(wm8804);
+
+ wm8804->control_type = SND_SOC_SPI;
+ spi_set_drvdata(spi, wm8804);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_wm8804, &wm8804_dai, 1);
+ if (ret < 0)
+ kfree(wm8804);
+ return ret;
+}
+
+static int __devexit wm8804_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ kfree(spi_get_drvdata(spi));
+ return 0;
+}
+
+static struct spi_driver wm8804_spi_driver = {
+ .driver = {
+ .name = "wm8804",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8804_spi_probe,
+ .remove = __devexit_p(wm8804_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8804_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8804_priv *wm8804;
+ int ret;
+
+ wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+ if (IS_ERR(wm8804))
+ return PTR_ERR(wm8804);
+
+ wm8804->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8804);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8804, &wm8804_dai, 1);
+ if (ret < 0)
+ kfree(wm8804);
+ return ret;
+}
+
+static __devexit int wm8804_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8804_i2c_id[] = {
+ { "wm8804", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8804_i2c_id);
+
+static struct i2c_driver wm8804_i2c_driver = {
+ .driver = {
+ .name = "wm8804",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8804_i2c_probe,
+ .remove = __devexit_p(wm8804_i2c_remove),
+ .id_table = wm8804_i2c_id
+};
+#endif
+
+static int __init wm8804_modinit(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8804_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "Failed to register wm8804 I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&wm8804_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register wm8804 SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+}
+module_init(wm8804_modinit);
+
+static void __exit wm8804_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8804_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8804_spi_driver);
+#endif
+}
+module_exit(wm8804_exit);
+
+MODULE_DESCRIPTION("ASoC WM8804 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp(a)opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8804.h b/sound/soc/codecs/wm8804.h
new file mode 100644
index 0000000..8ec14f5
--- /dev/null
+++ b/sound/soc/codecs/wm8804.h
@@ -0,0 +1,61 @@
+/*
+ * wm8804.h -- WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp(a)opensource.wolfsonmicro.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 _WM8804_H
+#define _WM8804_H
+
+/*
+ * Register values.
+ */
+#define WM8804_RST_DEVID1 0x00
+#define WM8804_DEVID2 0x01
+#define WM8804_DEVREV 0x02
+#define WM8804_PLL1 0x03
+#define WM8804_PLL2 0x04
+#define WM8804_PLL3 0x05
+#define WM8804_PLL4 0x06
+#define WM8804_PLL5 0x07
+#define WM8804_PLL6 0x08
+#define WM8804_SPDMODE 0x09
+#define WM8804_INTMASK 0x0A
+#define WM8804_INTSTAT 0x0B
+#define WM8804_SPDSTAT 0x0C
+#define WM8804_RXCHAN1 0x0D
+#define WM8804_RXCHAN2 0x0E
+#define WM8804_RXCHAN3 0x0F
+#define WM8804_RXCHAN4 0x10
+#define WM8804_RXCHAN5 0x11
+#define WM8804_SPDTX1 0x12
+#define WM8804_SPDTX2 0x13
+#define WM8804_SPDTX3 0x14
+#define WM8804_SPDTX4 0x15
+#define WM8804_SPDTX5 0x16
+#define WM8804_GPO0 0x17
+#define WM8804_GPO1 0x18
+#define WM8804_GPO2 0x1A
+#define WM8804_AIFTX 0x1B
+#define WM8804_AIFRX 0x1C
+#define WM8804_SPDRX1 0x1D
+#define WM8804_PWRDN 0x1E
+
+#define WM8804_REGISTER_COUNT 30
+#define WM8804_MAX_REGISTER 0x1E
+
+#define WM8804_TX_CLKSRC_MCLK 1
+#define WM8804_TX_CLKSRC_PLL 2
+
+#define WM8804_CLKOUT_SRC_CLK1 3
+#define WM8804_CLKOUT_SRC_OSCCLK 4
+
+#define WM8804_CLKOUT_DIV 1
+
+#endif /* _WM8804_H */
--
1.7.3
4
4

02 Oct '10
The restrictions on configuring BCLK are overly cautious, other constraints
in the system should ensure that reconfiguration is not possible when the
device is sufficiently active to be unable to support reclocking.
Signed-off-by: Mark Brown <broonie(a)opensource.wolfsonmicro.com>
---
sound/soc/codecs/wm8962.c | 6 ------
1 files changed, 0 insertions(+), 6 deletions(-)
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index be34146..38cbf85 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -911,12 +911,6 @@ static void wm8962_configure_bclk(struct snd_soc_codec *codec)
int clocking2 = 0;
int aif2 = 0;
- /* If the CODEC is powered on we can configure BCLK */
- if (codec->bias_level != SND_SOC_BIAS_OFF) {
- dev_dbg(codec->dev, "Bias is off, can't configure BCLK\n");
- return;
- }
-
if (!wm8962->bclk) {
dev_dbg(codec->dev, "No BCLK rate configured\n");
return;
--
1.7.1
2
2

Re: [alsa-devel] [PATCH 2/2] omap: zoom: Move new code introduced by ASoC m-c to board-zoom-peripherals
by Liam Girdwood 02 Oct '10
by Liam Girdwood 02 Oct '10
02 Oct '10
On Fri, 2010-09-24 at 11:31 -0500, Lopez Cruz, Misael wrote:
> Hi Jarkko,
>
> > ASoC Multi-Component Support moves some code from sound/soc/omap/zoom2.c
> > into
> > arch/arm/mach-omap2/board-zoom2.c. However, that code should go to
> > board-zoom-peripherals.c instead as there is common code and registration
> > for zoom boards.
> >
> > Signed-off-by: Jarkko Nikula <jhnikula(a)gmail.com>
> > Cc: Vikram Pandita <vikram.pandita(a)ti.com>
> > Cc: Lopez Cruz, Misael <x0052729(a)ti.com>
> > Cc: Jorge Eduardo Candelaria <jorge.candelaria(a)ti.com>
> > Cc: Tony Lindgren <tony(a)atomide.com>
> > ---
> > I don't have this HW so not tested.
> Tested on zoom2 using the 2 patches of your series, compilation break is
> gone and audio works fine.
>
> Thanks,
> -Misael
Applied, and manually added your Tested-by:
Thanks
Liam
--
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk
3
2