From d273ebe77a780d3aba1f5b86615af3b0e87b76bf Mon Sep 17 00:00:00 2001 From: jassi brar Date: Mon, 22 Feb 2010 15:58:04 +0900 Subject: ASoC: Pass dai_link as argument to platform suspend and resume Passing pointer to relevant dai_link provides easier reach to the ASoC tree in suspend/resume of snd_soc_platform. It also provides direct access to the dai at the other end of the dai_link. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 5 +++-- sound/soc/atmel/atmel-pcm.c | 6 ++++-- sound/soc/soc-core.c | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 5d234a8c250..27a2ad9a6b8 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -212,6 +212,7 @@ struct snd_soc_dai_mode; struct snd_soc_pcm_runtime; struct snd_soc_dai; struct snd_soc_platform; +struct snd_soc_dai_link; struct snd_soc_codec; struct soc_enum; struct snd_soc_ac97_ops; @@ -461,8 +462,8 @@ struct snd_soc_platform { int (*probe)(struct platform_device *pdev); int (*remove)(struct platform_device *pdev); - int (*suspend)(struct snd_soc_dai *dai); - int (*resume)(struct snd_soc_dai *dai); + int (*suspend)(struct snd_soc_dai_link *dai_link); + int (*resume)(struct snd_soc_dai_link *dai_link); /* pcm creation and destruction */ int (*pcm_new)(struct snd_card *, struct snd_soc_dai *, diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index 9ef6b96373f..67a32920af2 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -415,8 +415,9 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm) } #ifdef CONFIG_PM -static int atmel_pcm_suspend(struct snd_soc_dai *dai) +static int atmel_pcm_suspend(struct snd_soc_dai_link *dai_link) { + struct snd_soc_dai *dai = dai_link->cpu_dai; struct snd_pcm_runtime *runtime = dai->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; @@ -439,8 +440,9 @@ static int atmel_pcm_suspend(struct snd_soc_dai *dai) return 0; } -static int atmel_pcm_resume(struct snd_soc_dai *dai) +static int atmel_pcm_resume(struct snd_soc_dai_link *dai_link) { + struct snd_soc_dai *dai = dai_link->cpu_dai; struct snd_pcm_runtime *runtime = dai->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e1c0336868e..524a85a73ac 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -854,7 +854,7 @@ static int soc_suspend(struct device *dev) if (cpu_dai->suspend && !cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); if (platform->suspend) - platform->suspend(cpu_dai); + platform->suspend(&card->dai_link[i]); } /* close any waiting streams and save state */ @@ -943,7 +943,7 @@ static void soc_resume_deferred(struct work_struct *work) if (cpu_dai->resume && !cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); if (platform->resume) - platform->resume(cpu_dai); + platform->resume(&card->dai_link[i]); } if (card->resume_post) -- cgit v1.2.3-18-g5258 From 10cab262f49ee4574a20ab4c445bc4fdc41c36a8 Mon Sep 17 00:00:00 2001 From: jassi brar Date: Mon, 22 Feb 2010 15:59:02 +0900 Subject: ASoC: Change how suspend and resume obtain the PCM runtime Currently only the atmel driver make use of snd_soc_dai.runtime field. If the dais are to be shared among two or more dai_links, the field must be got rid of. So, in atmel driver reach the substream via dai_link->pcm so as to not depend of snd_soc_dai.runtime field. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index 67a32920af2..fdb25537212 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -417,8 +417,10 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm) #ifdef CONFIG_PM static int atmel_pcm_suspend(struct snd_soc_dai_link *dai_link) { - struct snd_soc_dai *dai = dai_link->cpu_dai; - struct snd_pcm_runtime *runtime = dai->runtime; + struct snd_pcm *pcm = dai_link->pcm; + struct snd_pcm_str *stream = &pcm->streams[0]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; @@ -442,8 +444,10 @@ static int atmel_pcm_suspend(struct snd_soc_dai_link *dai_link) static int atmel_pcm_resume(struct snd_soc_dai_link *dai_link) { - struct snd_soc_dai *dai = dai_link->cpu_dai; - struct snd_pcm_runtime *runtime = dai->runtime; + struct snd_pcm *pcm = dai_link->pcm; + struct snd_pcm_str *stream = &pcm->streams[0]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; -- cgit v1.2.3-18-g5258 From 6423c1875c87fa5ae56974ab8386d7c6110e3701 Mon Sep 17 00:00:00 2001 From: jassi brar Date: Mon, 22 Feb 2010 16:00:20 +0900 Subject: ASoC: Remove runtime field from DAI In order for having snd_soc_dais shared among two or more dai_links, remove the relatively global runtime field from the struct snd_soc_dai Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 - sound/soc/soc-core.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 061f16d4c87..20de0bcaa13 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -215,7 +215,6 @@ struct snd_soc_dai { unsigned int symmetric_rates:1; /* DAI runtime info */ - struct snd_pcm_runtime *runtime; struct snd_soc_codec *codec; unsigned int active; unsigned char pop_wait:1; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 524a85a73ac..1215a0ec2df 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -459,7 +459,6 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) else cpu_dai->capture.active = codec_dai->capture.active = 1; cpu_dai->active = codec_dai->active = 1; - cpu_dai->runtime = runtime; card->codec->active++; mutex_unlock(&pcm_mutex); return 0; @@ -559,7 +558,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream) if (platform->pcm_ops->close) platform->pcm_ops->close(substream); - cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* start delayed pop wq here for playback streams */ -- cgit v1.2.3-18-g5258 From 14dc5734bdac2629ed4228f3d30662bb440a3982 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Fri, 26 Feb 2010 09:12:32 +0900 Subject: ASoC: Allow mulitple usage count of codec and cpu dai If we are to have a snd_soc_dai i.e, cpu_dai and codec_dai, shared among two or more dai_links we need to log the number of active users of the dai. For that, we change semantics of the snd_soc_dai.active flag from indicator to reference counter. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 2 +- sound/soc/soc-core.c | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 27a2ad9a6b8..f792c1881b0 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -375,7 +375,7 @@ struct snd_soc_pcm_stream { unsigned int rate_max; /* max rate */ unsigned int channels_min; /* min channels */ unsigned int channels_max; /* max channels */ - unsigned int active:1; /* stream is in use */ + unsigned int active; /* num of active users of the stream */ }; /* SoC audio ops */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 1215a0ec2df..f30959cc954 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -454,11 +454,15 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->playback.active = codec_dai->playback.active = 1; - else - cpu_dai->capture.active = codec_dai->capture.active = 1; - cpu_dai->active = codec_dai->active = 1; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback.active++; + codec_dai->playback.active++; + } else { + cpu_dai->capture.active++; + codec_dai->capture.active++; + } + cpu_dai->active++; + codec_dai->active++; card->codec->active++; mutex_unlock(&pcm_mutex); return 0; @@ -530,15 +534,16 @@ static int soc_codec_close(struct snd_pcm_substream *substream) mutex_lock(&pcm_mutex); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->playback.active = codec_dai->playback.active = 0; - else - cpu_dai->capture.active = codec_dai->capture.active = 0; - - if (codec_dai->playback.active == 0 && - codec_dai->capture.active == 0) { - cpu_dai->active = codec_dai->active = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback.active--; + codec_dai->playback.active--; + } else { + cpu_dai->capture.active--; + codec_dai->capture.active--; } + + cpu_dai->active--; + codec_dai->active--; codec->active--; /* Muting the DAC suppresses artifacts caused during digital -- cgit v1.2.3-18-g5258 From eeec124685164cb8f9bbf274e1d4c9b9c2f8d6c9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 23 Feb 2010 11:15:34 +0000 Subject: ASoC: Wolfson Microelectronics 1133-EV1 audio support Initial support for audio using the 1133-EV1 audio and PMIC module for the i.MX31ADS. Currently only playback is supported, and the FIQ DMA driver has performance problems at higher sample rates which cause audible dropouts. This driver is based heavily on an out of tree one written by Liam Girdwood. Signed-off-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/imx/Kconfig | 8 ++ sound/soc/imx/Makefile | 3 + sound/soc/imx/wm1133-ev1.c | 291 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 sound/soc/imx/wm1133-ev1.c diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index c7d0fd9b7de..c045da8ff61 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -11,3 +11,11 @@ config SND_IMX_SOC config SND_MXC_SOC_SSI tristate +config SND_MXC_SOC_WM1133_EV1 + tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted" + depends on SND_IMX_SOC && EXPERIMENTAL + select SND_SOC_WM8350 + select SND_MXC_SOC_SSI + help + Enable support for audio on the i.MX31ADS with the WM1133-EV1 + PMIC board with WM8835x fitted. diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index 9f8bb92ddfc..2d203635ac1 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -9,4 +9,7 @@ obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o # i.MX Machine Support snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-wm1133-ev1-objs := wm1133-ev1.o + obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c new file mode 100644 index 00000000000..b75fcde85e8 --- /dev/null +++ b/sound/soc/imx/wm1133-ev1.c @@ -0,0 +1,291 @@ +/* + * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS + * + * Copyright (c) 2010 Wolfson Microelectronics plc + * Author: Mark Brown + * + * Based on an earlier driver for the same hardware by Liam Girdwood. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "imx-ssi.h" +#include "../codecs/wm8350.h" + +/* There is a silicon mic on the board optionally connected via a solder pad + * SP1. Define this to enable it. + */ +#undef USE_SIMIC + +struct _wm8350_audio { + unsigned int channels; + snd_pcm_format_t format; + unsigned int rate; + unsigned int sysclk; + unsigned int bclkdiv; + unsigned int clkdiv; + unsigned int lr_rate; +}; + +/* in order of power consumption per rate (lowest first) */ +static const struct _wm8350_audio wm8350_audio[] = { + /* 16bit mono modes */ + {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1, + WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,}, + + /* 16 bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + + /* 24bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, +}; + +static int wm1133_ev1_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 *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int i, found = 0; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + u32 dai_format; + + /* find the correct audio parameters */ + for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { + if (rate == wm8350_audio[i].rate && + format == wm8350_audio[i].format && + channels == wm8350_audio[i].channels) { + found = 1; + break; + } + } + if (!found) + return -EINVAL; + + /* codec FLL input is 14.75 MHz from MCLK */ + snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0); + break; + default: + return -EINVAL; + } + + /* set MCLK as the codec system clock for DAC and ADC */ + snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK, + wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, + wm8350_audio[i].bclkdiv); + + /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate); + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate); + + /* now configure DAC and ADC clocks */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); + + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); + + return 0; +} + +static struct snd_soc_ops wm1133_ev1_ops = { + .hw_params = wm1133_ev1_hw_params, +}; + +static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = { +#ifdef USE_SIMIC + SND_SOC_DAPM_MIC("SiMIC", NULL), +#endif + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_MIC("Mic2 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +/* imx32ads soc_card audio map */ +static const struct snd_soc_dapm_route wm1133_ev1_map[] = { + +#ifdef USE_SIMIC + /* SiMIC --> IN1LN (with automatic bias) via SP1 */ + { "IN1LN", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "SiMIC" }, +#endif + + /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ + { "IN1LN", NULL, "Mic Bias" }, + { "IN1LP", NULL, "Mic1 Jack" }, + { "Mic Bias", NULL, "Mic1 Jack" }, + + /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ + { "IN1RN", NULL, "Mic Bias" }, + { "IN1RP", NULL, "Mic1 Jack" }, + { "Mic Bias", NULL, "Mic1 Jack" }, + + /* Line in Jack --> AUX (L+R) */ + { "IN3R", NULL, "Line In Jack" }, + { "IN3L", NULL, "Line In Jack" }, + + /* Out1 --> Headphone Jack */ + { "Headphone Jack", NULL, "OUT1R" }, + { "Headphone Jack", NULL, "OUT1L" }, + + /* Out1 --> Line Out Jack */ + { "Line Out Jack", NULL, "OUT2R" }, + { "Line Out Jack", NULL, "OUT2L" }, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, +}; + +static int wm1133_ev1_init(struct snd_soc_codec *codec) +{ + struct snd_soc_card *card = codec->socdev->card; + + snd_soc_dapm_new_controls(codec, wm1133_ev1_widgets, + ARRAY_SIZE(wm1133_ev1_widgets)); + + snd_soc_dapm_add_routes(codec, wm1133_ev1_map, + ARRAY_SIZE(wm1133_ev1_map)); + + /* Headphone jack detection */ + snd_soc_jack_new(card, "Headphone", SND_JACK_HEADPHONE, &hp_jack); + snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), + hp_jack_pins); + wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); + + return 0; +} + + +static struct snd_soc_dai_link wm1133_ev1_dai = { + .name = "WM1133-EV1", + .stream_name = "Audio", + .cpu_dai = &imx_ssi_pcm_dai[0], + .codec_dai = &wm8350_dai, + .init = wm1133_ev1_init, + .ops = &wm1133_ev1_ops, + .symmetric_rates = 1, +}; + +static struct snd_soc_card wm1133_ev1 = { + .name = "WM1133-EV1", + .platform = &imx_soc_platform, + .dai_link = &wm1133_ev1_dai, + .num_links = 1, +}; + +static struct snd_soc_device wm1133_ev1_snd_devdata = { + .card = &wm1133_ev1, + .codec_dev = &soc_codec_dev_wm8350, +}; + +static struct platform_device *wm1133_ev1_snd_device; + +static int __init wm1133_ev1_audio_init(void) +{ + int ret; + unsigned int ptcr, pdcr; + + /* SSI0 mastered by port 5 */ + ptcr = MXC_AUDMUX_V2_PTCR_SYN | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr); + + ptcr = MXC_AUDMUX_V2_PTCR_SYN; + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr); + + wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1); + if (!wm1133_ev1_snd_device) + return -ENOMEM; + + platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1_snd_devdata); + wm1133_ev1_snd_devdata.dev = &wm1133_ev1_snd_device->dev; + ret = platform_device_add(wm1133_ev1_snd_device); + + if (ret) + platform_device_put(wm1133_ev1_snd_device); + + return ret; +} +module_init(wm1133_ev1_audio_init); + +static void __exit wm1133_ev1_audio_exit(void) +{ + platform_device_unregister(wm1133_ev1_snd_device); +} +module_exit(wm1133_ev1_audio_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-18-g5258 From 50831450503b828b3f8c031aa664340062afb3b0 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 3 Mar 2010 15:08:04 +0200 Subject: ASoC: core: fix tailing whitespace in soc_pcm_apply_symmetry My editor removes the tailing spaces, which causes problems when changing the soc-core.c Removing the space. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f30959cc954..7b4179eca89 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -315,7 +315,7 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) if (codec_dai->symmetric_rates || cpu_dai->symmetric_rates || machine->symmetric_rates) { - dev_dbg(card->dev, "Symmetry forces %dHz rate\n", + dev_dbg(card->dev, "Symmetry forces %dHz rate\n", machine->rate); ret = snd_pcm_hw_constraint_minmax(substream->runtime, -- cgit v1.2.3-18-g5258 From 377b6f62effcb91c53cd7ff8709a94d72d23b6ae Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 3 Mar 2010 15:08:06 +0200 Subject: ASoC: core: soc level wrapper for pcm_pointer callback Create a soc level wrapper for pcm_pointer callback. This will facilitate the soc level handling of different HW buffers in the audio path. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7b4179eca89..feb572c616c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -800,6 +800,23 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +/* + * soc level wrapper for pointer callback + */ +static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_card *card = socdev->card; + struct snd_soc_platform *platform = card->platform; + snd_pcm_uframes_t offset = 0; + + if (platform->pcm_ops->pointer) + offset = platform->pcm_ops->pointer(substream); + + return offset; +} + /* ASoC PCM operations */ static struct snd_pcm_ops soc_pcm_ops = { .open = soc_pcm_open, @@ -808,6 +825,7 @@ static struct snd_pcm_ops soc_pcm_ops = { .hw_free = soc_pcm_hw_free, .prepare = soc_pcm_prepare, .trigger = soc_pcm_trigger, + .pointer = soc_pcm_pointer, }; #ifdef CONFIG_PM @@ -1328,7 +1346,6 @@ static int soc_new_pcm(struct snd_soc_device *socdev, dai_link->pcm = pcm; pcm->private_data = rtd; soc_pcm_ops.mmap = platform->pcm_ops->mmap; - soc_pcm_ops.pointer = platform->pcm_ops->pointer; soc_pcm_ops.ioctl = platform->pcm_ops->ioctl; soc_pcm_ops.copy = platform->pcm_ops->copy; soc_pcm_ops.silence = platform->pcm_ops->silence; -- cgit v1.2.3-18-g5258 From 258020d0882e89c1462800a70eb414b8a4fec78c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 3 Mar 2010 15:08:07 +0200 Subject: ASoC: core: Add delay operation to snd_soc_dai_ops The delay callback can be used by the core to query the delay on the dai caused by FIFO or delay in the platform side. In case if both CPU and CODEC dai has FIFO the delay reported by each will be added to form the full delay on the chain. If none of the dai has FIFO, than the delay will be kept as zero. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 6 ++++++ include/sound/soc.h | 7 +++++++ sound/soc/soc-core.c | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 20de0bcaa13..6cf76a41501 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -182,6 +182,12 @@ struct snd_soc_dai_ops { struct snd_soc_dai *); int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *); + /* + * For hardware based FIFO caused delay reporting. + * Optional. + */ + snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, + struct snd_soc_dai *); }; /* diff --git a/include/sound/soc.h b/include/sound/soc.h index f792c1881b0..dbfec16015d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -470,6 +470,13 @@ struct snd_soc_platform { struct snd_pcm *); void (*pcm_free)(struct snd_pcm *); + /* + * For platform caused delay reporting. + * Optional. + */ + snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, + struct snd_soc_dai *); + /* platform stream ops */ struct snd_pcm_ops *pcm_ops; }; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index feb572c616c..4011ad3dc57 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -802,6 +802,8 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) /* * soc level wrapper for pointer callback + * If cpu_dai, codec_dai, platform driver has the delay callback, than + * the runtime->delay will be updated accordingly. */ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) { @@ -809,11 +811,27 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_card *card = socdev->card; struct snd_soc_platform *platform = card->platform; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t offset = 0; + snd_pcm_sframes_t delay = 0; if (platform->pcm_ops->pointer) offset = platform->pcm_ops->pointer(substream); + if (cpu_dai->ops->delay) + delay += cpu_dai->ops->delay(substream, cpu_dai); + + if (codec_dai->ops->delay) + delay += codec_dai->ops->delay(substream, codec_dai); + + if (platform->delay) + delay += platform->delay(substream, codec_dai); + + runtime->delay = delay; + return offset; } -- cgit v1.2.3-18-g5258 From a24d62d297fad31c866eef28e3d8b691a2b14a92 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Mar 2010 20:00:04 +0000 Subject: ASoC: Prettify wm8960 logging The driver name gets used by dev_() logging so use something a bit more idiomatic. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8960.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index d07bcc1e1c6..35171e4354a 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -882,7 +882,7 @@ MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); static struct i2c_driver wm8960_i2c_driver = { .driver = { - .name = "WM8960 I2C Codec", + .name = "wm8960", .owner = THIS_MODULE, }, .probe = wm8960_i2c_probe, -- cgit v1.2.3-18-g5258 From b6877a477d356a7c07a6c173d58c34a0a6abb086 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 3 Mar 2010 11:43:38 +0000 Subject: ASoC: Move WM8960 platform data into include/sound Avoids machine files having to peer into sound/soc which is a bit rude and icky. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8960.h | 22 ++++++++++++++++++++++ sound/soc/codecs/wm8960.c | 1 + sound/soc/codecs/wm8960.h | 10 ---------- 3 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 include/sound/wm8960.h diff --git a/include/sound/wm8960.h b/include/sound/wm8960.h new file mode 100644 index 00000000000..808a258e696 --- /dev/null +++ b/include/sound/wm8960.h @@ -0,0 +1,22 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver platform data + * + * 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 _WM8960_PDATA_H +#define _WM8960_PDATA_H + +#define WM8960_DRES_400R 0 +#define WM8960_DRES_200R 1 +#define WM8960_DRES_600R 2 +#define WM8960_DRES_150R 3 +#define WM8960_DRES_MAX 3 + +struct wm8960_data { + int dres; /* Discharge resistance for headphone outputs */ +}; + +#endif diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 35171e4354a..cf5cb3f73b6 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "wm8960.h" diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h index c9af56c9d9d..d67bfe1300d 100644 --- a/sound/soc/codecs/wm8960.h +++ b/sound/soc/codecs/wm8960.h @@ -114,14 +114,4 @@ extern struct snd_soc_dai wm8960_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8960; -#define WM8960_DRES_400R 0 -#define WM8960_DRES_200R 1 -#define WM8960_DRES_600R 2 -#define WM8960_DRES_150R 3 -#define WM8960_DRES_MAX 3 - -struct wm8960_data { - int dres; -}; - #endif -- cgit v1.2.3-18-g5258 From 913d7b4cc0d958df9f2e4bc0e6926c037d96d07e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 3 Mar 2010 13:47:03 +0000 Subject: ASoC: Add support for WM8960 capless mode The WM8960 headphone outputs can be run in capless mode with OUT3 used to drive a pseudo ground for the headphone drivers. In this mode the mono mixer is not used, the mixer should be turned on in concert with the headphone output drivers and the device bias levels are managed differently. Also tweak the existing bias management to remove the use of active discharge while we're at it since that's often audible. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8960.h | 2 + sound/soc/codecs/wm8960.c | 206 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 179 insertions(+), 29 deletions(-) diff --git a/include/sound/wm8960.h b/include/sound/wm8960.h index 808a258e696..74e9a95529c 100644 --- a/include/sound/wm8960.h +++ b/include/sound/wm8960.h @@ -16,6 +16,8 @@ #define WM8960_DRES_MAX 3 struct wm8960_data { + bool capless; /* Headphone outputs configured in capless mode */ + int dres; /* Discharge resistance for headphone outputs */ }; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index cf5cb3f73b6..c2960d3ec6d 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -31,8 +31,14 @@ struct snd_soc_codec_device soc_codec_dev_wm8960; /* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 #define WM8960_VREF 0x40 +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + /* R28 - Anti-pop 1 */ #define WM8960_POBCTRL 0x80 #define WM8960_BUFDCOPEN 0x10 @@ -42,6 +48,7 @@ struct snd_soc_codec_device soc_codec_dev_wm8960; /* R29 - Anti-pop 2 */ #define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 /* * wm8960 register cache @@ -68,6 +75,9 @@ static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { struct wm8960_priv { u16 reg_cache[WM8960_CACHEREGNUM]; struct snd_soc_codec codec; + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; }; #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) @@ -226,10 +236,6 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, &wm8960_routput_mixer[0], ARRAY_SIZE(wm8960_routput_mixer)), -SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, - &wm8960_mono_out[0], - ARRAY_SIZE(wm8960_mono_out)), - SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), @@ -248,6 +254,17 @@ SND_SOC_DAPM_OUTPUT("SPK_RN"), SND_SOC_DAPM_OUTPUT("OUT3"), }; +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + static const struct snd_soc_dapm_route audio_paths[] = { { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, @@ -278,9 +295,6 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, - { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, - { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, - { "LOUT1 PGA", NULL, "Left Output Mixer" }, { "ROUT1 PGA", NULL, "Right Output Mixer" }, @@ -297,17 +311,65 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "SPK_LP", NULL, "Left Speaker Output" }, { "SPK_RN", NULL, "Right Speaker Output" }, { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, { "OUT3", NULL, "Mono Output Mixer", } }; +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + static int wm8960_add_widgets(struct snd_soc_codec *codec) { + struct wm8960_data *pdata = codec->dev->platform_data; + struct wm8960_priv *wm8960 = codec->private_data; + struct snd_soc_dapm_widget *w; + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(codec, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(codec, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + return 0; } @@ -408,10 +470,9 @@ static int wm8960_mute(struct snd_soc_dai *dai, int mute) return 0; } -static int wm8960_set_bias_level(struct snd_soc_codec *codec, - enum snd_soc_bias_level level) +static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { - struct wm8960_data *pdata = codec->dev->platform_data; u16 reg; switch (level) { @@ -430,18 +491,8 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, - WM8960_POBCTRL | WM8960_SOFT_ST | - WM8960_BUFDCOPEN | WM8960_BUFIOEN); - - /* Discharge HP output */ - reg = WM8960_DISOP; - if (pdata) - reg |= pdata->dres << 4; - snd_soc_write(codec, WM8960_APOP2, reg); - - msleep(400); - - snd_soc_write(codec, WM8960_APOP2, 0); + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Enable & ramp VMID at 2x50k */ reg = snd_soc_read(codec, WM8960_POWER1); @@ -472,8 +523,101 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, /* Disable VMID and VREF, let them discharge */ snd_soc_write(codec, WM8960_POWER1, 0); msleep(600); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = codec->private_data; + int reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_update_bits(codec, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + break; + + case SND_SOC_BIAS_ON: + /* Enable anti-pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_STANDBY: + switch (codec->bias_level) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_update_bits(codec, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; - snd_soc_write(codec, WM8960_APOP1, 0); + case SND_SOC_BIAS_OFF: break; } @@ -663,7 +807,7 @@ static int wm8960_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; - wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); + codec->set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -682,8 +826,8 @@ static int wm8960_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8960_set_bias_level(codec, codec->suspend_bias_level); + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + codec->set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -753,6 +897,8 @@ static int wm8960_register(struct wm8960_priv *wm8960, goto err; } + codec->set_bias_level = wm8960_set_bias_level_out3; + if (!pdata) { dev_warn(codec->dev, "No platform data supplied\n"); } else { @@ -760,6 +906,9 @@ static int wm8960_register(struct wm8960_priv *wm8960, dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); pdata->dres = 0; } + + if (pdata->capless) + codec->set_bias_level = wm8960_set_bias_level_capless; } mutex_init(&codec->mutex); @@ -770,7 +919,6 @@ static int wm8960_register(struct wm8960_priv *wm8960, codec->name = "WM8960"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; - codec->set_bias_level = wm8960_set_bias_level; codec->dai = &wm8960_dai; codec->num_dai = 1; codec->reg_cache_size = WM8960_CACHEREGNUM; @@ -792,7 +940,7 @@ static int wm8960_register(struct wm8960_priv *wm8960, wm8960_dai.dev = codec->dev; - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the update bits */ reg = snd_soc_read(codec, WM8960_LINVOL); @@ -841,7 +989,7 @@ err: static void wm8960_unregister(struct wm8960_priv *wm8960) { - wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); + wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dai(&wm8960_dai); snd_soc_unregister_codec(&wm8960->codec); kfree(wm8960); -- cgit v1.2.3-18-g5258 From 1ca7578043a79d74152774acee0ed6e393134d12 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 4 Mar 2010 14:54:40 +0000 Subject: ASoC: Add delay information for Samsung IISv2 DAIs Report the current FIFO depth when delay is queried. The FIFO is only 16 frames deep so the latency will be at most a couple of miliseconds (and we tend to end up reporting zero most of the time) but it may help some applications. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/s3c24xx/s3c-i2s-v2.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index e994d8374fe..aa84f4cb4d8 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -550,6 +550,21 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, return 0; } +static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 reg = readl(i2s->regs + S3C2412_IISFIC); + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = S3C2412_IISFIC_TXCOUNT(reg); + else + delay = S3C2412_IISFIC_RXCOUNT(reg); + + return delay; +} + /* default table of all avaialable root fs divisors */ static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; @@ -736,6 +751,10 @@ int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) ops->set_fmt = s3c2412_i2s_set_fmt; ops->set_clkdiv = s3c2412_i2s_set_clkdiv; + /* Allow overriding by (for example) IISv4 */ + if (!ops->delay) + ops->delay = s3c2412_i2s_delay, + dai->suspend = s3c2412_i2s_suspend; dai->resume = s3c2412_i2s_resume; -- cgit v1.2.3-18-g5258 From bc700ab1407864ebee838de53c9565a394f4da38 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 4 Mar 2010 19:46:11 +0100 Subject: ALSA: usb-audio: move ua101 driver As part of the USB audio code cleanup, move the non-standard ua101 driver out of the way. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/Makefile | 5 +- sound/usb/misc/Makefile | 2 + sound/usb/misc/ua101.c | 1387 +++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/ua101.c | 1387 ----------------------------------------------- 4 files changed, 1391 insertions(+), 1390 deletions(-) create mode 100644 sound/usb/misc/Makefile create mode 100644 sound/usb/misc/ua101.c delete mode 100644 sound/usb/ua101.c diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 5bf64aef955..b0e55973c68 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -4,12 +4,11 @@ snd-usb-audio-objs := usbaudio.o usbmixer.o snd-usb-lib-objs := usbmidi.o -snd-ua101-objs := ua101.o # Toplevel Module Dependency obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o -obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o snd-usb-lib.o +obj-$(CONFIG_SND_USB_UA101) += snd-usb-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usb-lib.o -obj-$(CONFIG_SND) += usx2y/ caiaq/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile new file mode 100644 index 00000000000..ccefd815893 --- /dev/null +++ b/sound/usb/misc/Makefile @@ -0,0 +1,2 @@ +snd-ua101-objs := ua101.o +obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c new file mode 100644 index 00000000000..e9b0ae52ca8 --- /dev/null +++ b/sound/usb/misc/ua101.c @@ -0,0 +1,1387 @@ +/* + * Edirol UA-101/UA-1000 driver + * Copyright (c) Clemens Ladisch + * + * This driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../usbaudio.h" + +MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}"); + +/* + * Should not be lower than the minimum scheduling delay of the host + * controller. Some Intel controllers need more than one frame; as long as + * that driver doesn't tell us about this, use 1.5 frames just to be sure. + */ +#define MIN_QUEUE_LENGTH 12 +/* Somewhat random. */ +#define MAX_QUEUE_LENGTH 30 +/* + * This magic value optimizes memory usage efficiency for the UA-101's packet + * sizes at all sample rates, taking into account the stupid cache pool sizes + * that usb_buffer_alloc() uses. + */ +#define DEFAULT_QUEUE_LENGTH 21 + +#define MAX_PACKET_SIZE 672 /* hardware specific */ +#define MAX_MEMORY_BUFFERS DIV_ROUND_UP(MAX_QUEUE_LENGTH, \ + PAGE_SIZE / MAX_PACKET_SIZE) + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static unsigned int queue_length = 21; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); +module_param(queue_length, uint, 0644); +MODULE_PARM_DESC(queue_length, "USB queue length in microframes, " + __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH)); + +enum { + INTF_PLAYBACK, + INTF_CAPTURE, + INTF_MIDI, + + INTF_COUNT +}; + +/* bits in struct ua101::states */ +enum { + USB_CAPTURE_RUNNING, + USB_PLAYBACK_RUNNING, + ALSA_CAPTURE_OPEN, + ALSA_PLAYBACK_OPEN, + ALSA_CAPTURE_RUNNING, + ALSA_PLAYBACK_RUNNING, + CAPTURE_URB_COMPLETED, + PLAYBACK_URB_COMPLETED, + DISCONNECTED, +}; + +struct ua101 { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf[INTF_COUNT]; + int card_index; + struct snd_pcm *pcm; + struct list_head midi_list; + u64 format_bit; + unsigned int rate; + unsigned int packets_per_second; + spinlock_t lock; + struct mutex mutex; + unsigned long states; + + /* FIFO to synchronize playback rate to capture rate */ + unsigned int rate_feedback_start; + unsigned int rate_feedback_count; + u8 rate_feedback[MAX_QUEUE_LENGTH]; + + struct list_head ready_playback_urbs; + struct tasklet_struct playback_tasklet; + wait_queue_head_t alsa_capture_wait; + wait_queue_head_t rate_feedback_wait; + wait_queue_head_t alsa_playback_wait; + struct ua101_stream { + struct snd_pcm_substream *substream; + unsigned int usb_pipe; + unsigned int channels; + unsigned int frame_bytes; + unsigned int max_packet_bytes; + unsigned int period_pos; + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { + struct urb urb; + struct usb_iso_packet_descriptor iso_frame_desc[1]; + struct list_head ready_list; + } *urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; + dma_addr_t dma; + } buffers[MAX_MEMORY_BUFFERS]; + } capture, playback; +}; + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; +static struct usb_driver ua101_driver; + +static void abort_alsa_playback(struct ua101 *ua); +static void abort_alsa_capture(struct ua101 *ua); + +static const char *usb_error_string(int err) +{ + switch (err) { + case -ENODEV: + return "no device"; + case -ENOENT: + return "endpoint not enabled"; + case -EPIPE: + return "endpoint stalled"; + case -ENOSPC: + return "not enough bandwidth"; + case -ESHUTDOWN: + return "device disabled"; + case -EHOSTUNREACH: + return "device suspended"; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + return "internal error"; + default: + return "unknown error"; + } +} + +static void abort_usb_capture(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) { + wake_up(&ua->alsa_capture_wait); + wake_up(&ua->rate_feedback_wait); + } +} + +static void abort_usb_playback(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states)) + wake_up(&ua->alsa_playback_wait); +} + +static void playback_urb_complete(struct urb *usb_urb) +{ + struct ua101_urb *urb = (struct ua101_urb *)usb_urb; + struct ua101 *ua = urb->urb.context; + unsigned long flags; + + if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ + urb->urb.status == -ENODEV || /* device removed */ + urb->urb.status == -ECONNRESET || /* unlinked */ + urb->urb.status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; + } + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + spin_lock_irqsave(&ua->lock, flags); + list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + tasklet_schedule(&ua->playback_tasklet); + ua->playback.substream->runtime->delay -= + urb->urb.iso_frame_desc[0].length / + ua->playback.frame_bytes; + spin_unlock_irqrestore(&ua->lock, flags); + } +} + +static void first_playback_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); + + set_bit(PLAYBACK_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_playback_wait); +} + +/* copy data from the ALSA ring buffer into the URB buffer */ +static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + const u8 *source; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + source = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(urb->transfer_buffer, source, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(urb->transfer_buffer, source, frames1 * frame_bytes); + memcpy(urb->transfer_buffer + frames1 * frame_bytes, + runtime->dma_area, (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static inline void add_with_wraparound(struct ua101 *ua, + unsigned int *value, unsigned int add) +{ + *value += add; + if (*value >= ua->playback.queue_length) + *value -= ua->playback.queue_length; +} + +static void playback_tasklet(unsigned long data) +{ + struct ua101 *ua = (void *)data; + unsigned long flags; + unsigned int frames; + struct ua101_urb *urb; + bool do_period_elapsed = false; + int err; + + if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states))) + return; + + /* + * Synchronizing the playback rate to the capture rate is done by using + * the same sequence of packet sizes for both streams. + * Submitting a playback URB therefore requires both a ready URB and + * the size of the corresponding capture packet, i.e., both playback + * and capture URBs must have been completed. Since the USB core does + * not guarantee that playback and capture complete callbacks are + * called alternately, we use two FIFOs for packet sizes and read URBs; + * submitting playback URBs is possible as long as both FIFOs are + * nonempty. + */ + spin_lock_irqsave(&ua->lock, flags); + while (ua->rate_feedback_count > 0 && + !list_empty(&ua->ready_playback_urbs)) { + /* take packet size out of FIFO */ + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + + /* take URB out of FIFO */ + urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&urb->ready_list); + + /* fill packet with data or silence */ + urb->urb.iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, + &urb->urb, + frames); + else + memset(urb->urb.transfer_buffer, 0, + urb->urb.iso_frame_desc[0].length); + + /* and off you go ... */ + err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + abort_usb_playback(ua); + abort_alsa_playback(ua); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return; + } + ua->playback.substream->runtime->delay += frames; + } + spin_unlock_irqrestore(&ua->lock, flags); + if (do_period_elapsed) + snd_pcm_period_elapsed(ua->playback.substream); +} + +/* copy data from the URB buffer into the ALSA ring buffer */ +static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + u8 *dest; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + dest = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(dest, urb->transfer_buffer, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes); + memcpy(runtime->dma_area, + urb->transfer_buffer + frames1 * frame_bytes, + (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static void capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + struct ua101_stream *stream = &ua->capture; + unsigned long flags; + unsigned int frames, write_ptr; + bool do_period_elapsed; + int err; + + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) /* device disabled */ + goto stream_stopped; + + if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0) + frames = urb->iso_frame_desc[0].actual_length / + stream->frame_bytes; + else + frames = 0; + + spin_lock_irqsave(&ua->lock, flags); + + if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + do_period_elapsed = copy_capture_data(stream, urb, frames); + else + do_period_elapsed = false; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + goto stream_stopped; + } + + /* append packet size to FIFO */ + write_ptr = ua->rate_feedback_start; + add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); + ua->rate_feedback[write_ptr] = frames; + if (ua->rate_feedback_count < ua->playback.queue_length) { + ua->rate_feedback_count++; + if (ua->rate_feedback_count == + ua->playback.queue_length) + wake_up(&ua->rate_feedback_wait); + } else { + /* + * Ring buffer overflow; this happens when the playback + * stream is not running. Throw away the oldest entry, + * so that the playback stream, when it starts, sees + * the most recent packet sizes. + */ + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + } + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && + !list_empty(&ua->ready_playback_urbs)) + tasklet_schedule(&ua->playback_tasklet); + } + + spin_unlock_irqrestore(&ua->lock, flags); + + if (do_period_elapsed) + snd_pcm_period_elapsed(stream->substream); + + return; + +stream_stopped: + abort_usb_playback(ua); + abort_usb_capture(ua); + abort_alsa_playback(ua); + abort_alsa_capture(ua); +} + +static void first_capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); + + set_bit(CAPTURE_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_capture_wait); +} + +static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { + int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void kill_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) + usb_kill_urb(&stream->urbs[i]->urb); +} + +static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 1) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 1); + if (err < 0) { + dev_err(&ua->dev->dev, + "cannot initialize interface; error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 0) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 0); + if (err < 0 && !test_bit(DISCONNECTED, &ua->states)) + dev_warn(&ua->dev->dev, + "interface reset failed; error %d: %s\n", + err, usb_error_string(err)); + } +} + +static void stop_usb_capture(struct ua101 *ua) +{ + clear_bit(USB_CAPTURE_RUNNING, &ua->states); + + kill_stream_urbs(&ua->capture); + + disable_iso_interface(ua, INTF_CAPTURE); +} + +static int start_usb_capture(struct ua101 *ua) +{ + int err; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->capture); + + err = enable_iso_interface(ua, INTF_CAPTURE); + if (err < 0) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); + ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + + set_bit(USB_CAPTURE_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->capture); + if (err < 0) + stop_usb_capture(ua); + return err; +} + +static void stop_usb_playback(struct ua101 *ua) +{ + clear_bit(USB_PLAYBACK_RUNNING, &ua->states); + + kill_stream_urbs(&ua->playback); + + tasklet_kill(&ua->playback_tasklet); + + disable_iso_interface(ua, INTF_PLAYBACK); +} + +static int start_usb_playback(struct ua101 *ua) +{ + unsigned int i, frames; + struct urb *urb; + int err = 0; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->playback); + tasklet_kill(&ua->playback_tasklet); + + err = enable_iso_interface(ua, INTF_PLAYBACK); + if (err < 0) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); + ua->playback.urbs[0]->urb.complete = + first_playback_urb_complete; + spin_lock_irq(&ua->lock); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + spin_unlock_irq(&ua->lock); + + /* + * We submit the initial URBs all at once, so we have to wait for the + * packet size FIFO to be full. + */ + wait_event(ua->rate_feedback_wait, + ua->rate_feedback_count >= ua->playback.queue_length || + !test_bit(USB_CAPTURE_RUNNING, &ua->states) || + test_bit(DISCONNECTED, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) { + stop_usb_playback(ua); + return -ENODEV; + } + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + stop_usb_playback(ua); + return -EIO; + } + + for (i = 0; i < ua->playback.queue_length; ++i) { + /* all initial URBs contain silence */ + spin_lock_irq(&ua->lock); + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + spin_unlock_irq(&ua->lock); + urb = &ua->playback.urbs[i]->urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, + urb->iso_frame_desc[0].length); + } + + set_bit(USB_PLAYBACK_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->playback); + if (err < 0) + stop_usb_playback(ua); + return err; +} + +static void abort_alsa_capture(struct ua101 *ua) +{ + if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + snd_pcm_stop(ua->capture.substream, SNDRV_PCM_STATE_XRUN); +} + +static void abort_alsa_playback(struct ua101 *ua) +{ + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + snd_pcm_stop(ua->playback.substream, SNDRV_PCM_STATE_XRUN); +} + +static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream, + unsigned int channels) +{ + int err; + + substream->runtime->hw.info = + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_FIFO_IN_FRAMES; + substream->runtime->hw.formats = ua->format_bit; + substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate); + substream->runtime->hw.rate_min = ua->rate; + substream->runtime->hw.rate_max = ua->rate; + substream->runtime->hw.channels_min = channels; + substream->runtime->hw.channels_max = channels; + substream->runtime->hw.buffer_bytes_max = 45000 * 1024; + substream->runtime->hw.period_bytes_min = 1; + substream->runtime->hw.period_bytes_max = UINT_MAX; + substream->runtime->hw.periods_min = 2; + substream->runtime->hw.periods_max = UINT_MAX; + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 1500000 / ua->packets_per_second, + 8192000); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + return err; +} + +static int capture_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->capture.substream = substream; + err = set_stream_hw(ua, substream, ua->capture.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second); + substream->runtime->delay = substream->runtime->hw.fifo_size; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + set_bit(ALSA_CAPTURE_OPEN, &ua->states); + mutex_unlock(&ua->mutex); + return err; +} + +static int playback_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->playback.substream = substream; + err = set_stream_hw(ua, substream, ua->playback.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length, + ua->packets_per_second); + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err < 0) + goto error; + err = start_usb_playback(ua); + if (err < 0) { + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + goto error; + } + set_bit(ALSA_PLAYBACK_OPEN, &ua->states); +error: + mutex_unlock(&ua->mutex); + return err; +} + +static int capture_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + clear_bit(ALSA_CAPTURE_OPEN, &ua->states); + if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int playback_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + clear_bit(ALSA_PLAYBACK_OPEN, &ua->states); + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int capture_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int playback_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int ua101_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* + * The EHCI driver schedules the first packet of an iso stream at 10 ms + * in the future, i.e., no data is actually captured for that long. + * Take the wait here so that the stream is known to be actually + * running when the start trigger has been called. + */ + wait_event(ua->alsa_capture_wait, + test_bit(CAPTURE_URB_COMPLETED, &ua->states) || + !test_bit(USB_CAPTURE_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + + ua->capture.period_pos = 0; + ua->capture.buffer_pos = 0; + return 0; +} + +static int playback_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* see the comment in capture_pcm_prepare() */ + wait_event(ua->alsa_playback_wait, + test_bit(PLAYBACK_URB_COMPLETED, &ua->states) || + !test_bit(USB_PLAYBACK_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + + substream->runtime->delay = 0; + ua->playback.period_pos = 0; + ua->playback.buffer_pos = 0; + return 0; +} + +static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua, + struct ua101_stream *stream) +{ + unsigned long flags; + unsigned int pos; + + spin_lock_irqsave(&ua->lock, flags); + pos = stream->buffer_pos; + spin_unlock_irqrestore(&ua->lock, flags); + return pos; +} + +static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->capture); +} + +static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->playback); +} + +static struct snd_pcm_ops capture_pcm_ops = { + .open = capture_pcm_open, + .close = capture_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = capture_pcm_prepare, + .trigger = capture_pcm_trigger, + .pointer = capture_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static struct snd_pcm_ops playback_pcm_ops = { + .open = playback_pcm_open, + .close = playback_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = playback_pcm_prepare, + .trigger = playback_pcm_trigger, + .pointer = playback_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static const struct uac_format_type_i_discrete_descriptor * +find_format_descriptor(struct usb_interface *interface) +{ + struct usb_host_interface *alt; + u8 *extra; + int extralen; + + if (interface->num_altsetting != 2) { + dev_err(&interface->dev, "invalid num_altsetting\n"); + return NULL; + } + + alt = &interface->altsetting[0]; + if (alt->desc.bNumEndpoints != 0) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + alt = &interface->altsetting[1]; + if (alt->desc.bNumEndpoints != 1) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + extra = alt->extra; + extralen = alt->extralen; + while (extralen >= sizeof(struct usb_descriptor_header)) { + struct uac_format_type_i_discrete_descriptor *desc; + + desc = (struct uac_format_type_i_discrete_descriptor *)extra; + if (desc->bLength > extralen) { + dev_err(&interface->dev, "descriptor overflow\n"); + return NULL; + } + if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) && + desc->bDescriptorType == USB_DT_CS_INTERFACE && + desc->bDescriptorSubtype == UAC_FORMAT_TYPE) { + if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM || + desc->bSamFreqType != 1) { + dev_err(&interface->dev, + "invalid format type\n"); + return NULL; + } + return desc; + } + extralen -= desc->bLength; + extra += desc->bLength; + } + dev_err(&interface->dev, "sample format descriptor not found\n"); + return NULL; +} + +static int detect_usb_format(struct ua101 *ua) +{ + const struct uac_format_type_i_discrete_descriptor *fmt_capture; + const struct uac_format_type_i_discrete_descriptor *fmt_playback; + const struct usb_endpoint_descriptor *epd; + unsigned int rate2; + + fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]); + fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]); + if (!fmt_capture || !fmt_playback) + return -ENXIO; + + switch (fmt_capture->bSubframeSize) { + case 3: + ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n"); + return -ENXIO; + } + if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) { + dev_err(&ua->dev->dev, + "playback/capture sample widths do not match\n"); + return -ENXIO; + } + + if (fmt_capture->bBitResolution != 24 || + fmt_playback->bBitResolution != 24) { + dev_err(&ua->dev->dev, "sample width is not 24 bits\n"); + return -ENXIO; + } + + ua->rate = combine_triple(fmt_capture->tSamFreq[0]); + rate2 = combine_triple(fmt_playback->tSamFreq[0]); + if (ua->rate != rate2) { + dev_err(&ua->dev->dev, + "playback/capture rates do not match: %u/%u\n", + rate2, ua->rate); + return -ENXIO; + } + + switch (ua->dev->speed) { + case USB_SPEED_FULL: + ua->packets_per_second = 1000; + break; + case USB_SPEED_HIGH: + ua->packets_per_second = 8000; + break; + default: + dev_err(&ua->dev->dev, "unknown device speed\n"); + return -ENXIO; + } + + ua->capture.channels = fmt_capture->bNrChannels; + ua->playback.channels = fmt_playback->bNrChannels; + ua->capture.frame_bytes = + fmt_capture->bSubframeSize * ua->capture.channels; + ua->playback.frame_bytes = + fmt_playback->bSubframeSize * ua->playback.channels; + + epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_in(epd)) { + dev_err(&ua->dev->dev, "invalid capture endpoint\n"); + return -ENXIO; + } + ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->capture.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); + + epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_out(epd)) { + dev_err(&ua->dev->dev, "invalid playback endpoint\n"); + return -ENXIO; + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); + return 0; +} + +static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int remaining_packets, packets, packets_per_page, i; + size_t size; + + stream->queue_length = queue_length; + stream->queue_length = max(stream->queue_length, + (unsigned int)MIN_QUEUE_LENGTH); + stream->queue_length = min(stream->queue_length, + (unsigned int)MAX_QUEUE_LENGTH); + + /* + * The cache pool sizes used by usb_buffer_alloc() (128, 512, 2048) are + * quite bad when used with the packet sizes of this device (e.g. 280, + * 520, 624). Therefore, we allocate and subdivide entire pages, using + * a smaller buffer only for the last chunk. + */ + remaining_packets = stream->queue_length; + packets_per_page = PAGE_SIZE / stream->max_packet_bytes; + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) { + packets = min(remaining_packets, packets_per_page); + size = packets * stream->max_packet_bytes; + stream->buffers[i].addr = + usb_buffer_alloc(ua->dev, size, GFP_KERNEL, + &stream->buffers[i].dma); + if (!stream->buffers[i].addr) + return -ENOMEM; + stream->buffers[i].size = size; + remaining_packets -= packets; + if (!remaining_packets) + break; + } + if (remaining_packets) { + dev_err(&ua->dev->dev, "too many packets\n"); + return -ENXIO; + } + return 0; +} + +static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) + usb_buffer_free(ua->dev, + stream->buffers[i].size, + stream->buffers[i].addr, + stream->buffers[i].dma); +} + +static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) +{ + unsigned max_packet_size = stream->max_packet_bytes; + struct ua101_urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { + unsigned int size = stream->buffers[b].size; + u8 *addr = stream->buffers[b].addr; + dma_addr_t dma = stream->buffers[b].dma; + + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; + urb = kmalloc(sizeof(*urb), GFP_KERNEL); + if (!urb) + return -ENOMEM; + usb_init_urb(&urb->urb); + urb->urb.dev = ua->dev; + urb->urb.pipe = stream->usb_pipe; + urb->urb.transfer_flags = URB_ISO_ASAP | + URB_NO_TRANSFER_DMA_MAP; + urb->urb.transfer_buffer = addr; + urb->urb.transfer_dma = dma; + urb->urb.transfer_buffer_length = max_packet_size; + urb->urb.number_of_packets = 1; + urb->urb.interval = 1; + urb->urb.context = ua; + urb->urb.complete = urb_complete; + urb->urb.iso_frame_desc[0].offset = 0; + urb->urb.iso_frame_desc[0].length = max_packet_size; + stream->urbs[u++] = urb; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; + } + } + if (u == stream->queue_length) + return 0; +bufsize_error: + dev_err(&ua->dev->dev, "internal buffer size error\n"); + return -ENXIO; +} + +static void free_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) + kfree(stream->urbs[i]); +} + +static void free_usb_related_resources(struct ua101 *ua, + struct usb_interface *interface) +{ + unsigned int i; + + free_stream_urbs(&ua->capture); + free_stream_urbs(&ua->playback); + free_stream_buffers(ua, &ua->capture); + free_stream_buffers(ua, &ua->playback); + + for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) + if (ua->intf[i]) { + usb_set_intfdata(ua->intf[i], NULL); + if (ua->intf[i] != interface) + usb_driver_release_interface(&ua101_driver, + ua->intf[i]); + } +} + +static void ua101_card_free(struct snd_card *card) +{ + struct ua101 *ua = card->private_data; + + mutex_destroy(&ua->mutex); +} + +static int ua101_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + static const struct snd_usb_midi_endpoint_info midi_ep = { + .out_cables = 0x0001, + .in_cables = 0x0001 + }; + static const struct snd_usb_audio_quirk midi_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &midi_ep + }; + static const int intf_numbers[2][3] = { + { /* UA-101 */ + [INTF_PLAYBACK] = 0, + [INTF_CAPTURE] = 1, + [INTF_MIDI] = 2, + }, + { /* UA-1000 */ + [INTF_CAPTURE] = 1, + [INTF_PLAYBACK] = 2, + [INTF_MIDI] = 3, + }, + }; + struct snd_card *card; + struct ua101 *ua; + unsigned int card_index, i; + int is_ua1000; + const char *name; + char usb_path[32]; + int err; + + is_ua1000 = usb_id->idProduct == 0x0044; + + if (interface->altsetting->desc.bInterfaceNumber != + intf_numbers[is_ua1000][0]) + return -ENODEV; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (enable[card_index] && !(devices_used & (1 << card_index))) + break; + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + err = snd_card_create(index[card_index], id[card_index], THIS_MODULE, + sizeof(*ua), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + card->private_free = ua101_card_free; + ua = card->private_data; + ua->dev = interface_to_usbdev(interface); + ua->card = card; + ua->card_index = card_index; + INIT_LIST_HEAD(&ua->midi_list); + spin_lock_init(&ua->lock); + mutex_init(&ua->mutex); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + tasklet_init(&ua->playback_tasklet, + playback_tasklet, (unsigned long)ua); + init_waitqueue_head(&ua->alsa_capture_wait); + init_waitqueue_head(&ua->rate_feedback_wait); + init_waitqueue_head(&ua->alsa_playback_wait); + + ua->intf[0] = interface; + for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) { + ua->intf[i] = usb_ifnum_to_if(ua->dev, + intf_numbers[is_ua1000][i]); + if (!ua->intf[i]) { + dev_err(&ua->dev->dev, "interface %u not found\n", + intf_numbers[is_ua1000][i]); + err = -ENXIO; + goto probe_error; + } + err = usb_driver_claim_interface(&ua101_driver, + ua->intf[i], ua); + if (err < 0) { + ua->intf[i] = NULL; + err = -EBUSY; + goto probe_error; + } + } + + snd_card_set_dev(card, &interface->dev); + + err = detect_usb_format(ua); + if (err < 0) + goto probe_error; + + name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101"; + strcpy(card->driver, "UA-101"); + strcpy(card->shortname, name); + usb_make_path(ua->dev, usb_path, sizeof(usb_path)); + snprintf(ua->card->longname, sizeof(ua->card->longname), + "EDIROL %s (serial %s), %u Hz at %s, %s speed", name, + ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path, + ua->dev->speed == USB_SPEED_HIGH ? "high" : "full"); + + err = alloc_stream_buffers(ua, &ua->capture); + if (err < 0) + goto probe_error; + err = alloc_stream_buffers(ua, &ua->playback); + if (err < 0) + goto probe_error; + + err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete); + if (err < 0) + goto probe_error; + err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete); + if (err < 0) + goto probe_error; + + err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm); + if (err < 0) + goto probe_error; + ua->pcm->private_data = ua; + strcpy(ua->pcm->name, name); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops); + + err = snd_usbmidi_create(card, ua->intf[INTF_MIDI], + &ua->midi_list, &midi_quirk); + if (err < 0) + goto probe_error; + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + usb_set_intfdata(interface, ua); + devices_used |= 1 << card_index; + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + free_usb_related_resources(ua, interface); + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void ua101_disconnect(struct usb_interface *interface) +{ + struct ua101 *ua = usb_get_intfdata(interface); + struct list_head *midi; + + if (!ua) + return; + + mutex_lock(&devices_mutex); + + set_bit(DISCONNECTED, &ua->states); + wake_up(&ua->rate_feedback_wait); + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(ua->card); + + /* make sure that there are no pending USB requests */ + __list_for_each(midi, &ua->midi_list) + snd_usbmidi_disconnect(midi); + abort_alsa_playback(ua); + abort_alsa_capture(ua); + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + + free_usb_related_resources(ua, interface); + + devices_used &= ~(1 << ua->card_index); + + snd_card_free_when_closed(ua->card); + + mutex_unlock(&devices_mutex); +} + +static struct usb_device_id ua101_ids[] = { + { USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */ + { USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */ + { USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */ + { } +}; +MODULE_DEVICE_TABLE(usb, ua101_ids); + +static struct usb_driver ua101_driver = { + .name = "snd-ua101", + .id_table = ua101_ids, + .probe = ua101_probe, + .disconnect = ua101_disconnect, +#if 0 + .suspend = ua101_suspend, + .resume = ua101_resume, +#endif +}; + +static int __init alsa_card_ua101_init(void) +{ + return usb_register(&ua101_driver); +} + +static void __exit alsa_card_ua101_exit(void) +{ + usb_deregister(&ua101_driver); + mutex_destroy(&devices_mutex); +} + +module_init(alsa_card_ua101_init); +module_exit(alsa_card_ua101_exit); diff --git a/sound/usb/ua101.c b/sound/usb/ua101.c deleted file mode 100644 index 3d458d3b996..00000000000 --- a/sound/usb/ua101.c +++ /dev/null @@ -1,1387 +0,0 @@ -/* - * Edirol UA-101/UA-1000 driver - * Copyright (c) Clemens Ladisch - * - * This driver is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2. - * - * This driver 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 driver. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "usbaudio.h" - -MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); -MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); -MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}"); - -/* - * Should not be lower than the minimum scheduling delay of the host - * controller. Some Intel controllers need more than one frame; as long as - * that driver doesn't tell us about this, use 1.5 frames just to be sure. - */ -#define MIN_QUEUE_LENGTH 12 -/* Somewhat random. */ -#define MAX_QUEUE_LENGTH 30 -/* - * This magic value optimizes memory usage efficiency for the UA-101's packet - * sizes at all sample rates, taking into account the stupid cache pool sizes - * that usb_buffer_alloc() uses. - */ -#define DEFAULT_QUEUE_LENGTH 21 - -#define MAX_PACKET_SIZE 672 /* hardware specific */ -#define MAX_MEMORY_BUFFERS DIV_ROUND_UP(MAX_QUEUE_LENGTH, \ - PAGE_SIZE / MAX_PACKET_SIZE) - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; -static unsigned int queue_length = 21; - -module_param_array(index, int, NULL, 0444); -MODULE_PARM_DESC(index, "card index"); -module_param_array(id, charp, NULL, 0444); -MODULE_PARM_DESC(id, "ID string"); -module_param_array(enable, bool, NULL, 0444); -MODULE_PARM_DESC(enable, "enable card"); -module_param(queue_length, uint, 0644); -MODULE_PARM_DESC(queue_length, "USB queue length in microframes, " - __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH)); - -enum { - INTF_PLAYBACK, - INTF_CAPTURE, - INTF_MIDI, - - INTF_COUNT -}; - -/* bits in struct ua101::states */ -enum { - USB_CAPTURE_RUNNING, - USB_PLAYBACK_RUNNING, - ALSA_CAPTURE_OPEN, - ALSA_PLAYBACK_OPEN, - ALSA_CAPTURE_RUNNING, - ALSA_PLAYBACK_RUNNING, - CAPTURE_URB_COMPLETED, - PLAYBACK_URB_COMPLETED, - DISCONNECTED, -}; - -struct ua101 { - struct usb_device *dev; - struct snd_card *card; - struct usb_interface *intf[INTF_COUNT]; - int card_index; - struct snd_pcm *pcm; - struct list_head midi_list; - u64 format_bit; - unsigned int rate; - unsigned int packets_per_second; - spinlock_t lock; - struct mutex mutex; - unsigned long states; - - /* FIFO to synchronize playback rate to capture rate */ - unsigned int rate_feedback_start; - unsigned int rate_feedback_count; - u8 rate_feedback[MAX_QUEUE_LENGTH]; - - struct list_head ready_playback_urbs; - struct tasklet_struct playback_tasklet; - wait_queue_head_t alsa_capture_wait; - wait_queue_head_t rate_feedback_wait; - wait_queue_head_t alsa_playback_wait; - struct ua101_stream { - struct snd_pcm_substream *substream; - unsigned int usb_pipe; - unsigned int channels; - unsigned int frame_bytes; - unsigned int max_packet_bytes; - unsigned int period_pos; - unsigned int buffer_pos; - unsigned int queue_length; - struct ua101_urb { - struct urb urb; - struct usb_iso_packet_descriptor iso_frame_desc[1]; - struct list_head ready_list; - } *urbs[MAX_QUEUE_LENGTH]; - struct { - unsigned int size; - void *addr; - dma_addr_t dma; - } buffers[MAX_MEMORY_BUFFERS]; - } capture, playback; -}; - -static DEFINE_MUTEX(devices_mutex); -static unsigned int devices_used; -static struct usb_driver ua101_driver; - -static void abort_alsa_playback(struct ua101 *ua); -static void abort_alsa_capture(struct ua101 *ua); - -static const char *usb_error_string(int err) -{ - switch (err) { - case -ENODEV: - return "no device"; - case -ENOENT: - return "endpoint not enabled"; - case -EPIPE: - return "endpoint stalled"; - case -ENOSPC: - return "not enough bandwidth"; - case -ESHUTDOWN: - return "device disabled"; - case -EHOSTUNREACH: - return "device suspended"; - case -EINVAL: - case -EAGAIN: - case -EFBIG: - case -EMSGSIZE: - return "internal error"; - default: - return "unknown error"; - } -} - -static void abort_usb_capture(struct ua101 *ua) -{ - if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) { - wake_up(&ua->alsa_capture_wait); - wake_up(&ua->rate_feedback_wait); - } -} - -static void abort_usb_playback(struct ua101 *ua) -{ - if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states)) - wake_up(&ua->alsa_playback_wait); -} - -static void playback_urb_complete(struct urb *usb_urb) -{ - struct ua101_urb *urb = (struct ua101_urb *)usb_urb; - struct ua101 *ua = urb->urb.context; - unsigned long flags; - - if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ - urb->urb.status == -ENODEV || /* device removed */ - urb->urb.status == -ECONNRESET || /* unlinked */ - urb->urb.status == -ESHUTDOWN)) { /* device disabled */ - abort_usb_playback(ua); - abort_alsa_playback(ua); - return; - } - - if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { - /* append URB to FIFO */ - spin_lock_irqsave(&ua->lock, flags); - list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); - if (ua->rate_feedback_count > 0) - tasklet_schedule(&ua->playback_tasklet); - ua->playback.substream->runtime->delay -= - urb->urb.iso_frame_desc[0].length / - ua->playback.frame_bytes; - spin_unlock_irqrestore(&ua->lock, flags); - } -} - -static void first_playback_urb_complete(struct urb *urb) -{ - struct ua101 *ua = urb->context; - - urb->complete = playback_urb_complete; - playback_urb_complete(urb); - - set_bit(PLAYBACK_URB_COMPLETED, &ua->states); - wake_up(&ua->alsa_playback_wait); -} - -/* copy data from the ALSA ring buffer into the URB buffer */ -static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb, - unsigned int frames) -{ - struct snd_pcm_runtime *runtime; - unsigned int frame_bytes, frames1; - const u8 *source; - - runtime = stream->substream->runtime; - frame_bytes = stream->frame_bytes; - source = runtime->dma_area + stream->buffer_pos * frame_bytes; - if (stream->buffer_pos + frames <= runtime->buffer_size) { - memcpy(urb->transfer_buffer, source, frames * frame_bytes); - } else { - /* wrap around at end of ring buffer */ - frames1 = runtime->buffer_size - stream->buffer_pos; - memcpy(urb->transfer_buffer, source, frames1 * frame_bytes); - memcpy(urb->transfer_buffer + frames1 * frame_bytes, - runtime->dma_area, (frames - frames1) * frame_bytes); - } - - stream->buffer_pos += frames; - if (stream->buffer_pos >= runtime->buffer_size) - stream->buffer_pos -= runtime->buffer_size; - stream->period_pos += frames; - if (stream->period_pos >= runtime->period_size) { - stream->period_pos -= runtime->period_size; - return true; - } - return false; -} - -static inline void add_with_wraparound(struct ua101 *ua, - unsigned int *value, unsigned int add) -{ - *value += add; - if (*value >= ua->playback.queue_length) - *value -= ua->playback.queue_length; -} - -static void playback_tasklet(unsigned long data) -{ - struct ua101 *ua = (void *)data; - unsigned long flags; - unsigned int frames; - struct ua101_urb *urb; - bool do_period_elapsed = false; - int err; - - if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states))) - return; - - /* - * Synchronizing the playback rate to the capture rate is done by using - * the same sequence of packet sizes for both streams. - * Submitting a playback URB therefore requires both a ready URB and - * the size of the corresponding capture packet, i.e., both playback - * and capture URBs must have been completed. Since the USB core does - * not guarantee that playback and capture complete callbacks are - * called alternately, we use two FIFOs for packet sizes and read URBs; - * submitting playback URBs is possible as long as both FIFOs are - * nonempty. - */ - spin_lock_irqsave(&ua->lock, flags); - while (ua->rate_feedback_count > 0 && - !list_empty(&ua->ready_playback_urbs)) { - /* take packet size out of FIFO */ - frames = ua->rate_feedback[ua->rate_feedback_start]; - add_with_wraparound(ua, &ua->rate_feedback_start, 1); - ua->rate_feedback_count--; - - /* take URB out of FIFO */ - urb = list_first_entry(&ua->ready_playback_urbs, - struct ua101_urb, ready_list); - list_del(&urb->ready_list); - - /* fill packet with data or silence */ - urb->urb.iso_frame_desc[0].length = - frames * ua->playback.frame_bytes; - if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) - do_period_elapsed |= copy_playback_data(&ua->playback, - &urb->urb, - frames); - else - memset(urb->urb.transfer_buffer, 0, - urb->urb.iso_frame_desc[0].length); - - /* and off you go ... */ - err = usb_submit_urb(&urb->urb, GFP_ATOMIC); - if (unlikely(err < 0)) { - spin_unlock_irqrestore(&ua->lock, flags); - abort_usb_playback(ua); - abort_alsa_playback(ua); - dev_err(&ua->dev->dev, "USB request error %d: %s\n", - err, usb_error_string(err)); - return; - } - ua->playback.substream->runtime->delay += frames; - } - spin_unlock_irqrestore(&ua->lock, flags); - if (do_period_elapsed) - snd_pcm_period_elapsed(ua->playback.substream); -} - -/* copy data from the URB buffer into the ALSA ring buffer */ -static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, - unsigned int frames) -{ - struct snd_pcm_runtime *runtime; - unsigned int frame_bytes, frames1; - u8 *dest; - - runtime = stream->substream->runtime; - frame_bytes = stream->frame_bytes; - dest = runtime->dma_area + stream->buffer_pos * frame_bytes; - if (stream->buffer_pos + frames <= runtime->buffer_size) { - memcpy(dest, urb->transfer_buffer, frames * frame_bytes); - } else { - /* wrap around at end of ring buffer */ - frames1 = runtime->buffer_size - stream->buffer_pos; - memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes); - memcpy(runtime->dma_area, - urb->transfer_buffer + frames1 * frame_bytes, - (frames - frames1) * frame_bytes); - } - - stream->buffer_pos += frames; - if (stream->buffer_pos >= runtime->buffer_size) - stream->buffer_pos -= runtime->buffer_size; - stream->period_pos += frames; - if (stream->period_pos >= runtime->period_size) { - stream->period_pos -= runtime->period_size; - return true; - } - return false; -} - -static void capture_urb_complete(struct urb *urb) -{ - struct ua101 *ua = urb->context; - struct ua101_stream *stream = &ua->capture; - unsigned long flags; - unsigned int frames, write_ptr; - bool do_period_elapsed; - int err; - - if (unlikely(urb->status == -ENOENT || /* unlinked */ - urb->status == -ENODEV || /* device removed */ - urb->status == -ECONNRESET || /* unlinked */ - urb->status == -ESHUTDOWN)) /* device disabled */ - goto stream_stopped; - - if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0) - frames = urb->iso_frame_desc[0].actual_length / - stream->frame_bytes; - else - frames = 0; - - spin_lock_irqsave(&ua->lock, flags); - - if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) - do_period_elapsed = copy_capture_data(stream, urb, frames); - else - do_period_elapsed = false; - - if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { - err = usb_submit_urb(urb, GFP_ATOMIC); - if (unlikely(err < 0)) { - spin_unlock_irqrestore(&ua->lock, flags); - dev_err(&ua->dev->dev, "USB request error %d: %s\n", - err, usb_error_string(err)); - goto stream_stopped; - } - - /* append packet size to FIFO */ - write_ptr = ua->rate_feedback_start; - add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); - ua->rate_feedback[write_ptr] = frames; - if (ua->rate_feedback_count < ua->playback.queue_length) { - ua->rate_feedback_count++; - if (ua->rate_feedback_count == - ua->playback.queue_length) - wake_up(&ua->rate_feedback_wait); - } else { - /* - * Ring buffer overflow; this happens when the playback - * stream is not running. Throw away the oldest entry, - * so that the playback stream, when it starts, sees - * the most recent packet sizes. - */ - add_with_wraparound(ua, &ua->rate_feedback_start, 1); - } - if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && - !list_empty(&ua->ready_playback_urbs)) - tasklet_schedule(&ua->playback_tasklet); - } - - spin_unlock_irqrestore(&ua->lock, flags); - - if (do_period_elapsed) - snd_pcm_period_elapsed(stream->substream); - - return; - -stream_stopped: - abort_usb_playback(ua); - abort_usb_capture(ua); - abort_alsa_playback(ua); - abort_alsa_capture(ua); -} - -static void first_capture_urb_complete(struct urb *urb) -{ - struct ua101 *ua = urb->context; - - urb->complete = capture_urb_complete; - capture_urb_complete(urb); - - set_bit(CAPTURE_URB_COMPLETED, &ua->states); - wake_up(&ua->alsa_capture_wait); -} - -static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) -{ - unsigned int i; - - for (i = 0; i < stream->queue_length; ++i) { - int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); - if (err < 0) { - dev_err(&ua->dev->dev, "USB request error %d: %s\n", - err, usb_error_string(err)); - return err; - } - } - return 0; -} - -static void kill_stream_urbs(struct ua101_stream *stream) -{ - unsigned int i; - - for (i = 0; i < stream->queue_length; ++i) - usb_kill_urb(&stream->urbs[i]->urb); -} - -static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) -{ - struct usb_host_interface *alts; - - alts = ua->intf[intf_index]->cur_altsetting; - if (alts->desc.bAlternateSetting != 1) { - int err = usb_set_interface(ua->dev, - alts->desc.bInterfaceNumber, 1); - if (err < 0) { - dev_err(&ua->dev->dev, - "cannot initialize interface; error %d: %s\n", - err, usb_error_string(err)); - return err; - } - } - return 0; -} - -static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index) -{ - struct usb_host_interface *alts; - - alts = ua->intf[intf_index]->cur_altsetting; - if (alts->desc.bAlternateSetting != 0) { - int err = usb_set_interface(ua->dev, - alts->desc.bInterfaceNumber, 0); - if (err < 0 && !test_bit(DISCONNECTED, &ua->states)) - dev_warn(&ua->dev->dev, - "interface reset failed; error %d: %s\n", - err, usb_error_string(err)); - } -} - -static void stop_usb_capture(struct ua101 *ua) -{ - clear_bit(USB_CAPTURE_RUNNING, &ua->states); - - kill_stream_urbs(&ua->capture); - - disable_iso_interface(ua, INTF_CAPTURE); -} - -static int start_usb_capture(struct ua101 *ua) -{ - int err; - - if (test_bit(DISCONNECTED, &ua->states)) - return -ENODEV; - - if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) - return 0; - - kill_stream_urbs(&ua->capture); - - err = enable_iso_interface(ua, INTF_CAPTURE); - if (err < 0) - return err; - - clear_bit(CAPTURE_URB_COMPLETED, &ua->states); - ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; - ua->rate_feedback_start = 0; - ua->rate_feedback_count = 0; - - set_bit(USB_CAPTURE_RUNNING, &ua->states); - err = submit_stream_urbs(ua, &ua->capture); - if (err < 0) - stop_usb_capture(ua); - return err; -} - -static void stop_usb_playback(struct ua101 *ua) -{ - clear_bit(USB_PLAYBACK_RUNNING, &ua->states); - - kill_stream_urbs(&ua->playback); - - tasklet_kill(&ua->playback_tasklet); - - disable_iso_interface(ua, INTF_PLAYBACK); -} - -static int start_usb_playback(struct ua101 *ua) -{ - unsigned int i, frames; - struct urb *urb; - int err = 0; - - if (test_bit(DISCONNECTED, &ua->states)) - return -ENODEV; - - if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) - return 0; - - kill_stream_urbs(&ua->playback); - tasklet_kill(&ua->playback_tasklet); - - err = enable_iso_interface(ua, INTF_PLAYBACK); - if (err < 0) - return err; - - clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); - ua->playback.urbs[0]->urb.complete = - first_playback_urb_complete; - spin_lock_irq(&ua->lock); - INIT_LIST_HEAD(&ua->ready_playback_urbs); - spin_unlock_irq(&ua->lock); - - /* - * We submit the initial URBs all at once, so we have to wait for the - * packet size FIFO to be full. - */ - wait_event(ua->rate_feedback_wait, - ua->rate_feedback_count >= ua->playback.queue_length || - !test_bit(USB_CAPTURE_RUNNING, &ua->states) || - test_bit(DISCONNECTED, &ua->states)); - if (test_bit(DISCONNECTED, &ua->states)) { - stop_usb_playback(ua); - return -ENODEV; - } - if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) { - stop_usb_playback(ua); - return -EIO; - } - - for (i = 0; i < ua->playback.queue_length; ++i) { - /* all initial URBs contain silence */ - spin_lock_irq(&ua->lock); - frames = ua->rate_feedback[ua->rate_feedback_start]; - add_with_wraparound(ua, &ua->rate_feedback_start, 1); - ua->rate_feedback_count--; - spin_unlock_irq(&ua->lock); - urb = &ua->playback.urbs[i]->urb; - urb->iso_frame_desc[0].length = - frames * ua->playback.frame_bytes; - memset(urb->transfer_buffer, 0, - urb->iso_frame_desc[0].length); - } - - set_bit(USB_PLAYBACK_RUNNING, &ua->states); - err = submit_stream_urbs(ua, &ua->playback); - if (err < 0) - stop_usb_playback(ua); - return err; -} - -static void abort_alsa_capture(struct ua101 *ua) -{ - if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) - snd_pcm_stop(ua->capture.substream, SNDRV_PCM_STATE_XRUN); -} - -static void abort_alsa_playback(struct ua101 *ua) -{ - if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) - snd_pcm_stop(ua->playback.substream, SNDRV_PCM_STATE_XRUN); -} - -static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream, - unsigned int channels) -{ - int err; - - substream->runtime->hw.info = - SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_FIFO_IN_FRAMES; - substream->runtime->hw.formats = ua->format_bit; - substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate); - substream->runtime->hw.rate_min = ua->rate; - substream->runtime->hw.rate_max = ua->rate; - substream->runtime->hw.channels_min = channels; - substream->runtime->hw.channels_max = channels; - substream->runtime->hw.buffer_bytes_max = 45000 * 1024; - substream->runtime->hw.period_bytes_min = 1; - substream->runtime->hw.period_bytes_max = UINT_MAX; - substream->runtime->hw.periods_min = 2; - substream->runtime->hw.periods_max = UINT_MAX; - err = snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 1500000 / ua->packets_per_second, - 8192000); - if (err < 0) - return err; - err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); - return err; -} - -static int capture_pcm_open(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - int err; - - ua->capture.substream = substream; - err = set_stream_hw(ua, substream, ua->capture.channels); - if (err < 0) - return err; - substream->runtime->hw.fifo_size = - DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second); - substream->runtime->delay = substream->runtime->hw.fifo_size; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - if (err >= 0) - set_bit(ALSA_CAPTURE_OPEN, &ua->states); - mutex_unlock(&ua->mutex); - return err; -} - -static int playback_pcm_open(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - int err; - - ua->playback.substream = substream; - err = set_stream_hw(ua, substream, ua->playback.channels); - if (err < 0) - return err; - substream->runtime->hw.fifo_size = - DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length, - ua->packets_per_second); - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - if (err < 0) - goto error; - err = start_usb_playback(ua); - if (err < 0) { - if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) - stop_usb_capture(ua); - goto error; - } - set_bit(ALSA_PLAYBACK_OPEN, &ua->states); -error: - mutex_unlock(&ua->mutex); - return err; -} - -static int capture_pcm_close(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - - mutex_lock(&ua->mutex); - clear_bit(ALSA_CAPTURE_OPEN, &ua->states); - if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states)) - stop_usb_capture(ua); - mutex_unlock(&ua->mutex); - return 0; -} - -static int playback_pcm_close(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - - mutex_lock(&ua->mutex); - stop_usb_playback(ua); - clear_bit(ALSA_PLAYBACK_OPEN, &ua->states); - if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) - stop_usb_capture(ua); - mutex_unlock(&ua->mutex); - return 0; -} - -static int capture_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct ua101 *ua = substream->private_data; - int err; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - - return snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); -} - -static int playback_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct ua101 *ua = substream->private_data; - int err; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - if (err >= 0) - err = start_usb_playback(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - - return snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); -} - -static int ua101_pcm_hw_free(struct snd_pcm_substream *substream) -{ - return snd_pcm_lib_free_vmalloc_buffer(substream); -} - -static int capture_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - int err; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - - /* - * The EHCI driver schedules the first packet of an iso stream at 10 ms - * in the future, i.e., no data is actually captured for that long. - * Take the wait here so that the stream is known to be actually - * running when the start trigger has been called. - */ - wait_event(ua->alsa_capture_wait, - test_bit(CAPTURE_URB_COMPLETED, &ua->states) || - !test_bit(USB_CAPTURE_RUNNING, &ua->states)); - if (test_bit(DISCONNECTED, &ua->states)) - return -ENODEV; - if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) - return -EIO; - - ua->capture.period_pos = 0; - ua->capture.buffer_pos = 0; - return 0; -} - -static int playback_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct ua101 *ua = substream->private_data; - int err; - - mutex_lock(&ua->mutex); - err = start_usb_capture(ua); - if (err >= 0) - err = start_usb_playback(ua); - mutex_unlock(&ua->mutex); - if (err < 0) - return err; - - /* see the comment in capture_pcm_prepare() */ - wait_event(ua->alsa_playback_wait, - test_bit(PLAYBACK_URB_COMPLETED, &ua->states) || - !test_bit(USB_PLAYBACK_RUNNING, &ua->states)); - if (test_bit(DISCONNECTED, &ua->states)) - return -ENODEV; - if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) - return -EIO; - - substream->runtime->delay = 0; - ua->playback.period_pos = 0; - ua->playback.buffer_pos = 0; - return 0; -} - -static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct ua101 *ua = substream->private_data; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) - return -EIO; - set_bit(ALSA_CAPTURE_RUNNING, &ua->states); - return 0; - case SNDRV_PCM_TRIGGER_STOP: - clear_bit(ALSA_CAPTURE_RUNNING, &ua->states); - return 0; - default: - return -EINVAL; - } -} - -static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct ua101 *ua = substream->private_data; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) - return -EIO; - set_bit(ALSA_PLAYBACK_RUNNING, &ua->states); - return 0; - case SNDRV_PCM_TRIGGER_STOP: - clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states); - return 0; - default: - return -EINVAL; - } -} - -static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua, - struct ua101_stream *stream) -{ - unsigned long flags; - unsigned int pos; - - spin_lock_irqsave(&ua->lock, flags); - pos = stream->buffer_pos; - spin_unlock_irqrestore(&ua->lock, flags); - return pos; -} - -static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs) -{ - struct ua101 *ua = subs->private_data; - - return ua101_pcm_pointer(ua, &ua->capture); -} - -static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs) -{ - struct ua101 *ua = subs->private_data; - - return ua101_pcm_pointer(ua, &ua->playback); -} - -static struct snd_pcm_ops capture_pcm_ops = { - .open = capture_pcm_open, - .close = capture_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = capture_pcm_hw_params, - .hw_free = ua101_pcm_hw_free, - .prepare = capture_pcm_prepare, - .trigger = capture_pcm_trigger, - .pointer = capture_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, -}; - -static struct snd_pcm_ops playback_pcm_ops = { - .open = playback_pcm_open, - .close = playback_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = playback_pcm_hw_params, - .hw_free = ua101_pcm_hw_free, - .prepare = playback_pcm_prepare, - .trigger = playback_pcm_trigger, - .pointer = playback_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, -}; - -static const struct uac_format_type_i_discrete_descriptor * -find_format_descriptor(struct usb_interface *interface) -{ - struct usb_host_interface *alt; - u8 *extra; - int extralen; - - if (interface->num_altsetting != 2) { - dev_err(&interface->dev, "invalid num_altsetting\n"); - return NULL; - } - - alt = &interface->altsetting[0]; - if (alt->desc.bNumEndpoints != 0) { - dev_err(&interface->dev, "invalid bNumEndpoints\n"); - return NULL; - } - - alt = &interface->altsetting[1]; - if (alt->desc.bNumEndpoints != 1) { - dev_err(&interface->dev, "invalid bNumEndpoints\n"); - return NULL; - } - - extra = alt->extra; - extralen = alt->extralen; - while (extralen >= sizeof(struct usb_descriptor_header)) { - struct uac_format_type_i_discrete_descriptor *desc; - - desc = (struct uac_format_type_i_discrete_descriptor *)extra; - if (desc->bLength > extralen) { - dev_err(&interface->dev, "descriptor overflow\n"); - return NULL; - } - if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) && - desc->bDescriptorType == USB_DT_CS_INTERFACE && - desc->bDescriptorSubtype == UAC_FORMAT_TYPE) { - if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM || - desc->bSamFreqType != 1) { - dev_err(&interface->dev, - "invalid format type\n"); - return NULL; - } - return desc; - } - extralen -= desc->bLength; - extra += desc->bLength; - } - dev_err(&interface->dev, "sample format descriptor not found\n"); - return NULL; -} - -static int detect_usb_format(struct ua101 *ua) -{ - const struct uac_format_type_i_discrete_descriptor *fmt_capture; - const struct uac_format_type_i_discrete_descriptor *fmt_playback; - const struct usb_endpoint_descriptor *epd; - unsigned int rate2; - - fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]); - fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]); - if (!fmt_capture || !fmt_playback) - return -ENXIO; - - switch (fmt_capture->bSubframeSize) { - case 3: - ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n"); - return -ENXIO; - } - if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) { - dev_err(&ua->dev->dev, - "playback/capture sample widths do not match\n"); - return -ENXIO; - } - - if (fmt_capture->bBitResolution != 24 || - fmt_playback->bBitResolution != 24) { - dev_err(&ua->dev->dev, "sample width is not 24 bits\n"); - return -ENXIO; - } - - ua->rate = combine_triple(fmt_capture->tSamFreq[0]); - rate2 = combine_triple(fmt_playback->tSamFreq[0]); - if (ua->rate != rate2) { - dev_err(&ua->dev->dev, - "playback/capture rates do not match: %u/%u\n", - rate2, ua->rate); - return -ENXIO; - } - - switch (ua->dev->speed) { - case USB_SPEED_FULL: - ua->packets_per_second = 1000; - break; - case USB_SPEED_HIGH: - ua->packets_per_second = 8000; - break; - default: - dev_err(&ua->dev->dev, "unknown device speed\n"); - return -ENXIO; - } - - ua->capture.channels = fmt_capture->bNrChannels; - ua->playback.channels = fmt_playback->bNrChannels; - ua->capture.frame_bytes = - fmt_capture->bSubframeSize * ua->capture.channels; - ua->playback.frame_bytes = - fmt_playback->bSubframeSize * ua->playback.channels; - - epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_in(epd)) { - dev_err(&ua->dev->dev, "invalid capture endpoint\n"); - return -ENXIO; - } - ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd)); - ua->capture.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); - - epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_out(epd)) { - dev_err(&ua->dev->dev, "invalid playback endpoint\n"); - return -ENXIO; - } - ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); - ua->playback.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); - return 0; -} - -static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) -{ - unsigned int remaining_packets, packets, packets_per_page, i; - size_t size; - - stream->queue_length = queue_length; - stream->queue_length = max(stream->queue_length, - (unsigned int)MIN_QUEUE_LENGTH); - stream->queue_length = min(stream->queue_length, - (unsigned int)MAX_QUEUE_LENGTH); - - /* - * The cache pool sizes used by usb_buffer_alloc() (128, 512, 2048) are - * quite bad when used with the packet sizes of this device (e.g. 280, - * 520, 624). Therefore, we allocate and subdivide entire pages, using - * a smaller buffer only for the last chunk. - */ - remaining_packets = stream->queue_length; - packets_per_page = PAGE_SIZE / stream->max_packet_bytes; - for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) { - packets = min(remaining_packets, packets_per_page); - size = packets * stream->max_packet_bytes; - stream->buffers[i].addr = - usb_buffer_alloc(ua->dev, size, GFP_KERNEL, - &stream->buffers[i].dma); - if (!stream->buffers[i].addr) - return -ENOMEM; - stream->buffers[i].size = size; - remaining_packets -= packets; - if (!remaining_packets) - break; - } - if (remaining_packets) { - dev_err(&ua->dev->dev, "too many packets\n"); - return -ENXIO; - } - return 0; -} - -static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) - usb_buffer_free(ua->dev, - stream->buffers[i].size, - stream->buffers[i].addr, - stream->buffers[i].dma); -} - -static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, - void (*urb_complete)(struct urb *)) -{ - unsigned max_packet_size = stream->max_packet_bytes; - struct ua101_urb *urb; - unsigned int b, u = 0; - - for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { - unsigned int size = stream->buffers[b].size; - u8 *addr = stream->buffers[b].addr; - dma_addr_t dma = stream->buffers[b].dma; - - while (size >= max_packet_size) { - if (u >= stream->queue_length) - goto bufsize_error; - urb = kmalloc(sizeof(*urb), GFP_KERNEL); - if (!urb) - return -ENOMEM; - usb_init_urb(&urb->urb); - urb->urb.dev = ua->dev; - urb->urb.pipe = stream->usb_pipe; - urb->urb.transfer_flags = URB_ISO_ASAP | - URB_NO_TRANSFER_DMA_MAP; - urb->urb.transfer_buffer = addr; - urb->urb.transfer_dma = dma; - urb->urb.transfer_buffer_length = max_packet_size; - urb->urb.number_of_packets = 1; - urb->urb.interval = 1; - urb->urb.context = ua; - urb->urb.complete = urb_complete; - urb->urb.iso_frame_desc[0].offset = 0; - urb->urb.iso_frame_desc[0].length = max_packet_size; - stream->urbs[u++] = urb; - size -= max_packet_size; - addr += max_packet_size; - dma += max_packet_size; - } - } - if (u == stream->queue_length) - return 0; -bufsize_error: - dev_err(&ua->dev->dev, "internal buffer size error\n"); - return -ENXIO; -} - -static void free_stream_urbs(struct ua101_stream *stream) -{ - unsigned int i; - - for (i = 0; i < stream->queue_length; ++i) - kfree(stream->urbs[i]); -} - -static void free_usb_related_resources(struct ua101 *ua, - struct usb_interface *interface) -{ - unsigned int i; - - free_stream_urbs(&ua->capture); - free_stream_urbs(&ua->playback); - free_stream_buffers(ua, &ua->capture); - free_stream_buffers(ua, &ua->playback); - - for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) - if (ua->intf[i]) { - usb_set_intfdata(ua->intf[i], NULL); - if (ua->intf[i] != interface) - usb_driver_release_interface(&ua101_driver, - ua->intf[i]); - } -} - -static void ua101_card_free(struct snd_card *card) -{ - struct ua101 *ua = card->private_data; - - mutex_destroy(&ua->mutex); -} - -static int ua101_probe(struct usb_interface *interface, - const struct usb_device_id *usb_id) -{ - static const struct snd_usb_midi_endpoint_info midi_ep = { - .out_cables = 0x0001, - .in_cables = 0x0001 - }; - static const struct snd_usb_audio_quirk midi_quirk = { - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &midi_ep - }; - static const int intf_numbers[2][3] = { - { /* UA-101 */ - [INTF_PLAYBACK] = 0, - [INTF_CAPTURE] = 1, - [INTF_MIDI] = 2, - }, - { /* UA-1000 */ - [INTF_CAPTURE] = 1, - [INTF_PLAYBACK] = 2, - [INTF_MIDI] = 3, - }, - }; - struct snd_card *card; - struct ua101 *ua; - unsigned int card_index, i; - int is_ua1000; - const char *name; - char usb_path[32]; - int err; - - is_ua1000 = usb_id->idProduct == 0x0044; - - if (interface->altsetting->desc.bInterfaceNumber != - intf_numbers[is_ua1000][0]) - return -ENODEV; - - mutex_lock(&devices_mutex); - - for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) - if (enable[card_index] && !(devices_used & (1 << card_index))) - break; - if (card_index >= SNDRV_CARDS) { - mutex_unlock(&devices_mutex); - return -ENOENT; - } - err = snd_card_create(index[card_index], id[card_index], THIS_MODULE, - sizeof(*ua), &card); - if (err < 0) { - mutex_unlock(&devices_mutex); - return err; - } - card->private_free = ua101_card_free; - ua = card->private_data; - ua->dev = interface_to_usbdev(interface); - ua->card = card; - ua->card_index = card_index; - INIT_LIST_HEAD(&ua->midi_list); - spin_lock_init(&ua->lock); - mutex_init(&ua->mutex); - INIT_LIST_HEAD(&ua->ready_playback_urbs); - tasklet_init(&ua->playback_tasklet, - playback_tasklet, (unsigned long)ua); - init_waitqueue_head(&ua->alsa_capture_wait); - init_waitqueue_head(&ua->rate_feedback_wait); - init_waitqueue_head(&ua->alsa_playback_wait); - - ua->intf[0] = interface; - for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) { - ua->intf[i] = usb_ifnum_to_if(ua->dev, - intf_numbers[is_ua1000][i]); - if (!ua->intf[i]) { - dev_err(&ua->dev->dev, "interface %u not found\n", - intf_numbers[is_ua1000][i]); - err = -ENXIO; - goto probe_error; - } - err = usb_driver_claim_interface(&ua101_driver, - ua->intf[i], ua); - if (err < 0) { - ua->intf[i] = NULL; - err = -EBUSY; - goto probe_error; - } - } - - snd_card_set_dev(card, &interface->dev); - - err = detect_usb_format(ua); - if (err < 0) - goto probe_error; - - name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101"; - strcpy(card->driver, "UA-101"); - strcpy(card->shortname, name); - usb_make_path(ua->dev, usb_path, sizeof(usb_path)); - snprintf(ua->card->longname, sizeof(ua->card->longname), - "EDIROL %s (serial %s), %u Hz at %s, %s speed", name, - ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path, - ua->dev->speed == USB_SPEED_HIGH ? "high" : "full"); - - err = alloc_stream_buffers(ua, &ua->capture); - if (err < 0) - goto probe_error; - err = alloc_stream_buffers(ua, &ua->playback); - if (err < 0) - goto probe_error; - - err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete); - if (err < 0) - goto probe_error; - err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete); - if (err < 0) - goto probe_error; - - err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm); - if (err < 0) - goto probe_error; - ua->pcm->private_data = ua; - strcpy(ua->pcm->name, name); - snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops); - snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops); - - err = snd_usbmidi_create(card, ua->intf[INTF_MIDI], - &ua->midi_list, &midi_quirk); - if (err < 0) - goto probe_error; - - err = snd_card_register(card); - if (err < 0) - goto probe_error; - - usb_set_intfdata(interface, ua); - devices_used |= 1 << card_index; - - mutex_unlock(&devices_mutex); - return 0; - -probe_error: - free_usb_related_resources(ua, interface); - snd_card_free(card); - mutex_unlock(&devices_mutex); - return err; -} - -static void ua101_disconnect(struct usb_interface *interface) -{ - struct ua101 *ua = usb_get_intfdata(interface); - struct list_head *midi; - - if (!ua) - return; - - mutex_lock(&devices_mutex); - - set_bit(DISCONNECTED, &ua->states); - wake_up(&ua->rate_feedback_wait); - - /* make sure that userspace cannot create new requests */ - snd_card_disconnect(ua->card); - - /* make sure that there are no pending USB requests */ - __list_for_each(midi, &ua->midi_list) - snd_usbmidi_disconnect(midi); - abort_alsa_playback(ua); - abort_alsa_capture(ua); - mutex_lock(&ua->mutex); - stop_usb_playback(ua); - stop_usb_capture(ua); - mutex_unlock(&ua->mutex); - - free_usb_related_resources(ua, interface); - - devices_used &= ~(1 << ua->card_index); - - snd_card_free_when_closed(ua->card); - - mutex_unlock(&devices_mutex); -} - -static struct usb_device_id ua101_ids[] = { - { USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */ - { USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */ - { USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */ - { } -}; -MODULE_DEVICE_TABLE(usb, ua101_ids); - -static struct usb_driver ua101_driver = { - .name = "snd-ua101", - .id_table = ua101_ids, - .probe = ua101_probe, - .disconnect = ua101_disconnect, -#if 0 - .suspend = ua101_suspend, - .resume = ua101_resume, -#endif -}; - -static int __init alsa_card_ua101_init(void) -{ - return usb_register(&ua101_driver); -} - -static void __exit alsa_card_ua101_exit(void) -{ - usb_deregister(&ua101_driver); - mutex_destroy(&devices_mutex); -} - -module_init(alsa_card_ua101_init); -module_exit(alsa_card_ua101_exit); -- cgit v1.2.3-18-g5258 From 3e1aebef6fb55e35668d2d7cf608cf03f30c904f Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 4 Mar 2010 19:46:12 +0100 Subject: ALSA: usb-audio: header file cleanups Rename snd-usb-lib to snd-usbmidi-lib as MIDI functions are the only thing it actually contains. Introduce a new header file to only declare these functions. Introduced usbmixer.h for all functions exported by usbmixer.c. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/Makefile | 11 +++++----- sound/usb/misc/ua101.c | 1 + sound/usb/usbaudio.c | 3 ++- sound/usb/usbaudio.h | 51 ---------------------------------------------- sound/usb/usbmidi.c | 1 + sound/usb/usbmidi.h | 48 +++++++++++++++++++++++++++++++++++++++++++ sound/usb/usbmixer.c | 1 + sound/usb/usbmixer.h | 11 ++++++++++ sound/usb/usx2y/us122l.c | 1 + sound/usb/usx2y/usbusx2y.h | 1 + 10 files changed, 72 insertions(+), 57 deletions(-) create mode 100644 sound/usb/usbmidi.h create mode 100644 sound/usb/usbmixer.h diff --git a/sound/usb/Makefile b/sound/usb/Makefile index b0e55973c68..423d829056f 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -3,12 +3,13 @@ # snd-usb-audio-objs := usbaudio.o usbmixer.o -snd-usb-lib-objs := usbmidi.o +snd-usbmidi-lib-objs := usbmidi.o # Toplevel Module Dependency -obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o -obj-$(CONFIG_SND_USB_UA101) += snd-usb-lib.o -obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o -obj-$(CONFIG_SND_USB_US122L) += snd-usb-lib.o +obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o + +obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o +obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o +obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index e9b0ae52ca8..b4a4cb46a17 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -24,6 +24,7 @@ #include #include #include "../usbaudio.h" +#include "../usbmidi.h" MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); MODULE_AUTHOR("Clemens Ladisch "); diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index 11b0826b8fe..5b91aa02b40 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -56,7 +56,8 @@ #include #include "usbaudio.h" - +#include "usbmidi.h" +#include "usbmixer.h" MODULE_AUTHOR("Takashi Iwai "); MODULE_DESCRIPTION("USB Audio"); diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 42c299cbf63..49a691a0b28 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -21,9 +21,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* maximum number of endpoints per interface */ -#define MIDI_MAX_ENDPOINTS 2 - /* handling of USB vendor/product ID pairs as 32-bit numbers */ #define USB_ID(vendor, product) (((vendor) << 16) | (product)) #define USB_ID_VENDOR(id) ((id) >> 16) @@ -89,39 +86,6 @@ struct snd_usb_audio_quirk { const void *data; }; -/* data for QUIRK_MIDI_FIXED_ENDPOINT */ -struct snd_usb_midi_endpoint_info { - int8_t out_ep; /* ep number, 0 autodetect */ - uint8_t out_interval; /* interval for interrupt endpoints */ - int8_t in_ep; - uint8_t in_interval; - uint16_t out_cables; /* bitmask */ - uint16_t in_cables; /* bitmask */ -}; - -/* for QUIRK_MIDI_YAMAHA, data is NULL */ - -/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info - * structure (out_cables and in_cables only) */ - -/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk - * structures, terminated with .ifnum = -1 */ - -/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ - -/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ - -/* for QUIRK_AUDIO_EDIROL_UAXX, data is NULL */ - -/* for QUIRK_IGNORE_INTERFACE, data is NULL */ - -/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */ - -/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info - * structure (out_cables and in_cables only) */ - -/* for QUIRK_MIDI_CME, data is NULL */ - /* */ @@ -148,21 +112,6 @@ int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout); -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, - int ignore_error); -void snd_usb_mixer_disconnect(struct list_head *p); - -int snd_usbmidi_create(struct snd_card *card, - struct usb_interface *iface, - struct list_head *midi_list, - const struct snd_usb_audio_quirk *quirk); -void snd_usbmidi_input_stop(struct list_head* p); -void snd_usbmidi_input_start(struct list_head* p); -void snd_usbmidi_disconnect(struct list_head *p); - -void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, - unsigned char samplerate_id); - /* * retrieve usb_interface descriptor from the host interface * (conditional for compatibility with the older API) diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c index 2c59afd9961..5915a04cdb9 100644 --- a/sound/usb/usbmidi.c +++ b/sound/usb/usbmidi.c @@ -53,6 +53,7 @@ #include #include #include "usbaudio.h" +#include "usbmidi.h" /* diff --git a/sound/usb/usbmidi.h b/sound/usb/usbmidi.h new file mode 100644 index 00000000000..2089ec987c6 --- /dev/null +++ b/sound/usb/usbmidi.h @@ -0,0 +1,48 @@ +#ifndef __USBMIDI_H +#define __USBMIDI_H + +/* maximum number of endpoints per interface */ +#define MIDI_MAX_ENDPOINTS 2 + +/* data for QUIRK_MIDI_FIXED_ENDPOINT */ +struct snd_usb_midi_endpoint_info { + int8_t out_ep; /* ep number, 0 autodetect */ + uint8_t out_interval; /* interval for interrupt endpoints */ + int8_t in_ep; + uint8_t in_interval; + uint16_t out_cables; /* bitmask */ + uint16_t in_cables; /* bitmask */ +}; + +/* for QUIRK_MIDI_YAMAHA, data is NULL */ + +/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk + * structures, terminated with .ifnum = -1 */ + +/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ + +/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ + +/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */ + +/* for QUIRK_IGNORE_INTERFACE, data is NULL */ + +/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */ + +/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_MIDI_CME, data is NULL */ + +int snd_usbmidi_create(struct snd_card *card, + struct usb_interface *iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk *quirk); +void snd_usbmidi_input_stop(struct list_head* p); +void snd_usbmidi_input_start(struct list_head* p); +void snd_usbmidi_disconnect(struct list_head *p); + +#endif /* __USBMIDI_H */ diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c index 8e8f871b74c..43d53a36249 100644 --- a/sound/usb/usbmixer.c +++ b/sound/usb/usbmixer.c @@ -41,6 +41,7 @@ #include #include "usbaudio.h" +#include "usbmixer.h" /* */ diff --git a/sound/usb/usbmixer.h b/sound/usb/usbmixer.h new file mode 100644 index 00000000000..e199e4bb02f --- /dev/null +++ b/sound/usb/usbmixer.h @@ -0,0 +1,11 @@ +#ifndef __USBMIXER_H +#define __USBMIXER_H + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error); +void snd_usb_mixer_disconnect(struct list_head *p); + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id); + +#endif /* __USBMIXER_H */ diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 44deb21b177..4f6518c9b05 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -25,6 +25,7 @@ #define MODNAME "US122L" #include "usb_stream.c" #include "../usbaudio.h" +#include "../usbmidi.h" #include "us122l.h" MODULE_AUTHOR("Karsten Wiese "); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index 1d174cea352..9ab97b40a35 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -1,6 +1,7 @@ #ifndef USBUSX2Y_H #define USBUSX2Y_H #include "../usbaudio.h" +#include "../usbmidi.h" #include "usbus428ctldefs.h" #define NRURBS 2 -- cgit v1.2.3-18-g5258 From e5779998bf8b70e48a6cc208c8b61b33bd6117ea Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 4 Mar 2010 19:46:13 +0100 Subject: ALSA: usb-audio: refactor code Clean up the usb audio driver by factoring out a lot of functions to separate files. Code for procfs, quirks, urbs, format parsers etc all got a new home now. Moved almost all special quirk handling to quirks.c and introduced new generic functions to handle them, so the exceptions do not pollute the whole driver. Renamed usbaudio.c to card.c because this is what it actually does now. Renamed usbmidi.c to midi.c for namespace clarity. Removed more things from usbaudio.h. The non-standard drivers were adopted accordingly. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/Makefile | 13 +- sound/usb/card.c | 648 +++++++ sound/usb/card.h | 105 ++ sound/usb/debug.h | 15 + sound/usb/endpoint.c | 358 ++++ sound/usb/endpoint.h | 11 + sound/usb/format.c | 445 +++++ sound/usb/format.h | 8 + sound/usb/helper.c | 112 ++ sound/usb/helper.h | 32 + sound/usb/midi.c | 2069 ++++++++++++++++++++++ sound/usb/midi.h | 48 + sound/usb/misc/ua101.c | 2 +- sound/usb/pcm.c | 845 +++++++++ sound/usb/pcm.h | 14 + sound/usb/proc.c | 163 ++ sound/usb/proc.h | 8 + sound/usb/quirks-table.h | 2248 ++++++++++++++++++++++++ sound/usb/quirks.c | 592 +++++++ sound/usb/quirks.h | 23 + sound/usb/urb.c | 989 +++++++++++ sound/usb/urb.h | 21 + sound/usb/usbaudio.c | 4051 -------------------------------------------- sound/usb/usbaudio.h | 42 +- sound/usb/usbmidi.c | 2069 ---------------------- sound/usb/usbmidi.h | 48 - sound/usb/usbmixer.c | 1 + sound/usb/usbquirks.h | 2248 ------------------------ sound/usb/usx2y/us122l.c | 2 +- sound/usb/usx2y/usbusx2y.h | 2 +- 30 files changed, 8774 insertions(+), 8458 deletions(-) create mode 100644 sound/usb/card.c create mode 100644 sound/usb/card.h create mode 100644 sound/usb/debug.h create mode 100644 sound/usb/endpoint.c create mode 100644 sound/usb/endpoint.h create mode 100644 sound/usb/format.c create mode 100644 sound/usb/format.h create mode 100644 sound/usb/helper.c create mode 100644 sound/usb/helper.h create mode 100644 sound/usb/midi.c create mode 100644 sound/usb/midi.h create mode 100644 sound/usb/pcm.c create mode 100644 sound/usb/pcm.h create mode 100644 sound/usb/proc.c create mode 100644 sound/usb/proc.h create mode 100644 sound/usb/quirks-table.h create mode 100644 sound/usb/quirks.c create mode 100644 sound/usb/quirks.h create mode 100644 sound/usb/urb.c create mode 100644 sound/usb/urb.h delete mode 100644 sound/usb/usbaudio.c delete mode 100644 sound/usb/usbmidi.c delete mode 100644 sound/usb/usbmidi.h delete mode 100644 sound/usb/usbquirks.h diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 423d829056f..0758d8dc8cd 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -2,8 +2,17 @@ # Makefile for ALSA # -snd-usb-audio-objs := usbaudio.o usbmixer.o -snd-usbmidi-lib-objs := usbmidi.o +snd-usb-audio-objs := card.o \ + usbmixer.o \ + proc.o \ + quirks.o \ + format.o \ + endpoint.o \ + urb.o \ + pcm.o \ + helper.o + +snd-usbmidi-lib-objs := midi.o # Toplevel Module Dependency obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o diff --git a/sound/usb/card.c b/sound/usb/card.c new file mode 100644 index 00000000000..426aabc729d --- /dev/null +++ b/sound/usb/card.c @@ -0,0 +1,648 @@ +/* + * (Tentative) USB Audio Driver for ALSA + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * + * 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. + * + * 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 + * + * + * NOTES: + * + * - async unlink should be used for avoiding the sleep inside lock. + * 2.4.22 usb-uhci seems buggy for async unlinking and results in + * oops. in such a cse, pass async_unlink=0 option. + * - the linked URBs would be preferred but not used so far because of + * the instability of unlinking. + * - type II is not supported properly. there is no device which supports + * this type *correctly*. SB extigy looks as if it supports, but it's + * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream). + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "midi.h" +#include "usbmixer.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "helper.h" +#include "debug.h" +#include "pcm.h" +#include "urb.h" +#include "format.h" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("USB Audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */ +/* Vendor/product IDs for this card */ +static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; +static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; +static int nrpacks = 8; /* max. number of packets per urb */ +static int async_unlink = 1; +static int device_setup[SNDRV_CARDS]; /* device parameter for this card */ +static int ignore_ctl_error; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the USB audio adapter."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable USB audio adapter."); +module_param_array(vid, int, NULL, 0444); +MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device."); +module_param_array(pid, int, NULL, 0444); +MODULE_PARM_DESC(pid, "Product ID for the USB audio device."); +module_param(nrpacks, int, 0644); +MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB."); +module_param(async_unlink, bool, 0444); +MODULE_PARM_DESC(async_unlink, "Use async unlink mode."); +module_param_array(device_setup, int, NULL, 0444); +MODULE_PARM_DESC(device_setup, "Specific device setup (if needed)."); +module_param(ignore_ctl_error, bool, 0444); +MODULE_PARM_DESC(ignore_ctl_error, + "Ignore errors from USB controller for mixer interfaces."); + +/* + * we keep the snd_usb_audio_t instances by ourselves for merging + * the all interfaces on the same card as one sound device. + */ + +static DEFINE_MUTEX(register_mutex); +static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; +static struct usb_driver usb_audio_driver; + +/* + * disconnect streams + * called from snd_usb_audio_disconnect() + */ +static void snd_usb_stream_disconnect(struct list_head *head) +{ + int idx; + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + + as = list_entry(head, struct snd_usb_stream, list); + for (idx = 0; idx < 2; idx++) { + subs = &as->substream[idx]; + if (!subs->num_formats) + return; + snd_usb_release_substream_urbs(subs, 1); + subs->interface = -1; + } +} + +static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_interface *iface = usb_ifnum_to_if(dev, interface); + + if (!iface) { + snd_printk(KERN_ERR "%d:%u:%d : does not exist\n", + dev->devnum, ctrlif, interface); + return -EINVAL; + } + + if (usb_interface_claimed(iface)) { + snd_printdd(KERN_INFO "%d:%d:%d: skipping, already claimed\n", + dev->devnum, ctrlif, interface); + return -EINVAL; + } + + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && + altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { + int err = snd_usbmidi_create(chip->card, iface, + &chip->midi_list, NULL); + if (err < 0) { + snd_printk(KERN_ERR "%d:%u:%d: cannot create sequencer device\n", + dev->devnum, ctrlif, interface); + return -EINVAL; + } + usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); + + return 0; + } + + if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && + altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || + altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) { + snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n", + dev->devnum, ctrlif, interface, altsd->bInterfaceClass); + /* skip non-supported classes */ + return -EINVAL; + } + + if (snd_usb_get_speed(dev) == USB_SPEED_LOW) { + snd_printk(KERN_ERR "low speed audio streaming not supported\n"); + return -EINVAL; + } + + if (! snd_usb_parse_audio_endpoints(chip, interface)) { + usb_set_interface(dev, interface, 0); /* reset the current interface */ + usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); + return -EINVAL; + } + + return 0; +} + +/* + * parse audio control descriptor and create pcm/midi streams + */ +static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *host_iface; + struct usb_interface_descriptor *altsd; + void *control_header; + int i, protocol; + + /* find audiocontrol interface */ + host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; + control_header = snd_usb_find_csint_desc(host_iface->extra, + host_iface->extralen, + NULL, UAC_HEADER); + altsd = get_iface_desc(host_iface); + protocol = altsd->bInterfaceProtocol; + + if (!control_header) { + snd_printk(KERN_ERR "cannot find UAC_HEADER\n"); + return -EINVAL; + } + + switch (protocol) { + case UAC_VERSION_1: { + struct uac_ac_header_descriptor_v1 *h1 = control_header; + + if (!h1->bInCollection) { + snd_printk(KERN_INFO "skipping empty audio interface (v1)\n"); + return -EINVAL; + } + + if (h1->bLength < sizeof(*h1) + h1->bInCollection) { + snd_printk(KERN_ERR "invalid UAC_HEADER (v1)\n"); + return -EINVAL; + } + + for (i = 0; i < h1->bInCollection; i++) + snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]); + + break; + } + + case UAC_VERSION_2: { + struct uac_clock_source_descriptor *cs; + struct usb_interface_assoc_descriptor *assoc = + usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + + if (!assoc) { + snd_printk(KERN_ERR "Audio class v2 interfaces need an interface association\n"); + return -EINVAL; + } + + /* FIXME: for now, we expect there is at least one clock source + * descriptor and we always take the first one. + * We should properly support devices with multiple clock sources, + * clock selectors and sample rate conversion units. */ + + cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, + NULL, UAC_CLOCK_SOURCE); + + if (!cs) { + snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n"); + return -EINVAL; + } + + chip->clock_id = cs->bClockID; + + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + if (intf != ctrlif) + snd_usb_create_stream(chip, ctrlif, intf); + } + + break; + } + + default: + snd_printk(KERN_ERR "unknown protocol version 0x%02x\n", protocol); + return -EINVAL; + } + + return 0; +} + +/* + * free the chip instance + * + * here we have to do not much, since pcm and controls are already freed + * + */ + +static int snd_usb_audio_free(struct snd_usb_audio *chip) +{ + kfree(chip); + return 0; +} + +static int snd_usb_audio_dev_free(struct snd_device *device) +{ + struct snd_usb_audio *chip = device->device_data; + return snd_usb_audio_free(chip); +} + + +/* + * create a chip instance and set its names. + */ +static int snd_usb_audio_create(struct usb_device *dev, int idx, + const struct snd_usb_audio_quirk *quirk, + struct snd_usb_audio **rchip) +{ + struct snd_card *card; + struct snd_usb_audio *chip; + int err, len; + char component[14]; + static struct snd_device_ops ops = { + .dev_free = snd_usb_audio_dev_free, + }; + + *rchip = NULL; + + if (snd_usb_get_speed(dev) != USB_SPEED_LOW && + snd_usb_get_speed(dev) != USB_SPEED_FULL && + snd_usb_get_speed(dev) != USB_SPEED_HIGH) { + snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev)); + return -ENXIO; + } + + err = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card); + if (err < 0) { + snd_printk(KERN_ERR "cannot create card instance %d\n", idx); + return err; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (! chip) { + snd_card_free(card); + return -ENOMEM; + } + + chip->index = idx; + chip->dev = dev; + chip->card = card; + chip->setup = device_setup[idx]; + chip->nrpacks = nrpacks; + chip->async_unlink = async_unlink; + + chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + INIT_LIST_HEAD(&chip->pcm_list); + INIT_LIST_HEAD(&chip->midi_list); + INIT_LIST_HEAD(&chip->mixer_list); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_usb_audio_free(chip); + snd_card_free(card); + return err; + } + + strcpy(card->driver, "USB-Audio"); + sprintf(component, "USB%04x:%04x", + USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); + snd_component_add(card, component); + + /* retrieve the device string as shortname */ + if (quirk && quirk->product_name) { + strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname)); + } else { + if (!dev->descriptor.iProduct || + usb_string(dev, dev->descriptor.iProduct, + card->shortname, sizeof(card->shortname)) <= 0) { + /* no name available from anywhere, so use ID */ + sprintf(card->shortname, "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + } + } + + /* retrieve the vendor and device strings as longname */ + if (quirk && quirk->vendor_name) { + len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); + } else { + if (dev->descriptor.iManufacturer) + len = usb_string(dev, dev->descriptor.iManufacturer, + card->longname, sizeof(card->longname)); + else + len = 0; + /* we don't really care if there isn't any vendor string */ + } + if (len > 0) + strlcat(card->longname, " ", sizeof(card->longname)); + + strlcat(card->longname, card->shortname, sizeof(card->longname)); + + len = strlcat(card->longname, " at ", sizeof(card->longname)); + + if (len < sizeof(card->longname)) + usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); + + strlcat(card->longname, + snd_usb_get_speed(dev) == USB_SPEED_LOW ? ", low speed" : + snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" : + ", high speed", + sizeof(card->longname)); + + snd_usb_audio_create_proc(chip); + + *rchip = chip; + return 0; +} + +/* + * probe the active usb device + * + * note that this can be called multiple times per a device, when it + * includes multiple audio control interfaces. + * + * thus we check the usb device pointer and creates the card instance + * only at the first time. the successive calls of this function will + * append the pcm interface to the corresponding card. + */ +static void *snd_usb_audio_probe(struct usb_device *dev, + struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info; + int i, err; + struct snd_usb_audio *chip; + struct usb_host_interface *alts; + int ifnum; + u32 id; + + alts = &intf->altsetting[0]; + ifnum = get_iface_desc(alts)->bInterfaceNumber; + id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) + goto __err_val; + + if (snd_usb_apply_boot_quirk(dev, intf, quirk) < 0) + goto __err_val; + + /* + * found a config. now register to ALSA + */ + + /* check whether it's already registered */ + chip = NULL; + mutex_lock(®ister_mutex); + for (i = 0; i < SNDRV_CARDS; i++) { + if (usb_chip[i] && usb_chip[i]->dev == dev) { + if (usb_chip[i]->shutdown) { + snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n"); + goto __error; + } + chip = usb_chip[i]; + break; + } + } + if (! chip) { + /* it's a fresh one. + * now look for an empty slot and create a new card instance + */ + for (i = 0; i < SNDRV_CARDS; i++) + if (enable[i] && ! usb_chip[i] && + (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && + (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { + if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) { + goto __error; + } + snd_card_set_dev(chip->card, &intf->dev); + break; + } + if (!chip) { + printk(KERN_ERR "no available usb audio device\n"); + goto __error; + } + } + + chip->txfr_quirk = 0; + err = 1; /* continue */ + if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) { + /* need some special handlings */ + if ((err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk)) < 0) + goto __error; + } + + if (err > 0) { + /* create normal USB audio interfaces */ + if (snd_usb_create_streams(chip, ifnum) < 0 || + snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) { + goto __error; + } + } + + /* we are allowed to call snd_card_register() many times */ + if (snd_card_register(chip->card) < 0) { + goto __error; + } + + usb_chip[chip->index] = chip; + chip->num_interfaces++; + mutex_unlock(®ister_mutex); + return chip; + + __error: + if (chip && !chip->num_interfaces) + snd_card_free(chip->card); + mutex_unlock(®ister_mutex); + __err_val: + return NULL; +} + +/* + * we need to take care of counter, since disconnection can be called also + * many times as well as usb_audio_probe(). + */ +static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr) +{ + struct snd_usb_audio *chip; + struct snd_card *card; + struct list_head *p; + + if (ptr == (void *)-1L) + return; + + chip = ptr; + card = chip->card; + mutex_lock(®ister_mutex); + chip->shutdown = 1; + chip->num_interfaces--; + if (chip->num_interfaces <= 0) { + snd_card_disconnect(card); + /* release the pcm resources */ + list_for_each(p, &chip->pcm_list) { + snd_usb_stream_disconnect(p); + } + /* release the midi resources */ + list_for_each(p, &chip->midi_list) { + snd_usbmidi_disconnect(p); + } + /* release mixer resources */ + list_for_each(p, &chip->mixer_list) { + snd_usb_mixer_disconnect(p); + } + usb_chip[chip->index] = NULL; + mutex_unlock(®ister_mutex); + snd_card_free_when_closed(card); + } else { + mutex_unlock(®ister_mutex); + } +} + +/* + * new 2.5 USB kernel API + */ +static int usb_audio_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + void *chip; + chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id); + if (chip) { + usb_set_intfdata(intf, chip); + return 0; + } else + return -EIO; +} + +static void usb_audio_disconnect(struct usb_interface *intf) +{ + snd_usb_audio_disconnect(interface_to_usbdev(intf), + usb_get_intfdata(intf)); +} + +#ifdef CONFIG_PM +static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct list_head *p; + struct snd_usb_stream *as; + + if (chip == (void *)-1L) + return 0; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + if (!chip->num_suspended_intf++) { + list_for_each(p, &chip->pcm_list) { + as = list_entry(p, struct snd_usb_stream, list); + snd_pcm_suspend_all(as->pcm); + } + } + + return 0; +} + +static int usb_audio_resume(struct usb_interface *intf) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + + if (chip == (void *)-1L) + return 0; + if (--chip->num_suspended_intf) + return 0; + /* + * ALSA leaves material resumption to user space + * we just notify + */ + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif /* CONFIG_PM */ + +static struct usb_device_id usb_audio_ids [] = { +#include "quirks-table.h" + { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_audio_ids); + +/* + * entry point for linux usb interface + */ + +static struct usb_driver usb_audio_driver = { + .name = "snd-usb-audio", + .probe = usb_audio_probe, + .disconnect = usb_audio_disconnect, + .suspend = usb_audio_suspend, + .resume = usb_audio_resume, + .id_table = usb_audio_ids, +}; + +static int __init snd_usb_audio_init(void) +{ + if (nrpacks < 1 || nrpacks > MAX_PACKS) { + printk(KERN_WARNING "invalid nrpacks value.\n"); + return -EINVAL; + } + return usb_register(&usb_audio_driver); +} + +static void __exit snd_usb_audio_cleanup(void) +{ + usb_deregister(&usb_audio_driver); +} + +module_init(snd_usb_audio_init); +module_exit(snd_usb_audio_cleanup); diff --git a/sound/usb/card.h b/sound/usb/card.h new file mode 100644 index 00000000000..71f03c15103 --- /dev/null +++ b/sound/usb/card.h @@ -0,0 +1,105 @@ +#ifndef __USBAUDIO_CARD_H +#define __USBAUDIO_CARD_H + +#define MAX_PACKS 20 +#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */ +#define MAX_URBS 8 +#define SYNC_URBS 4 /* always four urbs for sync */ +#define MAX_QUEUE 24 /* try not to exceed this queue length, in ms */ + +struct audioformat { + struct list_head list; + snd_pcm_format_t format; /* format type */ + unsigned int channels; /* # channels */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + unsigned int frame_size; /* samples per frame for non-audio */ + int iface; /* interface number */ + unsigned char altsetting; /* corresponding alternate setting */ + unsigned char altset_idx; /* array index of altenate setting */ + unsigned char attributes; /* corresponding attributes of cs endpoint */ + unsigned char endpoint; /* endpoint */ + unsigned char ep_attr; /* endpoint attributes */ + unsigned char datainterval; /* log_2 of data packet interval */ + unsigned int maxpacksize; /* max. packet size */ + unsigned int rates; /* rate bitmasks */ + unsigned int rate_min, rate_max; /* min/max rates */ + unsigned int nr_rates; /* number of rate table entries */ + unsigned int *rate_table; /* rate table */ +}; + +struct snd_usb_substream; + +struct snd_urb_ctx { + struct urb *urb; + unsigned int buffer_size; /* size of data buffer, if data URB */ + struct snd_usb_substream *subs; + int index; /* index for urb array */ + int packets; /* number of packets per urb */ +}; + +struct snd_urb_ops { + int (*prepare)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); + int (*retire)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); + int (*prepare_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); + int (*retire_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); +}; + +struct snd_usb_substream { + struct snd_usb_stream *stream; + struct usb_device *dev; + struct snd_pcm_substream *pcm_substream; + int direction; /* playback or capture */ + int interface; /* current interface */ + int endpoint; /* assigned endpoint */ + struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ + unsigned int cur_rate; /* current rate (for hw_params callback) */ + unsigned int period_bytes; /* current period bytes (for hw_params callback) */ + unsigned int format; /* USB data format */ + unsigned int datapipe; /* the data i/o pipe */ + unsigned int syncpipe; /* 1 - async out or adaptive in */ + unsigned int datainterval; /* log_2 of data packet interval */ + unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ + unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ + unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ + unsigned int freqmax; /* maximum sampling rate, used for buffer management */ + unsigned int phase; /* phase accumulator */ + unsigned int maxpacksize; /* max packet size in bytes */ + unsigned int maxframesize; /* max packet size in frames */ + unsigned int curpacksize; /* current packet size in bytes (for capture) */ + unsigned int curframesize; /* current packet size in frames (for capture) */ + unsigned int fill_max: 1; /* fill max packet size always */ + unsigned int txfr_quirk:1; /* allow sub-frame alignment */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + + unsigned int running: 1; /* running status */ + + unsigned int hwptr_done; /* processed byte position in the buffer */ + unsigned int transfer_done; /* processed frames since last period update */ + unsigned long active_mask; /* bitmask of active urbs */ + unsigned long unlink_mask; /* bitmask of unlinked urbs */ + + unsigned int nurbs; /* # urbs */ + struct snd_urb_ctx dataurb[MAX_URBS]; /* data urb table */ + struct snd_urb_ctx syncurb[SYNC_URBS]; /* sync urb table */ + char *syncbuf; /* sync buffer for all sync URBs */ + dma_addr_t sync_dma; /* DMA address of syncbuf */ + + u64 formats; /* format bitmasks (all or'ed) */ + unsigned int num_formats; /* number of supported audio formats (list) */ + struct list_head fmt_list; /* format list */ + struct snd_pcm_hw_constraint_list rate_list; /* limited rates */ + spinlock_t lock; + + struct snd_urb_ops ops; /* callbacks (must be filled at init) */ +}; + +struct snd_usb_stream { + struct snd_usb_audio *chip; + struct snd_pcm *pcm; + int pcm_index; + unsigned int fmt_type; /* USB audio format type (1-3) */ + struct snd_usb_substream substream[2]; + struct list_head list; +}; + +#endif /* __USBAUDIO_CARD_H */ diff --git a/sound/usb/debug.h b/sound/usb/debug.h new file mode 100644 index 00000000000..343ec2d9ee6 --- /dev/null +++ b/sound/usb/debug.h @@ -0,0 +1,15 @@ +#ifndef __USBAUDIO_DEBUG_H +#define __USBAUDIO_DEBUG_H + +/* + * h/w constraints + */ + +#ifdef HW_CONST_DEBUG +#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args) +#else +#define hwc_debug(fmt, args...) /**/ +#endif + +#endif /* __USBAUDIO_DEBUG_H */ + diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c new file mode 100644 index 00000000000..3f53dee1270 --- /dev/null +++ b/sound/usb/endpoint.c @@ -0,0 +1,358 @@ +/* + * 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. + * + * 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 + * + */ + +#include +#include +#include + +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "urb.h" +#include "pcm.h" +#include "helper.h" +#include "format.h" + +/* + * free a substream + */ +static void free_substream(struct snd_usb_substream *subs) +{ + struct list_head *p, *n; + + if (!subs->num_formats) + return; /* not initialized */ + list_for_each_safe(p, n, &subs->fmt_list) { + struct audioformat *fp = list_entry(p, struct audioformat, list); + kfree(fp->rate_table); + kfree(fp); + } + kfree(subs->rate_list.list); +} + + +/* + * free a usb stream instance + */ +static void snd_usb_audio_stream_free(struct snd_usb_stream *stream) +{ + free_substream(&stream->substream[0]); + free_substream(&stream->substream[1]); + list_del(&stream->list); + kfree(stream); +} + +static void snd_usb_audio_pcm_free(struct snd_pcm *pcm) +{ + struct snd_usb_stream *stream = pcm->private_data; + if (stream) { + stream->pcm = NULL; + snd_usb_audio_stream_free(stream); + } +} + + +/* + * add this endpoint to the chip instance. + * if a stream with the same endpoint already exists, append to it. + * if not, create a new pcm stream. + */ +int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct audioformat *fp) +{ + struct list_head *p; + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + struct snd_pcm *pcm; + int err; + + list_for_each(p, &chip->pcm_list) { + as = list_entry(p, struct snd_usb_stream, list); + if (as->fmt_type != fp->fmt_type) + continue; + subs = &as->substream[stream]; + if (!subs->endpoint) + continue; + if (subs->endpoint == fp->endpoint) { + list_add_tail(&fp->list, &subs->fmt_list); + subs->num_formats++; + subs->formats |= 1ULL << fp->format; + return 0; + } + } + /* look for an empty stream */ + list_for_each(p, &chip->pcm_list) { + as = list_entry(p, struct snd_usb_stream, list); + if (as->fmt_type != fp->fmt_type) + continue; + subs = &as->substream[stream]; + if (subs->endpoint) + continue; + err = snd_pcm_new_stream(as->pcm, stream, 1); + if (err < 0) + return err; + snd_usb_init_substream(as, stream, fp); + return 0; + } + + /* create a new pcm */ + as = kzalloc(sizeof(*as), GFP_KERNEL); + if (!as) + return -ENOMEM; + as->pcm_index = chip->pcm_devs; + as->chip = chip; + as->fmt_type = fp->fmt_type; + err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1, + &pcm); + if (err < 0) { + kfree(as); + return err; + } + as->pcm = pcm; + pcm->private_data = as; + pcm->private_free = snd_usb_audio_pcm_free; + pcm->info_flags = 0; + if (chip->pcm_devs > 0) + sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs); + else + strcpy(pcm->name, "USB Audio"); + + snd_usb_init_substream(as, stream, fp); + + list_add(&as->list, &chip->pcm_list); + chip->pcm_devs++; + + snd_usb_proc_pcm_format_add(as); + + return 0; +} + +int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) +{ + struct usb_device *dev; + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + int i, altno, err, stream; + int format = 0, num_channels = 0; + struct audioformat *fp = NULL; + unsigned char *fmt, *csep; + int num, protocol; + + dev = chip->dev; + + /* parse the interface's altsettings */ + iface = usb_ifnum_to_if(dev, iface_no); + + num = iface->num_altsetting; + + /* + * Dallas DS4201 workaround: It presents 5 altsettings, but the last + * one misses syncpipe, and does not produce any sound. + */ + if (chip->usb_id == USB_ID(0x04fa, 0x4201)) + num = 4; + + for (i = 0; i < num; i++) { + alts = &iface->altsetting[i]; + altsd = get_iface_desc(alts); + protocol = altsd->bInterfaceProtocol; + /* skip invalid one */ + if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && + altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || + (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING && + altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC) || + altsd->bNumEndpoints < 1 || + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0) + continue; + /* must be isochronous */ + if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != + USB_ENDPOINT_XFER_ISOC) + continue; + /* check direction */ + stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + altno = altsd->bAlternateSetting; + + if (snd_usb_apply_interface_quirk(chip, iface_no, altno)) + continue; + + /* get audio formats */ + switch (protocol) { + case UAC_VERSION_1: { + struct uac_as_header_descriptor_v1 *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); + + if (!as) { + snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n", + dev->devnum, iface_no, altno); + continue; + } + + if (as->bLength < sizeof(*as)) { + snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n", + dev->devnum, iface_no, altno); + continue; + } + + format = le16_to_cpu(as->wFormatTag); /* remember the format value */ + break; + } + + case UAC_VERSION_2: { + struct uac_as_header_descriptor_v2 *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); + + if (!as) { + snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n", + dev->devnum, iface_no, altno); + continue; + } + + if (as->bLength < sizeof(*as)) { + snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n", + dev->devnum, iface_no, altno); + continue; + } + + num_channels = as->bNrChannels; + format = le32_to_cpu(as->bmFormats); + + break; + } + + default: + snd_printk(KERN_ERR "%d:%u:%d : unknown interface protocol %04x\n", + dev->devnum, iface_no, altno, protocol); + continue; + } + + /* get format type */ + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE); + if (!fmt) { + snd_printk(KERN_ERR "%d:%u:%d : no UAC_FORMAT_TYPE desc\n", + dev->devnum, iface_no, altno); + continue; + } + if (((protocol == UAC_VERSION_1) && (fmt[0] < 8)) || + ((protocol == UAC_VERSION_2) && (fmt[0] != 6))) { + snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n", + dev->devnum, iface_no, altno); + continue; + } + + /* + * Blue Microphones workaround: The last altsetting is identical + * with the previous one, except for a larger packet size, but + * is actually a mislabeled two-channel setting; ignore it. + */ + if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 && + fp && fp->altsetting == 1 && fp->channels == 1 && + fp->format == SNDRV_PCM_FORMAT_S16_LE && + protocol == UAC_VERSION_1 && + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == + fp->maxpacksize * 2) + continue; + + csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT); + /* Creamware Noah has this descriptor after the 2nd endpoint */ + if (!csep && altsd->bNumEndpoints >= 2) + csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT); + if (!csep || csep[0] < 7 || csep[2] != UAC_EP_GENERAL) { + snd_printk(KERN_WARNING "%d:%u:%d : no or invalid" + " class specific endpoint descriptor\n", + dev->devnum, iface_no, altno); + csep = NULL; + } + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (! fp) { + snd_printk(KERN_ERR "cannot malloc\n"); + return -ENOMEM; + } + + fp->iface = iface_no; + fp->altsetting = altno; + fp->altset_idx = i; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + /* num_channels is only set for v2 interfaces */ + fp->channels = num_channels; + if (snd_usb_get_speed(dev) == USB_SPEED_HIGH) + fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) + * (fp->maxpacksize & 0x7ff); + fp->attributes = csep ? csep[3] : 0; + + /* some quirks for attributes here */ + + switch (chip->usb_id) { + case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ + /* Optoplay sets the sample rate attribute although + * it seems not supporting it in fact. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE; + break; + case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */ + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + /* doesn't set the sample rate attribute, but supports it */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; + case USB_ID(0x047f, 0x0ca1): /* plantronics headset */ + case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is + an older model 77d:223) */ + /* + * plantronics headset and Griffin iMic have set adaptive-in + * although it's really not... + */ + fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE; + else + fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC; + break; + } + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) { + kfree(fp->rate_table); + kfree(fp); + continue; + } + + snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint %#x\n", dev->devnum, iface_no, altno, fp->endpoint); + err = snd_usb_add_audio_endpoint(chip, stream, fp); + if (err < 0) { + kfree(fp->rate_table); + kfree(fp); + return err; + } + /* try to set the interface... */ + usb_set_interface(chip->dev, iface_no, altno); + snd_usb_init_pitch(chip->dev, iface_no, alts, fp); + snd_usb_init_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max); + } + return 0; +} + diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h new file mode 100644 index 00000000000..64dd0db023b --- /dev/null +++ b/sound/usb/endpoint.h @@ -0,0 +1,11 @@ +#ifndef __USBAUDIO_ENDPOINT_H +#define __USBAUDIO_ENDPOINT_H + +int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, + int iface_no); + +int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip, + int stream, + struct audioformat *fp); + +#endif /* __USBAUDIO_ENDPOINT_H */ diff --git a/sound/usb/format.c b/sound/usb/format.c new file mode 100644 index 00000000000..cbfe0c23dbd --- /dev/null +++ b/sound/usb/format.c @@ -0,0 +1,445 @@ +/* + * 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. + * + * 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 + * + */ + +#include +#include +#include + +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "quirks.h" +#include "helper.h" +#include "debug.h" + +/* + * parse the audio format type I descriptor + * and returns the corresponding pcm format + * + * @dev: usb device + * @fp: audioformat record + * @format: the format tag (wFormatTag) + * @fmt: the format type descriptor + */ +static int parse_audio_format_i_type(struct snd_usb_audio *chip, + struct audioformat *fp, + int format, void *_fmt, + int protocol) +{ + int pcm_format, i; + int sample_width, sample_bytes; + + switch (protocol) { + case UAC_VERSION_1: { + struct uac_format_type_i_discrete_descriptor *fmt = _fmt; + sample_width = fmt->bBitResolution; + sample_bytes = fmt->bSubframeSize; + break; + } + + case UAC_VERSION_2: { + struct uac_format_type_i_ext_descriptor *fmt = _fmt; + sample_width = fmt->bBitResolution; + sample_bytes = fmt->bSubslotSize; + + /* + * FIXME + * USB audio class v2 devices specify a bitmap of possible + * audio formats rather than one fix value. For now, we just + * pick one of them and report that as the only possible + * value for this setting. + * The bit allocation map is in fact compatible to the + * wFormatTag of the v1 AS streaming descriptors, which is why + * we can simply map the matrix. + */ + + for (i = 0; i < 5; i++) + if (format & (1UL << i)) { + format = i + 1; + break; + } + + break; + } + + default: + return -EINVAL; + } + + /* FIXME: correct endianess and sign? */ + pcm_format = -1; + + switch (format) { + case UAC_FORMAT_TYPE_I_UNDEFINED: /* some devices don't define this correctly... */ + snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n", + chip->dev->devnum, fp->iface, fp->altsetting); + /* fall-through */ + case UAC_FORMAT_TYPE_I_PCM: + if (sample_width > sample_bytes * 8) { + snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n", + chip->dev->devnum, fp->iface, fp->altsetting, + sample_width, sample_bytes); + } + /* check the format byte size */ + switch (sample_bytes) { + case 1: + pcm_format = SNDRV_PCM_FORMAT_S8; + break; + case 2: + if (snd_usb_is_big_endian_format(chip, fp)) + pcm_format = SNDRV_PCM_FORMAT_S16_BE; /* grrr, big endian!! */ + else + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + break; + case 3: + if (snd_usb_is_big_endian_format(chip, fp)) + pcm_format = SNDRV_PCM_FORMAT_S24_3BE; /* grrr, big endian!! */ + else + pcm_format = SNDRV_PCM_FORMAT_S24_3LE; + break; + case 4: + pcm_format = SNDRV_PCM_FORMAT_S32_LE; + break; + default: + snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n", + chip->dev->devnum, fp->iface, fp->altsetting, + sample_width, sample_bytes); + break; + } + break; + case UAC_FORMAT_TYPE_I_PCM8: + pcm_format = SNDRV_PCM_FORMAT_U8; + + /* Dallas DS4201 workaround: it advertises U8 format, but really + supports S8. */ + if (chip->usb_id == USB_ID(0x04fa, 0x4201)) + pcm_format = SNDRV_PCM_FORMAT_S8; + break; + case UAC_FORMAT_TYPE_I_IEEE_FLOAT: + pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE; + break; + case UAC_FORMAT_TYPE_I_ALAW: + pcm_format = SNDRV_PCM_FORMAT_A_LAW; + break; + case UAC_FORMAT_TYPE_I_MULAW: + pcm_format = SNDRV_PCM_FORMAT_MU_LAW; + break; + default: + snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n", + chip->dev->devnum, fp->iface, fp->altsetting, format); + break; + } + return pcm_format; +} + + +/* + * parse the format descriptor and stores the possible sample rates + * on the audioformat table (audio class v1). + * + * @dev: usb device + * @fp: audioformat record + * @fmt: the format descriptor + * @offset: the start offset of descriptor pointing the rate type + * (7 for type I and II, 8 for type II) + */ +static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp, + unsigned char *fmt, int offset) +{ + int nr_rates = fmt[offset]; + + if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) { + snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n", + chip->dev->devnum, fp->iface, fp->altsetting); + return -1; + } + + if (nr_rates) { + /* + * build the rate table and bitmap flags + */ + int r, idx; + + fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL); + if (fp->rate_table == NULL) { + snd_printk(KERN_ERR "cannot malloc\n"); + return -1; + } + + fp->nr_rates = 0; + fp->rate_min = fp->rate_max = 0; + for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) { + unsigned int rate = combine_triple(&fmt[idx]); + if (!rate) + continue; + /* C-Media CM6501 mislabels its 96 kHz altsetting */ + if (rate == 48000 && nr_rates == 1 && + (chip->usb_id == USB_ID(0x0d8c, 0x0201) || + chip->usb_id == USB_ID(0x0d8c, 0x0102)) && + fp->altsetting == 5 && fp->maxpacksize == 392) + rate = 96000; + /* Creative VF0470 Live Cam reports 16 kHz instead of 8kHz */ + if (rate == 16000 && chip->usb_id == USB_ID(0x041e, 0x4068)) + rate = 8000; + + fp->rate_table[fp->nr_rates] = rate; + if (!fp->rate_min || rate < fp->rate_min) + fp->rate_min = rate; + if (!fp->rate_max || rate > fp->rate_max) + fp->rate_max = rate; + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + fp->nr_rates++; + } + if (!fp->nr_rates) { + hwc_debug("All rates were zero. Skipping format!\n"); + return -1; + } + } else { + /* continuous rates */ + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + fp->rate_min = combine_triple(&fmt[offset + 1]); + fp->rate_max = combine_triple(&fmt[offset + 4]); + } + return 0; +} + +/* + * parse the format descriptor and stores the possible sample rates + * on the audioformat table (audio class v2). + */ +static int parse_audio_format_rates_v2(struct snd_usb_audio *chip, + struct audioformat *fp, + struct usb_host_interface *iface) +{ + struct usb_device *dev = chip->dev; + unsigned char tmp[2], *data; + int i, nr_rates, data_size, ret = 0; + + /* get the number of sample rates first by only fetching 2 bytes */ + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 0x0100, chip->clock_id << 8, tmp, sizeof(tmp), 1000); + + if (ret < 0) { + snd_printk(KERN_ERR "unable to retrieve number of sample rates\n"); + goto err; + } + + nr_rates = (tmp[1] << 8) | tmp[0]; + data_size = 2 + 12 * nr_rates; + data = kzalloc(data_size, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err; + } + + /* now get the full information */ + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 0x0100, chip->clock_id << 8, data, data_size, 1000); + + if (ret < 0) { + snd_printk(KERN_ERR "unable to retrieve sample rate range\n"); + ret = -EINVAL; + goto err_free; + } + + fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL); + if (!fp->rate_table) { + ret = -ENOMEM; + goto err_free; + } + + fp->nr_rates = 0; + fp->rate_min = fp->rate_max = 0; + + for (i = 0; i < nr_rates; i++) { + int rate = combine_quad(&data[2 + 12 * i]); + + fp->rate_table[fp->nr_rates] = rate; + if (!fp->rate_min || rate < fp->rate_min) + fp->rate_min = rate; + if (!fp->rate_max || rate > fp->rate_max) + fp->rate_max = rate; + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + fp->nr_rates++; + } + +err_free: + kfree(data); +err: + return ret; +} + +/* + * parse the format type I and III descriptors + */ +static int parse_audio_format_i(struct snd_usb_audio *chip, + struct audioformat *fp, + int format, void *_fmt, + struct usb_host_interface *iface) +{ + struct usb_interface_descriptor *altsd = get_iface_desc(iface); + struct uac_format_type_i_discrete_descriptor *fmt = _fmt; + int protocol = altsd->bInterfaceProtocol; + int pcm_format, ret; + + if (fmt->bFormatType == UAC_FORMAT_TYPE_III) { + /* FIXME: the format type is really IECxxx + * but we give normal PCM format to get the existing + * apps working... + */ + switch (chip->usb_id) { + + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + if (chip->setup == 0x00 && + fp->altsetting == 6) + pcm_format = SNDRV_PCM_FORMAT_S16_BE; + else + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + break; + default: + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + } + } else { + pcm_format = parse_audio_format_i_type(chip, fp, format, fmt, protocol); + if (pcm_format < 0) + return -1; + } + + fp->format = pcm_format; + + /* gather possible sample rates */ + /* audio class v1 reports possible sample rates as part of the + * proprietary class specific descriptor. + * audio class v2 uses class specific EP0 range requests for that. + */ + switch (protocol) { + case UAC_VERSION_1: + fp->channels = fmt->bNrChannels; + ret = parse_audio_format_rates_v1(chip, fp, _fmt, 7); + break; + case UAC_VERSION_2: + /* fp->channels is already set in this case */ + ret = parse_audio_format_rates_v2(chip, fp, iface); + break; + } + + if (fp->channels < 1) { + snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n", + chip->dev->devnum, fp->iface, fp->altsetting, fp->channels); + return -1; + } + + return ret; +} + +/* + * parse the format type II descriptor + */ +static int parse_audio_format_ii(struct snd_usb_audio *chip, + struct audioformat *fp, + int format, void *_fmt, + struct usb_host_interface *iface) +{ + int brate, framesize, ret; + struct usb_interface_descriptor *altsd = get_iface_desc(iface); + int protocol = altsd->bInterfaceProtocol; + + switch (format) { + case UAC_FORMAT_TYPE_II_AC3: + /* FIXME: there is no AC3 format defined yet */ + // fp->format = SNDRV_PCM_FORMAT_AC3; + fp->format = SNDRV_PCM_FORMAT_U8; /* temporarily hack to receive byte streams */ + break; + case UAC_FORMAT_TYPE_II_MPEG: + fp->format = SNDRV_PCM_FORMAT_MPEG; + break; + default: + snd_printd(KERN_INFO "%d:%u:%d : unknown format tag %#x is detected. processed as MPEG.\n", + chip->dev->devnum, fp->iface, fp->altsetting, format); + fp->format = SNDRV_PCM_FORMAT_MPEG; + break; + } + + fp->channels = 1; + + switch (protocol) { + case UAC_VERSION_1: { + struct uac_format_type_ii_discrete_descriptor *fmt = _fmt; + brate = le16_to_cpu(fmt->wMaxBitRate); + framesize = le16_to_cpu(fmt->wSamplesPerFrame); + snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); + fp->frame_size = framesize; + ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */ + break; + } + case UAC_VERSION_2: { + struct uac_format_type_ii_ext_descriptor *fmt = _fmt; + brate = le16_to_cpu(fmt->wMaxBitRate); + framesize = le16_to_cpu(fmt->wSamplesPerFrame); + snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); + fp->frame_size = framesize; + ret = parse_audio_format_rates_v2(chip, fp, iface); + break; + } + } + + return ret; +} + +int snd_usb_parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp, + int format, unsigned char *fmt, int stream, + struct usb_host_interface *iface) +{ + int err; + + switch (fmt[3]) { + case UAC_FORMAT_TYPE_I: + case UAC_FORMAT_TYPE_III: + err = parse_audio_format_i(chip, fp, format, fmt, iface); + break; + case UAC_FORMAT_TYPE_II: + err = parse_audio_format_ii(chip, fp, format, fmt, iface); + break; + default: + snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n", + chip->dev->devnum, fp->iface, fp->altsetting, fmt[3]); + return -1; + } + fp->fmt_type = fmt[3]; + if (err < 0) + return err; +#if 1 + /* FIXME: temporary hack for extigy/audigy 2 nx/zs */ + /* extigy apparently supports sample rates other than 48k + * but not in ordinary way. so we enable only 48k atm. + */ + if (chip->usb_id == USB_ID(0x041e, 0x3000) || + chip->usb_id == USB_ID(0x041e, 0x3020) || + chip->usb_id == USB_ID(0x041e, 0x3061)) { + if (fmt[3] == UAC_FORMAT_TYPE_I && + fp->rates != SNDRV_PCM_RATE_48000 && + fp->rates != SNDRV_PCM_RATE_96000) + return -1; + } +#endif + return 0; +} + diff --git a/sound/usb/format.h b/sound/usb/format.h new file mode 100644 index 00000000000..8298c4e8ddf --- /dev/null +++ b/sound/usb/format.h @@ -0,0 +1,8 @@ +#ifndef __USBAUDIO_FORMAT_H +#define __USBAUDIO_FORMAT_H + +int snd_usb_parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp, + int format, unsigned char *fmt, int stream, + struct usb_host_interface *iface); + +#endif /* __USBAUDIO_FORMAT_H */ diff --git a/sound/usb/helper.c b/sound/usb/helper.c new file mode 100644 index 00000000000..ba7dba4d5cf --- /dev/null +++ b/sound/usb/helper.c @@ -0,0 +1,112 @@ +/* + * 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. + * + * 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 + * + */ + +#include +#include + +#include "usbaudio.h" +#include "helper.h" + +/* + * combine bytes and get an integer value + */ +unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size) +{ + switch (size) { + case 1: return *bytes; + case 2: return combine_word(bytes); + case 3: return combine_triple(bytes); + case 4: return combine_quad(bytes); + default: return 0; + } +} + +/* + * parse descriptor buffer and return the pointer starting the given + * descriptor type. + */ +void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype) +{ + u8 *p, *end, *next; + + p = descstart; + end = p + desclen; + for (; p < end;) { + if (p[0] < 2) + return NULL; + next = p + p[0]; + if (next > end) + return NULL; + if (p[1] == dtype && (!after || (void *)p > after)) { + return p; + } + p = next; + } + return NULL; +} + +/* + * find a class-specified interface descriptor with the given subtype. + */ +void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype) +{ + unsigned char *p = after; + + while ((p = snd_usb_find_desc(buffer, buflen, p, + USB_DT_CS_INTERFACE)) != NULL) { + if (p[0] >= 3 && p[2] == dsubtype) + return p; + } + return NULL; +} + +/* + * Wrapper for usb_control_msg(). + * Allocates a temp buffer to prevent dmaing from/to the stack. + */ +int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, + __u8 requesttype, __u16 value, __u16 index, void *data, + __u16 size, int timeout) +{ + int err; + void *buf = NULL; + + if (size > 0) { + buf = kmemdup(data, size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + err = usb_control_msg(dev, pipe, request, requesttype, + value, index, buf, size, timeout); + if (size > 0) { + memcpy(data, buf, size); + kfree(buf); + } + return err; +} + +unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, + struct usb_host_interface *alts) +{ + if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH && + get_endpoint(alts, 0)->bInterval >= 1 && + get_endpoint(alts, 0)->bInterval <= 4) + return get_endpoint(alts, 0)->bInterval - 1; + else + return 0; +} + diff --git a/sound/usb/helper.h b/sound/usb/helper.h new file mode 100644 index 00000000000..a6b0e51b3a9 --- /dev/null +++ b/sound/usb/helper.h @@ -0,0 +1,32 @@ +#ifndef __USBAUDIO_HELPER_H +#define __USBAUDIO_HELPER_H + +unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size); + +void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype); +void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype); + +int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size, int timeout); + +unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, + struct usb_host_interface *alts); + +/* + * retrieve usb_interface descriptor from the host interface + * (conditional for compatibility with the older API) + */ +#ifndef get_iface_desc +#define get_iface_desc(iface) (&(iface)->desc) +#define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc) +#define get_ep_desc(ep) (&(ep)->desc) +#define get_cfg_desc(cfg) (&(cfg)->desc) +#endif + +#ifndef snd_usb_get_speed +#define snd_usb_get_speed(dev) ((dev)->speed) +#endif + + +#endif /* __USBAUDIO_HELPER_H */ diff --git a/sound/usb/midi.c b/sound/usb/midi.c new file mode 100644 index 00000000000..c6ee4a18e51 --- /dev/null +++ b/sound/usb/midi.c @@ -0,0 +1,2069 @@ +/* + * usbmidi.c - ALSA USB MIDI driver + * + * Copyright (c) 2002-2009 Clemens Ladisch + * All rights reserved. + * + * Based on the OSS usb-midi driver by NAGANO Daisuke, + * NetBSD's umidi driver by Takuya SHIOZAKI, + * the "USB Device Class Definition for MIDI Devices" by Roland + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbaudio.h" +#include "midi.h" +#include "helper.h" + +/* + * define this to log all USB packets + */ +/* #define DUMP_PACKETS */ + +/* + * how long to wait after some USB errors, so that khubd can disconnect() us + * without too many spurious errors + */ +#define ERROR_DELAY_JIFFIES (HZ / 10) + +#define OUTPUT_URBS 7 +#define INPUT_URBS 7 + + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("USB Audio/MIDI helper module"); +MODULE_LICENSE("Dual BSD/GPL"); + + +struct usb_ms_header_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bcdMSC[2]; + __le16 wTotalLength; +} __attribute__ ((packed)); + +struct usb_ms_endpoint_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bNumEmbMIDIJack; + __u8 baAssocJackID[0]; +} __attribute__ ((packed)); + +struct snd_usb_midi_in_endpoint; +struct snd_usb_midi_out_endpoint; +struct snd_usb_midi_endpoint; + +struct usb_protocol_ops { + void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int); + void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb); + void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t); + void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint*); + void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint*); +}; + +struct snd_usb_midi { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *iface; + const struct snd_usb_audio_quirk *quirk; + struct snd_rawmidi *rmidi; + struct usb_protocol_ops* usb_protocol_ops; + struct list_head list; + struct timer_list error_timer; + spinlock_t disc_lock; + struct mutex mutex; + u32 usb_id; + int next_midi_device; + + struct snd_usb_midi_endpoint { + struct snd_usb_midi_out_endpoint *out; + struct snd_usb_midi_in_endpoint *in; + } endpoints[MIDI_MAX_ENDPOINTS]; + unsigned long input_triggered; + unsigned int opened; + unsigned char disconnected; + + struct snd_kcontrol *roland_load_ctl; +}; + +struct snd_usb_midi_out_endpoint { + struct snd_usb_midi* umidi; + struct out_urb_context { + struct urb *urb; + struct snd_usb_midi_out_endpoint *ep; + } urbs[OUTPUT_URBS]; + unsigned int active_urbs; + unsigned int drain_urbs; + int max_transfer; /* size of urb buffer */ + struct tasklet_struct tasklet; + unsigned int next_urb; + spinlock_t buffer_lock; + + struct usbmidi_out_port { + struct snd_usb_midi_out_endpoint* ep; + struct snd_rawmidi_substream *substream; + int active; + uint8_t cable; /* cable number << 4 */ + uint8_t state; +#define STATE_UNKNOWN 0 +#define STATE_1PARAM 1 +#define STATE_2PARAM_1 2 +#define STATE_2PARAM_2 3 +#define STATE_SYSEX_0 4 +#define STATE_SYSEX_1 5 +#define STATE_SYSEX_2 6 + uint8_t data[2]; + } ports[0x10]; + int current_port; + + wait_queue_head_t drain_wait; +}; + +struct snd_usb_midi_in_endpoint { + struct snd_usb_midi* umidi; + struct urb* urbs[INPUT_URBS]; + struct usbmidi_in_port { + struct snd_rawmidi_substream *substream; + u8 running_status_length; + } ports[0x10]; + u8 seen_f5; + u8 error_resubmit; + int current_port; +}; + +static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep); + +static const uint8_t snd_usbmidi_cin_length[] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 +}; + +/* + * Submits the URB, with error handling. + */ +static int snd_usbmidi_submit_urb(struct urb* urb, gfp_t flags) +{ + int err = usb_submit_urb(urb, flags); + if (err < 0 && err != -ENODEV) + snd_printk(KERN_ERR "usb_submit_urb: %d\n", err); + return err; +} + +/* + * Error handling for URB completion functions. + */ +static int snd_usbmidi_urb_error(int status) +{ + switch (status) { + /* manually unlinked, or device gone */ + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + case -ENODEV: + return -ENODEV; + /* errors that might occur during unplugging */ + case -EPROTO: + case -ETIME: + case -EILSEQ: + return -EIO; + default: + snd_printk(KERN_ERR "urb status %d\n", status); + return 0; /* continue */ + } +} + +/* + * Receives a chunk of MIDI data. + */ +static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint* ep, int portidx, + uint8_t* data, int length) +{ + struct usbmidi_in_port* port = &ep->ports[portidx]; + + if (!port->substream) { + snd_printd("unexpected port %d!\n", portidx); + return; + } + if (!test_bit(port->substream->number, &ep->umidi->input_triggered)) + return; + snd_rawmidi_receive(port->substream, data, length); +} + +#ifdef DUMP_PACKETS +static void dump_urb(const char *type, const u8 *data, int length) +{ + snd_printk(KERN_DEBUG "%s packet: [", type); + for (; length > 0; ++data, --length) + printk(" %02x", *data); + printk(" ]\n"); +} +#else +#define dump_urb(type, data, length) /* nothing */ +#endif + +/* + * Processes the data read from the device. + */ +static void snd_usbmidi_in_urb_complete(struct urb* urb) +{ + struct snd_usb_midi_in_endpoint* ep = urb->context; + + if (urb->status == 0) { + dump_urb("received", urb->transfer_buffer, urb->actual_length); + ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer, + urb->actual_length); + } else { + int err = snd_usbmidi_urb_error(urb->status); + if (err < 0) { + if (err != -ENODEV) { + ep->error_resubmit = 1; + mod_timer(&ep->umidi->error_timer, + jiffies + ERROR_DELAY_JIFFIES); + } + return; + } + } + + urb->dev = ep->umidi->dev; + snd_usbmidi_submit_urb(urb, GFP_ATOMIC); +} + +static void snd_usbmidi_out_urb_complete(struct urb* urb) +{ + struct out_urb_context *context = urb->context; + struct snd_usb_midi_out_endpoint* ep = context->ep; + unsigned int urb_index; + + spin_lock(&ep->buffer_lock); + urb_index = context - ep->urbs; + ep->active_urbs &= ~(1 << urb_index); + if (unlikely(ep->drain_urbs)) { + ep->drain_urbs &= ~(1 << urb_index); + wake_up(&ep->drain_wait); + } + spin_unlock(&ep->buffer_lock); + if (urb->status < 0) { + int err = snd_usbmidi_urb_error(urb->status); + if (err < 0) { + if (err != -ENODEV) + mod_timer(&ep->umidi->error_timer, + jiffies + ERROR_DELAY_JIFFIES); + return; + } + } + snd_usbmidi_do_output(ep); +} + +/* + * This is called when some data should be transferred to the device + * (from one or more substreams). + */ +static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep) +{ + unsigned int urb_index; + struct urb* urb; + unsigned long flags; + + spin_lock_irqsave(&ep->buffer_lock, flags); + if (ep->umidi->disconnected) { + spin_unlock_irqrestore(&ep->buffer_lock, flags); + return; + } + + urb_index = ep->next_urb; + for (;;) { + if (!(ep->active_urbs & (1 << urb_index))) { + urb = ep->urbs[urb_index].urb; + urb->transfer_buffer_length = 0; + ep->umidi->usb_protocol_ops->output(ep, urb); + if (urb->transfer_buffer_length == 0) + break; + + dump_urb("sending", urb->transfer_buffer, + urb->transfer_buffer_length); + urb->dev = ep->umidi->dev; + if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0) + break; + ep->active_urbs |= 1 << urb_index; + } + if (++urb_index >= OUTPUT_URBS) + urb_index = 0; + if (urb_index == ep->next_urb) + break; + } + ep->next_urb = urb_index; + spin_unlock_irqrestore(&ep->buffer_lock, flags); +} + +static void snd_usbmidi_out_tasklet(unsigned long data) +{ + struct snd_usb_midi_out_endpoint* ep = (struct snd_usb_midi_out_endpoint *) data; + + snd_usbmidi_do_output(ep); +} + +/* called after transfers had been interrupted due to some USB error */ +static void snd_usbmidi_error_timer(unsigned long data) +{ + struct snd_usb_midi *umidi = (struct snd_usb_midi *)data; + unsigned int i, j; + + spin_lock(&umidi->disc_lock); + if (umidi->disconnected) { + spin_unlock(&umidi->disc_lock); + return; + } + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in; + if (in && in->error_resubmit) { + in->error_resubmit = 0; + for (j = 0; j < INPUT_URBS; ++j) { + in->urbs[j]->dev = umidi->dev; + snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC); + } + } + if (umidi->endpoints[i].out) + snd_usbmidi_do_output(umidi->endpoints[i].out); + } + spin_unlock(&umidi->disc_lock); +} + +/* helper function to send static data that may not DMA-able */ +static int send_bulk_static_data(struct snd_usb_midi_out_endpoint* ep, + const void *data, int len) +{ + int err = 0; + void *buf = kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + dump_urb("sending", buf, len); + if (ep->urbs[0].urb) + err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe, + buf, len, NULL, 250); + kfree(buf); + return err; +} + +/* + * Standard USB MIDI protocol: see the spec. + * Midiman protocol: like the standard protocol, but the control byte is the + * fourth byte in each packet, and uses length instead of CIN. + */ + +static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i] != 0) { + int cable = buffer[i] >> 4; + int length = snd_usbmidi_cin_length[buffer[i] & 0x0f]; + snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length); + } +} + +static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i + 3] != 0) { + int port = buffer[i + 3] >> 4; + int length = buffer[i + 3] & 3; + snd_usbmidi_input_data(ep, port, &buffer[i], length); + } +} + +/* + * Buggy M-Audio device: running status on input results in a packet that has + * the data bytes but not the status byte and that is marked with CIN 4. + */ +static void snd_usbmidi_maudio_broken_running_status_input( + struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i] != 0) { + int cable = buffer[i] >> 4; + u8 cin = buffer[i] & 0x0f; + struct usbmidi_in_port *port = &ep->ports[cable]; + int length; + + length = snd_usbmidi_cin_length[cin]; + if (cin == 0xf && buffer[i + 1] >= 0xf8) + ; /* realtime msg: no running status change */ + else if (cin >= 0x8 && cin <= 0xe) + /* channel msg */ + port->running_status_length = length - 1; + else if (cin == 0x4 && + port->running_status_length != 0 && + buffer[i + 1] < 0x80) + /* CIN 4 that is not a SysEx */ + length = port->running_status_length; + else + /* + * All other msgs cannot begin running status. + * (A channel msg sent as two or three CIN 0xF + * packets could in theory, but this device + * doesn't use this format.) + */ + port->running_status_length = 0; + snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length); + } +} + +/* + * CME protocol: like the standard protocol, but SysEx commands are sent as a + * single USB packet preceded by a 0x0F byte. + */ +static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f) + snd_usbmidi_standard_input(ep, buffer, buffer_length); + else + snd_usbmidi_input_data(ep, buffer[0] >> 4, + &buffer[1], buffer_length - 1); +} + +/* + * Adds one USB MIDI packet to the output buffer. + */ +static void snd_usbmidi_output_standard_packet(struct urb* urb, uint8_t p0, + uint8_t p1, uint8_t p2, uint8_t p3) +{ + + uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length; + buf[0] = p0; + buf[1] = p1; + buf[2] = p2; + buf[3] = p3; + urb->transfer_buffer_length += 4; +} + +/* + * Adds one Midiman packet to the output buffer. + */ +static void snd_usbmidi_output_midiman_packet(struct urb* urb, uint8_t p0, + uint8_t p1, uint8_t p2, uint8_t p3) +{ + + uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length; + buf[0] = p1; + buf[1] = p2; + buf[2] = p3; + buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f]; + urb->transfer_buffer_length += 4; +} + +/* + * Converts MIDI commands to USB MIDI packets. + */ +static void snd_usbmidi_transmit_byte(struct usbmidi_out_port* port, + uint8_t b, struct urb* urb) +{ + uint8_t p0 = port->cable; + void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) = + port->ep->umidi->usb_protocol_ops->output_packet; + + if (b >= 0xf8) { + output_packet(urb, p0 | 0x0f, b, 0, 0); + } else if (b >= 0xf0) { + switch (b) { + case 0xf0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + port->state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + port->state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + port->state = STATE_UNKNOWN; + break; + case 0xf6: + output_packet(urb, p0 | 0x05, 0xf6, 0, 0); + port->state = STATE_UNKNOWN; + break; + case 0xf7: + switch (port->state) { + case STATE_SYSEX_0: + output_packet(urb, p0 | 0x05, 0xf7, 0, 0); + break; + case STATE_SYSEX_1: + output_packet(urb, p0 | 0x06, port->data[0], 0xf7, 0); + break; + case STATE_SYSEX_2: + output_packet(urb, p0 | 0x07, port->data[0], port->data[1], 0xf7); + break; + } + port->state = STATE_UNKNOWN; + break; + } + } else if (b >= 0x80) { + port->data[0] = b; + if (b >= 0xc0 && b <= 0xdf) + port->state = STATE_1PARAM; + else + port->state = STATE_2PARAM_1; + } else { /* b < 0x80 */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + } else { + p0 |= 0x02; + port->state = STATE_UNKNOWN; + } + output_packet(urb, p0, port->data[0], b, 0); + break; + case STATE_2PARAM_1: + port->data[1] = b; + port->state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + port->state = STATE_2PARAM_1; + } else { + p0 |= 0x03; + port->state = STATE_UNKNOWN; + } + output_packet(urb, p0, port->data[0], port->data[1], b); + break; + case STATE_SYSEX_0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + port->state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + output_packet(urb, p0 | 0x04, port->data[0], port->data[1], b); + port->state = STATE_SYSEX_0; + break; + } + } +} + +static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) +{ + int p; + + /* FIXME: lower-numbered ports can starve higher-numbered ports */ + for (p = 0; p < 0x10; ++p) { + struct usbmidi_out_port* port = &ep->ports[p]; + if (!port->active) + continue; + while (urb->transfer_buffer_length + 3 < ep->max_transfer) { + uint8_t b; + if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { + port->active = 0; + break; + } + snd_usbmidi_transmit_byte(port, b, urb); + } + } +} + +static struct usb_protocol_ops snd_usbmidi_standard_ops = { + .input = snd_usbmidi_standard_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +static struct usb_protocol_ops snd_usbmidi_midiman_ops = { + .input = snd_usbmidi_midiman_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_midiman_packet, +}; + +static struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = { + .input = snd_usbmidi_maudio_broken_running_status_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +static struct usb_protocol_ops snd_usbmidi_cme_ops = { + .input = snd_usbmidi_cme_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +/* + * Novation USB MIDI protocol: number of data bytes is in the first byte + * (when receiving) (+1!) or in the second byte (when sending); data begins + * at the third byte. + */ + +static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1) + return; + snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1); +} + +static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) +{ + uint8_t* transfer_buffer; + int count; + + if (!ep->ports[0].active) + return; + transfer_buffer = urb->transfer_buffer; + count = snd_rawmidi_transmit(ep->ports[0].substream, + &transfer_buffer[2], + ep->max_transfer - 2); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + transfer_buffer[0] = 0; + transfer_buffer[1] = count; + urb->transfer_buffer_length = 2 + count; +} + +static struct usb_protocol_ops snd_usbmidi_novation_ops = { + .input = snd_usbmidi_novation_input, + .output = snd_usbmidi_novation_output, +}; + +/* + * "raw" protocol: used by the MOTU FastLane. + */ + +static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + snd_usbmidi_input_data(ep, 0, buffer, buffer_length); +} + +static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) +{ + int count; + + if (!ep->ports[0].active) + return; + count = snd_rawmidi_transmit(ep->ports[0].substream, + urb->transfer_buffer, + ep->max_transfer); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + urb->transfer_buffer_length = count; +} + +static struct usb_protocol_ops snd_usbmidi_raw_ops = { + .input = snd_usbmidi_raw_input, + .output = snd_usbmidi_raw_output, +}; + +static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length != 9) + return; + buffer_length = 8; + while (buffer_length && buffer[buffer_length - 1] == 0xFD) + buffer_length--; + if (buffer_length) + snd_usbmidi_input_data(ep, 0, buffer, buffer_length); +} + +static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + int count; + + if (!ep->ports[0].active) + return; + count = snd_usb_get_speed(ep->umidi->dev) == USB_SPEED_HIGH ? 1 : 2; + count = snd_rawmidi_transmit(ep->ports[0].substream, + urb->transfer_buffer, + count); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + + memset(urb->transfer_buffer + count, 0xFD, 9 - count); + urb->transfer_buffer_length = count; +} + +static struct usb_protocol_ops snd_usbmidi_122l_ops = { + .input = snd_usbmidi_us122l_input, + .output = snd_usbmidi_us122l_output, +}; + +/* + * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching. + */ + +static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint* ep) +{ + static const u8 init_data[] = { + /* initialization magic: "get version" */ + 0xf0, + 0x00, 0x20, 0x31, /* Emagic */ + 0x64, /* Unitor8 */ + 0x0b, /* version number request */ + 0x00, /* command version */ + 0x00, /* EEPROM, box 0 */ + 0xf7 + }; + send_bulk_static_data(ep, init_data, sizeof(init_data)); + /* while we're at it, pour on more magic */ + send_bulk_static_data(ep, init_data, sizeof(init_data)); +} + +static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint* ep) +{ + static const u8 finish_data[] = { + /* switch to patch mode with last preset */ + 0xf0, + 0x00, 0x20, 0x31, /* Emagic */ + 0x64, /* Unitor8 */ + 0x10, /* patch switch command */ + 0x00, /* command version */ + 0x7f, /* to all boxes */ + 0x40, /* last preset in EEPROM */ + 0xf7 + }; + send_bulk_static_data(ep, finish_data, sizeof(finish_data)); +} + +static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint* ep, + uint8_t* buffer, int buffer_length) +{ + int i; + + /* FF indicates end of valid data */ + for (i = 0; i < buffer_length; ++i) + if (buffer[i] == 0xff) { + buffer_length = i; + break; + } + + /* handle F5 at end of last buffer */ + if (ep->seen_f5) + goto switch_port; + + while (buffer_length > 0) { + /* determine size of data until next F5 */ + for (i = 0; i < buffer_length; ++i) + if (buffer[i] == 0xf5) + break; + snd_usbmidi_input_data(ep, ep->current_port, buffer, i); + buffer += i; + buffer_length -= i; + + if (buffer_length <= 0) + break; + /* assert(buffer[0] == 0xf5); */ + ep->seen_f5 = 1; + ++buffer; + --buffer_length; + + switch_port: + if (buffer_length <= 0) + break; + if (buffer[0] < 0x80) { + ep->current_port = (buffer[0] - 1) & 15; + ++buffer; + --buffer_length; + } + ep->seen_f5 = 0; + } +} + +static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) +{ + int port0 = ep->current_port; + uint8_t* buf = urb->transfer_buffer; + int buf_free = ep->max_transfer; + int length, i; + + for (i = 0; i < 0x10; ++i) { + /* round-robin, starting at the last current port */ + int portnum = (port0 + i) & 15; + struct usbmidi_out_port* port = &ep->ports[portnum]; + + if (!port->active) + continue; + if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) { + port->active = 0; + continue; + } + + if (portnum != ep->current_port) { + if (buf_free < 2) + break; + ep->current_port = portnum; + buf[0] = 0xf5; + buf[1] = (portnum + 1) & 15; + buf += 2; + buf_free -= 2; + } + + if (buf_free < 1) + break; + length = snd_rawmidi_transmit(port->substream, buf, buf_free); + if (length > 0) { + buf += length; + buf_free -= length; + if (buf_free < 1) + break; + } + } + if (buf_free < ep->max_transfer && buf_free > 0) { + *buf = 0xff; + --buf_free; + } + urb->transfer_buffer_length = ep->max_transfer - buf_free; +} + +static struct usb_protocol_ops snd_usbmidi_emagic_ops = { + .input = snd_usbmidi_emagic_input, + .output = snd_usbmidi_emagic_output, + .init_out_endpoint = snd_usbmidi_emagic_init_out, + .finish_out_endpoint = snd_usbmidi_emagic_finish_out, +}; + + +static void update_roland_altsetting(struct snd_usb_midi* umidi) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + int is_light_load; + + intf = umidi->iface; + is_light_load = intf->cur_altsetting != intf->altsetting; + if (umidi->roland_load_ctl->private_value == is_light_load) + return; + hostif = &intf->altsetting[umidi->roland_load_ctl->private_value]; + intfd = get_iface_desc(hostif); + snd_usbmidi_input_stop(&umidi->list); + usb_set_interface(umidi->dev, intfd->bInterfaceNumber, + intfd->bAlternateSetting); + snd_usbmidi_input_start(&umidi->list); +} + +static void substream_open(struct snd_rawmidi_substream *substream, int open) +{ + struct snd_usb_midi* umidi = substream->rmidi->private_data; + struct snd_kcontrol *ctl; + + mutex_lock(&umidi->mutex); + if (open) { + if (umidi->opened++ == 0 && umidi->roland_load_ctl) { + ctl = umidi->roland_load_ctl; + ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(umidi->card, + SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + update_roland_altsetting(umidi); + } + } else { + if (--umidi->opened == 0 && umidi->roland_load_ctl) { + ctl = umidi->roland_load_ctl; + ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(umidi->card, + SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + } + } + mutex_unlock(&umidi->mutex); +} + +static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi* umidi = substream->rmidi->private_data; + struct usbmidi_out_port* port = NULL; + int i, j; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) + if (umidi->endpoints[i].out) + for (j = 0; j < 0x10; ++j) + if (umidi->endpoints[i].out->ports[j].substream == substream) { + port = &umidi->endpoints[i].out->ports[j]; + break; + } + if (!port) { + snd_BUG(); + return -ENXIO; + } + substream->runtime->private_data = port; + port->state = STATE_UNKNOWN; + substream_open(substream, 1); + return 0; +} + +static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) +{ + substream_open(substream, 0); + return 0; +} + +static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct usbmidi_out_port* port = (struct usbmidi_out_port*)substream->runtime->private_data; + + port->active = up; + if (up) { + if (port->ep->umidi->disconnected) { + /* gobble up remaining bytes to prevent wait in + * snd_rawmidi_drain_output */ + while (!snd_rawmidi_transmit_empty(substream)) + snd_rawmidi_transmit_ack(substream, 1); + return; + } + tasklet_schedule(&port->ep->tasklet); + } +} + +static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct usbmidi_out_port* port = substream->runtime->private_data; + struct snd_usb_midi_out_endpoint *ep = port->ep; + unsigned int drain_urbs; + DEFINE_WAIT(wait); + long timeout = msecs_to_jiffies(50); + + /* + * The substream buffer is empty, but some data might still be in the + * currently active URBs, so we have to wait for those to complete. + */ + spin_lock_irq(&ep->buffer_lock); + drain_urbs = ep->active_urbs; + if (drain_urbs) { + ep->drain_urbs |= drain_urbs; + do { + prepare_to_wait(&ep->drain_wait, &wait, + TASK_UNINTERRUPTIBLE); + spin_unlock_irq(&ep->buffer_lock); + timeout = schedule_timeout(timeout); + spin_lock_irq(&ep->buffer_lock); + drain_urbs &= ep->drain_urbs; + } while (drain_urbs && timeout); + finish_wait(&ep->drain_wait, &wait); + } + spin_unlock_irq(&ep->buffer_lock); +} + +static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream) +{ + substream_open(substream, 1); + return 0; +} + +static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream) +{ + substream_open(substream, 0); + return 0; +} + +static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_usb_midi* umidi = substream->rmidi->private_data; + + if (up) + set_bit(substream->number, &umidi->input_triggered); + else + clear_bit(substream->number, &umidi->input_triggered); +} + +static struct snd_rawmidi_ops snd_usbmidi_output_ops = { + .open = snd_usbmidi_output_open, + .close = snd_usbmidi_output_close, + .trigger = snd_usbmidi_output_trigger, + .drain = snd_usbmidi_output_drain, +}; + +static struct snd_rawmidi_ops snd_usbmidi_input_ops = { + .open = snd_usbmidi_input_open, + .close = snd_usbmidi_input_close, + .trigger = snd_usbmidi_input_trigger +}; + +static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb, + unsigned int buffer_length) +{ + usb_buffer_free(umidi->dev, buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); +} + +/* + * Frees an input endpoint. + * May be called when ep hasn't been initialized completely. + */ +static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint* ep) +{ + unsigned int i; + + for (i = 0; i < INPUT_URBS; ++i) + if (ep->urbs[i]) + free_urb_and_buffer(ep->umidi, ep->urbs[i], + ep->urbs[i]->transfer_buffer_length); + kfree(ep); +} + +/* + * Creates an input endpoint. + */ +static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* ep_info, + struct snd_usb_midi_endpoint* rep) +{ + struct snd_usb_midi_in_endpoint* ep; + void* buffer; + unsigned int pipe; + int length; + unsigned int i; + + rep->in = NULL; + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + ep->umidi = umidi; + + for (i = 0; i < INPUT_URBS; ++i) { + ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ep->urbs[i]) { + snd_usbmidi_in_endpoint_delete(ep); + return -ENOMEM; + } + } + if (ep_info->in_interval) + pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep); + else + pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep); + length = usb_maxpacket(umidi->dev, pipe, 0); + for (i = 0; i < INPUT_URBS; ++i) { + buffer = usb_buffer_alloc(umidi->dev, length, GFP_KERNEL, + &ep->urbs[i]->transfer_dma); + if (!buffer) { + snd_usbmidi_in_endpoint_delete(ep); + return -ENOMEM; + } + if (ep_info->in_interval) + usb_fill_int_urb(ep->urbs[i], umidi->dev, + pipe, buffer, length, + snd_usbmidi_in_urb_complete, + ep, ep_info->in_interval); + else + usb_fill_bulk_urb(ep->urbs[i], umidi->dev, + pipe, buffer, length, + snd_usbmidi_in_urb_complete, ep); + ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + } + + rep->in = ep; + return 0; +} + +/* + * Frees an output endpoint. + * May be called when ep hasn't been initialized completely. + */ +static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint* ep) +{ + unsigned int i; + + for (i = 0; i < OUTPUT_URBS; ++i) + if (ep->urbs[i].urb) + free_urb_and_buffer(ep->umidi, ep->urbs[i].urb, + ep->max_transfer); + kfree(ep); +} + +/* + * Creates an output endpoint, and initializes output ports. + */ +static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* ep_info, + struct snd_usb_midi_endpoint* rep) +{ + struct snd_usb_midi_out_endpoint* ep; + unsigned int i; + unsigned int pipe; + void* buffer; + + rep->out = NULL; + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + ep->umidi = umidi; + + for (i = 0; i < OUTPUT_URBS; ++i) { + ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ep->urbs[i].urb) { + snd_usbmidi_out_endpoint_delete(ep); + return -ENOMEM; + } + ep->urbs[i].ep = ep; + } + if (ep_info->out_interval) + pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep); + else + pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep); + switch (umidi->usb_id) { + default: + ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1); + break; + /* + * Various chips declare a packet size larger than 4 bytes, but + * do not actually work with larger packets: + */ + case USB_ID(0x0a92, 0x1020): /* ESI M4U */ + case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */ + case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */ + case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */ + case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */ + ep->max_transfer = 4; + break; + } + for (i = 0; i < OUTPUT_URBS; ++i) { + buffer = usb_buffer_alloc(umidi->dev, + ep->max_transfer, GFP_KERNEL, + &ep->urbs[i].urb->transfer_dma); + if (!buffer) { + snd_usbmidi_out_endpoint_delete(ep); + return -ENOMEM; + } + if (ep_info->out_interval) + usb_fill_int_urb(ep->urbs[i].urb, umidi->dev, + pipe, buffer, ep->max_transfer, + snd_usbmidi_out_urb_complete, + &ep->urbs[i], ep_info->out_interval); + else + usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev, + pipe, buffer, ep->max_transfer, + snd_usbmidi_out_urb_complete, + &ep->urbs[i]); + ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + } + + spin_lock_init(&ep->buffer_lock); + tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep); + init_waitqueue_head(&ep->drain_wait); + + for (i = 0; i < 0x10; ++i) + if (ep_info->out_cables & (1 << i)) { + ep->ports[i].ep = ep; + ep->ports[i].cable = i << 4; + } + + if (umidi->usb_protocol_ops->init_out_endpoint) + umidi->usb_protocol_ops->init_out_endpoint(ep); + + rep->out = ep; + return 0; +} + +/* + * Frees everything. + */ +static void snd_usbmidi_free(struct snd_usb_midi* umidi) +{ + int i; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; + if (ep->out) + snd_usbmidi_out_endpoint_delete(ep->out); + if (ep->in) + snd_usbmidi_in_endpoint_delete(ep->in); + } + mutex_destroy(&umidi->mutex); + kfree(umidi); +} + +/* + * Unlinks all URBs (must be done before the usb_device is deleted). + */ +void snd_usbmidi_disconnect(struct list_head* p) +{ + struct snd_usb_midi* umidi; + unsigned int i, j; + + umidi = list_entry(p, struct snd_usb_midi, list); + /* + * an URB's completion handler may start the timer and + * a timer may submit an URB. To reliably break the cycle + * a flag under lock must be used + */ + spin_lock_irq(&umidi->disc_lock); + umidi->disconnected = 1; + spin_unlock_irq(&umidi->disc_lock); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; + if (ep->out) + tasklet_kill(&ep->out->tasklet); + if (ep->out) { + for (j = 0; j < OUTPUT_URBS; ++j) + usb_kill_urb(ep->out->urbs[j].urb); + if (umidi->usb_protocol_ops->finish_out_endpoint) + umidi->usb_protocol_ops->finish_out_endpoint(ep->out); + } + if (ep->in) + for (j = 0; j < INPUT_URBS; ++j) + usb_kill_urb(ep->in->urbs[j]); + /* free endpoints here; later call can result in Oops */ + if (ep->out) { + snd_usbmidi_out_endpoint_delete(ep->out); + ep->out = NULL; + } + if (ep->in) { + snd_usbmidi_in_endpoint_delete(ep->in); + ep->in = NULL; + } + } + del_timer_sync(&umidi->error_timer); +} + +static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_usb_midi* umidi = rmidi->private_data; + snd_usbmidi_free(umidi); +} + +static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi* umidi, + int stream, int number) +{ + struct list_head* list; + + list_for_each(list, &umidi->rmidi->streams[stream].substreams) { + struct snd_rawmidi_substream *substream = list_entry(list, struct snd_rawmidi_substream, list); + if (substream->number == number) + return substream; + } + return NULL; +} + +/* + * This list specifies names for ports that do not fit into the standard + * "(product) MIDI (n)" schema because they aren't external MIDI ports, + * such as internal control or synthesizer ports. + */ +static struct port_info { + u32 id; + short int port; + short int voices; + const char *name; + unsigned int seq_flags; +} snd_usbmidi_port_info[] = { +#define PORT_INFO(vendor, product, num, name_, voices_, flags) \ + { .id = USB_ID(vendor, product), \ + .port = num, .voices = voices_, \ + .name = name_, .seq_flags = flags } +#define EXTERNAL_PORT(vendor, product, num, name) \ + PORT_INFO(vendor, product, num, name, 0, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_PORT) +#define CONTROL_PORT(vendor, product, num, name) \ + PORT_INFO(vendor, product, num, name, 0, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE) +#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \ + PORT_INFO(vendor, product, num, name, voices, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ + SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) +#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \ + PORT_INFO(vendor, product, num, name, voices, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ + SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ + SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) + /* Roland UA-100 */ + CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"), + /* Roland SC-8850 */ + SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128), + EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"), + /* Roland U-8 */ + EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"), + /* Roland SC-8820 */ + SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"), + /* Roland SK-500 */ + SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"), + /* Roland SC-D70 */ + SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"), + /* Edirol UM-880 */ + CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"), + /* Edirol SD-90 */ + ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128), + ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128), + EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"), + /* Edirol UM-550 */ + CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"), + /* Edirol SD-20 */ + ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64), + ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"), + /* Edirol SD-80 */ + ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128), + ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128), + EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"), + /* Edirol UA-700 */ + EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"), + /* Roland VariOS */ + EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"), + EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"), + /* Edirol PCR */ + EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"), + /* BOSS GS-10 */ + EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"), + /* Edirol UA-1000 */ + EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"), + /* Edirol UR-80 */ + EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"), + /* Edirol PCR-A */ + EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"), + /* Edirol UM-3EX */ + CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"), + /* M-Audio MidiSport 8x8 */ + CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"), + CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"), + /* MOTU Fastlane */ + EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"), + EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"), + /* Emagic Unitor8/AMT8/MT4 */ + EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"), + EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"), + EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"), + /* Access Music Virus TI */ + EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"), + PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER), +}; + +static struct port_info *find_port_info(struct snd_usb_midi* umidi, int number) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) { + if (snd_usbmidi_port_info[i].id == umidi->usb_id && + snd_usbmidi_port_info[i].port == number) + return &snd_usbmidi_port_info[i]; + } + return NULL; +} + +static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number, + struct snd_seq_port_info *seq_port_info) +{ + struct snd_usb_midi *umidi = rmidi->private_data; + struct port_info *port_info; + + /* TODO: read port flags from descriptors */ + port_info = find_port_info(umidi, number); + if (port_info) { + seq_port_info->type = port_info->seq_flags; + seq_port_info->midi_voices = port_info->voices; + } +} + +static void snd_usbmidi_init_substream(struct snd_usb_midi* umidi, + int stream, int number, + struct snd_rawmidi_substream ** rsubstream) +{ + struct port_info *port_info; + const char *name_format; + + struct snd_rawmidi_substream *substream = snd_usbmidi_find_substream(umidi, stream, number); + if (!substream) { + snd_printd(KERN_ERR "substream %d:%d not found\n", stream, number); + return; + } + + /* TODO: read port name from jack descriptor */ + port_info = find_port_info(umidi, number); + name_format = port_info ? port_info->name : "%s MIDI %d"; + snprintf(substream->name, sizeof(substream->name), + name_format, umidi->card->shortname, number + 1); + + *rsubstream = substream; +} + +/* + * Creates the endpoints and their ports. + */ +static int snd_usbmidi_create_endpoints(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoints) +{ + int i, j, err; + int out_ports = 0, in_ports = 0; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + if (endpoints[i].out_cables) { + err = snd_usbmidi_out_endpoint_create(umidi, &endpoints[i], + &umidi->endpoints[i]); + if (err < 0) + return err; + } + if (endpoints[i].in_cables) { + err = snd_usbmidi_in_endpoint_create(umidi, &endpoints[i], + &umidi->endpoints[i]); + if (err < 0) + return err; + } + + for (j = 0; j < 0x10; ++j) { + if (endpoints[i].out_cables & (1 << j)) { + snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, out_ports, + &umidi->endpoints[i].out->ports[j].substream); + ++out_ports; + } + if (endpoints[i].in_cables & (1 << j)) { + snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, in_ports, + &umidi->endpoints[i].in->ports[j].substream); + ++in_ports; + } + } + } + snd_printdd(KERN_INFO "created %d output and %d input ports\n", + out_ports, in_ports); + return 0; +} + +/* + * Returns MIDIStreaming device capabilities. + */ +static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoints) +{ + struct usb_interface* intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor* intfd; + struct usb_ms_header_descriptor* ms_header; + struct usb_host_endpoint *hostep; + struct usb_endpoint_descriptor* ep; + struct usb_ms_endpoint_descriptor* ms_ep; + int i, epidx; + + intf = umidi->iface; + if (!intf) + return -ENXIO; + hostif = &intf->altsetting[0]; + intfd = get_iface_desc(hostif); + ms_header = (struct usb_ms_header_descriptor*)hostif->extra; + if (hostif->extralen >= 7 && + ms_header->bLength >= 7 && + ms_header->bDescriptorType == USB_DT_CS_INTERFACE && + ms_header->bDescriptorSubtype == UAC_HEADER) + snd_printdd(KERN_INFO "MIDIStreaming version %02x.%02x\n", + ms_header->bcdMSC[1], ms_header->bcdMSC[0]); + else + snd_printk(KERN_WARNING "MIDIStreaming interface descriptor not found\n"); + + epidx = 0; + for (i = 0; i < intfd->bNumEndpoints; ++i) { + hostep = &hostif->endpoint[i]; + ep = get_ep_desc(hostep); + if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep)) + continue; + ms_ep = (struct usb_ms_endpoint_descriptor*)hostep->extra; + if (hostep->extralen < 4 || + ms_ep->bLength < 4 || + ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT || + ms_ep->bDescriptorSubtype != UAC_MS_GENERAL) + continue; + if (usb_endpoint_dir_out(ep)) { + if (endpoints[epidx].out_ep) { + if (++epidx >= MIDI_MAX_ENDPOINTS) { + snd_printk(KERN_WARNING "too many endpoints\n"); + break; + } + } + endpoints[epidx].out_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) + endpoints[epidx].out_interval = ep->bInterval; + else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) + /* + * Low speed bulk transfers don't exist, so + * force interrupt transfers for devices like + * ESI MIDI Mate that try to use them anyway. + */ + endpoints[epidx].out_interval = 1; + endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; + snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", + ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); + } else { + if (endpoints[epidx].in_ep) { + if (++epidx >= MIDI_MAX_ENDPOINTS) { + snd_printk(KERN_WARNING "too many endpoints\n"); + break; + } + } + endpoints[epidx].in_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) + endpoints[epidx].in_interval = ep->bInterval; + else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) + endpoints[epidx].in_interval = 1; + endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; + snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", + ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); + } + } + return 0; +} + +static int roland_load_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[] = { "High Load", "Light Load" }; + + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = 2; + if (info->value.enumerated.item > 1) + info->value.enumerated.item = 1; + strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); + return 0; +} + +static int roland_load_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + value->value.enumerated.item[0] = kcontrol->private_value; + return 0; +} + +static int roland_load_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_usb_midi* umidi = kcontrol->private_data; + int changed; + + if (value->value.enumerated.item[0] > 1) + return -EINVAL; + mutex_lock(&umidi->mutex); + changed = value->value.enumerated.item[0] != kcontrol->private_value; + if (changed) + kcontrol->private_value = value->value.enumerated.item[0]; + mutex_unlock(&umidi->mutex); + return changed; +} + +static struct snd_kcontrol_new roland_load_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIDI Input Mode", + .info = roland_load_info, + .get = roland_load_get, + .put = roland_load_put, + .private_value = 1, +}; + +/* + * On Roland devices, use the second alternate setting to be able to use + * the interrupt input endpoint. + */ +static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi) +{ + struct usb_interface* intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor* intfd; + + intf = umidi->iface; + if (!intf || intf->num_altsetting != 2) + return; + + hostif = &intf->altsetting[1]; + intfd = get_iface_desc(hostif); + if (intfd->bNumEndpoints != 2 || + (get_endpoint(hostif, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK || + (get_endpoint(hostif, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) + return; + + snd_printdd(KERN_INFO "switching to altsetting %d with int ep\n", + intfd->bAlternateSetting); + usb_set_interface(umidi->dev, intfd->bInterfaceNumber, + intfd->bAlternateSetting); + + umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi); + if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0) + umidi->roland_load_ctl = NULL; +} + +/* + * Try to find any usable endpoints in the interface. + */ +static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoint, + int max_endpoints) +{ + struct usb_interface* intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor* intfd; + struct usb_endpoint_descriptor* epd; + int i, out_eps = 0, in_eps = 0; + + if (USB_ID_VENDOR(umidi->usb_id) == 0x0582) + snd_usbmidi_switch_roland_altsetting(umidi); + + if (endpoint[0].out_ep || endpoint[0].in_ep) + return 0; + + intf = umidi->iface; + if (!intf || intf->num_altsetting < 1) + return -ENOENT; + hostif = intf->cur_altsetting; + intfd = get_iface_desc(hostif); + + for (i = 0; i < intfd->bNumEndpoints; ++i) { + epd = get_endpoint(hostif, i); + if (!usb_endpoint_xfer_bulk(epd) && + !usb_endpoint_xfer_int(epd)) + continue; + if (out_eps < max_endpoints && + usb_endpoint_dir_out(epd)) { + endpoint[out_eps].out_ep = usb_endpoint_num(epd); + if (usb_endpoint_xfer_int(epd)) + endpoint[out_eps].out_interval = epd->bInterval; + ++out_eps; + } + if (in_eps < max_endpoints && + usb_endpoint_dir_in(epd)) { + endpoint[in_eps].in_ep = usb_endpoint_num(epd); + if (usb_endpoint_xfer_int(epd)) + endpoint[in_eps].in_interval = epd->bInterval; + ++in_eps; + } + } + return (out_eps || in_eps) ? 0 : -ENOENT; +} + +/* + * Detects the endpoints for one-port-per-endpoint protocols. + */ +static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoints) +{ + int err, i; + + err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + if (endpoints[i].out_ep) + endpoints[i].out_cables = 0x0001; + if (endpoints[i].in_ep) + endpoints[i].in_cables = 0x0001; + } + return err; +} + +/* + * Detects the endpoints and ports of Yamaha devices. + */ +static int snd_usbmidi_detect_yamaha(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoint) +{ + struct usb_interface* intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor* intfd; + uint8_t* cs_desc; + + intf = umidi->iface; + if (!intf) + return -ENOENT; + hostif = intf->altsetting; + intfd = get_iface_desc(hostif); + if (intfd->bNumEndpoints < 1) + return -ENOENT; + + /* + * For each port there is one MIDI_IN/OUT_JACK descriptor, not + * necessarily with any useful contents. So simply count 'em. + */ + for (cs_desc = hostif->extra; + cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2; + cs_desc += cs_desc[0]) { + if (cs_desc[1] == USB_DT_CS_INTERFACE) { + if (cs_desc[2] == UAC_MIDI_IN_JACK) + endpoint->in_cables = (endpoint->in_cables << 1) | 1; + else if (cs_desc[2] == UAC_MIDI_OUT_JACK) + endpoint->out_cables = (endpoint->out_cables << 1) | 1; + } + } + if (!endpoint->in_cables && !endpoint->out_cables) + return -ENOENT; + + return snd_usbmidi_detect_endpoints(umidi, endpoint, 1); +} + +/* + * Creates the endpoints and their ports for Midiman devices. + */ +static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi* umidi, + struct snd_usb_midi_endpoint_info* endpoint) +{ + struct snd_usb_midi_endpoint_info ep_info; + struct usb_interface* intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor* intfd; + struct usb_endpoint_descriptor* epd; + int cable, err; + + intf = umidi->iface; + if (!intf) + return -ENOENT; + hostif = intf->altsetting; + intfd = get_iface_desc(hostif); + /* + * The various MidiSport devices have more or less random endpoint + * numbers, so we have to identify the endpoints by their index in + * the descriptor array, like the driver for that other OS does. + * + * There is one interrupt input endpoint for all input ports, one + * bulk output endpoint for even-numbered ports, and one for odd- + * numbered ports. Both bulk output endpoints have corresponding + * input bulk endpoints (at indices 1 and 3) which aren't used. + */ + if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) { + snd_printdd(KERN_ERR "not enough endpoints\n"); + return -ENOENT; + } + + epd = get_endpoint(hostif, 0); + if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) { + snd_printdd(KERN_ERR "endpoint[0] isn't interrupt\n"); + return -ENXIO; + } + epd = get_endpoint(hostif, 2); + if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) { + snd_printdd(KERN_ERR "endpoint[2] isn't bulk output\n"); + return -ENXIO; + } + if (endpoint->out_cables > 0x0001) { + epd = get_endpoint(hostif, 4); + if (!usb_endpoint_dir_out(epd) || + !usb_endpoint_xfer_bulk(epd)) { + snd_printdd(KERN_ERR "endpoint[4] isn't bulk output\n"); + return -ENXIO; + } + } + + ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + ep_info.out_interval = 0; + ep_info.out_cables = endpoint->out_cables & 0x5555; + err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]); + if (err < 0) + return err; + + ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + ep_info.in_interval = get_endpoint(hostif, 0)->bInterval; + ep_info.in_cables = endpoint->in_cables; + err = snd_usbmidi_in_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]); + if (err < 0) + return err; + + if (endpoint->out_cables > 0x0001) { + ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + ep_info.out_cables = endpoint->out_cables & 0xaaaa; + err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[1]); + if (err < 0) + return err; + } + + for (cable = 0; cable < 0x10; ++cable) { + if (endpoint->out_cables & (1 << cable)) + snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, cable, + &umidi->endpoints[cable & 1].out->ports[cable].substream); + if (endpoint->in_cables & (1 << cable)) + snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, cable, + &umidi->endpoints[0].in->ports[cable].substream); + } + return 0; +} + +static struct snd_rawmidi_global_ops snd_usbmidi_ops = { + .get_port_info = snd_usbmidi_get_port_info, +}; + +static int snd_usbmidi_create_rawmidi(struct snd_usb_midi* umidi, + int out_ports, int in_ports) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(umidi->card, "USB MIDI", + umidi->next_midi_device++, + out_ports, in_ports, &rmidi); + if (err < 0) + return err; + strcpy(rmidi->name, umidi->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->ops = &snd_usbmidi_ops; + rmidi->private_data = umidi; + rmidi->private_free = snd_usbmidi_rawmidi_free; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_usbmidi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usbmidi_input_ops); + + umidi->rmidi = rmidi; + return 0; +} + +/* + * Temporarily stop input. + */ +void snd_usbmidi_input_stop(struct list_head* p) +{ + struct snd_usb_midi* umidi; + unsigned int i, j; + + umidi = list_entry(p, struct snd_usb_midi, list); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; + if (ep->in) + for (j = 0; j < INPUT_URBS; ++j) + usb_kill_urb(ep->in->urbs[j]); + } +} + +static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint* ep) +{ + unsigned int i; + + if (!ep) + return; + for (i = 0; i < INPUT_URBS; ++i) { + struct urb* urb = ep->urbs[i]; + urb->dev = ep->umidi->dev; + snd_usbmidi_submit_urb(urb, GFP_KERNEL); + } +} + +/* + * Resume input after a call to snd_usbmidi_input_stop(). + */ +void snd_usbmidi_input_start(struct list_head* p) +{ + struct snd_usb_midi* umidi; + int i; + + umidi = list_entry(p, struct snd_usb_midi, list); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) + snd_usbmidi_input_start_ep(umidi->endpoints[i].in); +} + +/* + * Creates and registers everything needed for a MIDI streaming interface. + */ +int snd_usbmidi_create(struct snd_card *card, + struct usb_interface* iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk* quirk) +{ + struct snd_usb_midi* umidi; + struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; + int out_ports, in_ports; + int i, err; + + umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); + if (!umidi) + return -ENOMEM; + umidi->dev = interface_to_usbdev(iface); + umidi->card = card; + umidi->iface = iface; + umidi->quirk = quirk; + umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; + init_timer(&umidi->error_timer); + spin_lock_init(&umidi->disc_lock); + mutex_init(&umidi->mutex); + umidi->usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor), + le16_to_cpu(umidi->dev->descriptor.idProduct)); + umidi->error_timer.function = snd_usbmidi_error_timer; + umidi->error_timer.data = (unsigned long)umidi; + + /* detect the endpoint(s) to use */ + memset(endpoints, 0, sizeof(endpoints)); + switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) { + case QUIRK_MIDI_STANDARD_INTERFACE: + err = snd_usbmidi_get_ms_info(umidi, endpoints); + if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */ + umidi->usb_protocol_ops = + &snd_usbmidi_maudio_broken_running_status_ops; + break; + case QUIRK_MIDI_US122L: + umidi->usb_protocol_ops = &snd_usbmidi_122l_ops; + /* fall through */ + case QUIRK_MIDI_FIXED_ENDPOINT: + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); + break; + case QUIRK_MIDI_YAMAHA: + err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]); + break; + case QUIRK_MIDI_MIDIMAN: + umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops; + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = 0; + break; + case QUIRK_MIDI_NOVATION: + umidi->usb_protocol_ops = &snd_usbmidi_novation_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_FASTLANE: + umidi->usb_protocol_ops = &snd_usbmidi_raw_ops; + /* + * Interface 1 contains isochronous endpoints, but with the same + * numbers as in interface 0. Since it is interface 1 that the + * USB core has most recently seen, these descriptors are now + * associated with the endpoint numbers. This will foul up our + * attempts to submit bulk/interrupt URBs to the endpoints in + * interface 0, so we have to make sure that the USB core looks + * again at interface 0 by calling usb_set_interface() on it. + */ + usb_set_interface(umidi->dev, 0, 0); + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_EMAGIC: + umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops; + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); + break; + case QUIRK_MIDI_CME: + umidi->usb_protocol_ops = &snd_usbmidi_cme_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + default: + snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); + err = -ENXIO; + break; + } + if (err < 0) { + kfree(umidi); + return err; + } + + /* create rawmidi device */ + out_ports = 0; + in_ports = 0; + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + out_ports += hweight16(endpoints[i].out_cables); + in_ports += hweight16(endpoints[i].in_cables); + } + err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports); + if (err < 0) { + kfree(umidi); + return err; + } + + /* create endpoint/port structures */ + if (quirk && quirk->type == QUIRK_MIDI_MIDIMAN) + err = snd_usbmidi_create_endpoints_midiman(umidi, &endpoints[0]); + else + err = snd_usbmidi_create_endpoints(umidi, endpoints); + if (err < 0) { + snd_usbmidi_free(umidi); + return err; + } + + list_add_tail(&umidi->list, midi_list); + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) + snd_usbmidi_input_start_ep(umidi->endpoints[i].in); + return 0; +} + +EXPORT_SYMBOL(snd_usbmidi_create); +EXPORT_SYMBOL(snd_usbmidi_input_stop); +EXPORT_SYMBOL(snd_usbmidi_input_start); +EXPORT_SYMBOL(snd_usbmidi_disconnect); diff --git a/sound/usb/midi.h b/sound/usb/midi.h new file mode 100644 index 00000000000..2089ec987c6 --- /dev/null +++ b/sound/usb/midi.h @@ -0,0 +1,48 @@ +#ifndef __USBMIDI_H +#define __USBMIDI_H + +/* maximum number of endpoints per interface */ +#define MIDI_MAX_ENDPOINTS 2 + +/* data for QUIRK_MIDI_FIXED_ENDPOINT */ +struct snd_usb_midi_endpoint_info { + int8_t out_ep; /* ep number, 0 autodetect */ + uint8_t out_interval; /* interval for interrupt endpoints */ + int8_t in_ep; + uint8_t in_interval; + uint16_t out_cables; /* bitmask */ + uint16_t in_cables; /* bitmask */ +}; + +/* for QUIRK_MIDI_YAMAHA, data is NULL */ + +/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk + * structures, terminated with .ifnum = -1 */ + +/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ + +/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ + +/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */ + +/* for QUIRK_IGNORE_INTERFACE, data is NULL */ + +/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */ + +/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_MIDI_CME, data is NULL */ + +int snd_usbmidi_create(struct snd_card *card, + struct usb_interface *iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk *quirk); +void snd_usbmidi_input_stop(struct list_head* p); +void snd_usbmidi_input_start(struct list_head* p); +void snd_usbmidi_disconnect(struct list_head *p); + +#endif /* __USBMIDI_H */ diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index b4a4cb46a17..796d8b25ee8 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -24,7 +24,7 @@ #include #include #include "../usbaudio.h" -#include "../usbmidi.h" +#include "../midi.h" MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); MODULE_AUTHOR("Clemens Ladisch "); diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c new file mode 100644 index 00000000000..87863ccf906 --- /dev/null +++ b/sound/usb/pcm.c @@ -0,0 +1,845 @@ +/* + * 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. + * + * 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 + */ + +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "quirks.h" +#include "debug.h" +#include "urb.h" +#include "helper.h" +#include "pcm.h" + +/* + * return the current pcm pointer. just based on the hwptr_done value. + */ +static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs; + unsigned int hwptr_done; + + subs = (struct snd_usb_substream *)substream->runtime->private_data; + spin_lock(&subs->lock); + hwptr_done = subs->hwptr_done; + spin_unlock(&subs->lock); + return hwptr_done / (substream->runtime->frame_bits >> 3); +} + +/* + * find a matching audio format + */ +static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned int format, + unsigned int rate, unsigned int channels) +{ + struct list_head *p; + struct audioformat *found = NULL; + int cur_attr = 0, attr; + + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + if (fp->format != format || fp->channels != channels) + continue; + if (rate < fp->rate_min || rate > fp->rate_max) + continue; + if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { + unsigned int i; + for (i = 0; i < fp->nr_rates; i++) + if (fp->rate_table[i] == rate) + break; + if (i >= fp->nr_rates) + continue; + } + attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE; + if (! found) { + found = fp; + cur_attr = attr; + continue; + } + /* avoid async out and adaptive in if the other method + * supports the same format. + * this is a workaround for the case like + * M-audio audiophile USB. + */ + if (attr != cur_attr) { + if ((attr == USB_ENDPOINT_SYNC_ASYNC && + subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || + (attr == USB_ENDPOINT_SYNC_ADAPTIVE && + subs->direction == SNDRV_PCM_STREAM_CAPTURE)) + continue; + if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC && + subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || + (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE && + subs->direction == SNDRV_PCM_STREAM_CAPTURE)) { + found = fp; + cur_attr = attr; + continue; + } + } + /* find the format with the largest max. packet size */ + if (fp->maxpacksize > found->maxpacksize) { + found = fp; + cur_attr = attr; + } + } + return found; +} + + +/* + * initialize the picth control and sample rate + */ +int snd_usb_init_pitch(struct usb_device *dev, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt) +{ + unsigned int ep; + unsigned char data[1]; + int err; + + ep = get_endpoint(alts, 0)->bEndpointAddress; + /* if endpoint has pitch control, enable it */ + if (fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL) { + data[0] = 1; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", + dev->devnum, iface, ep); + return err; + } + } + return 0; +} + +int snd_usb_init_sample_rate(struct usb_device *dev, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + unsigned int ep; + unsigned char data[3]; + int err; + + ep = get_endpoint(alts, 0)->bEndpointAddress; + /* if endpoint has sampling rate control, set it */ + if (fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE) { + int crate; + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", + dev->devnum, iface, fmt->altsetting, rate, ep); + return err; + } + if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { + snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", + dev->devnum, iface, fmt->altsetting, ep); + return 0; /* some devices don't support reading */ + } + crate = data[0] | (data[1] << 8) | (data[2] << 16); + if (crate != rate) { + snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); + // runtime->rate = crate; + } + } + return 0; +} + +/* + * find a matching format and set up the interface + */ +static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) +{ + struct usb_device *dev = subs->dev; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_interface *iface; + unsigned int ep, attr; + int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; + int err; + + iface = usb_ifnum_to_if(dev, fmt->iface); + if (WARN_ON(!iface)) + return -EINVAL; + alts = &iface->altsetting[fmt->altset_idx]; + altsd = get_iface_desc(alts); + if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting)) + return -EINVAL; + + if (fmt == subs->cur_audiofmt) + return 0; + + /* close the old interface */ + if (subs->interface >= 0 && subs->interface != fmt->iface) { + if (usb_set_interface(subs->dev, subs->interface, 0) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: return to setting 0 failed\n", + dev->devnum, fmt->iface, fmt->altsetting); + return -EIO; + } + subs->interface = -1; + subs->format = 0; + } + + /* set interface */ + if (subs->interface != fmt->iface || subs->format != fmt->altset_idx) { + if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n", + dev->devnum, fmt->iface, fmt->altsetting); + return -EIO; + } + snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting); + subs->interface = fmt->iface; + subs->format = fmt->altset_idx; + } + + /* create a data pipe */ + ep = fmt->endpoint & USB_ENDPOINT_NUMBER_MASK; + if (is_playback) + subs->datapipe = usb_sndisocpipe(dev, ep); + else + subs->datapipe = usb_rcvisocpipe(dev, ep); + subs->datainterval = fmt->datainterval; + subs->syncpipe = subs->syncinterval = 0; + subs->maxpacksize = fmt->maxpacksize; + subs->fill_max = 0; + + /* we need a sync pipe in async OUT or adaptive IN mode */ + /* check the number of EP, since some devices have broken + * descriptors which fool us. if it has only one EP, + * assume it as adaptive-out or sync-in. + */ + attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; + if (((is_playback && attr == USB_ENDPOINT_SYNC_ASYNC) || + (! is_playback && attr == USB_ENDPOINT_SYNC_ADAPTIVE)) && + altsd->bNumEndpoints >= 2) { + /* check sync-pipe endpoint */ + /* ... and check descriptor size before accessing bSynchAddress + because there is a version of the SB Audigy 2 NX firmware lacking + the audio fields in the endpoint descriptors */ + if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != 0x01 || + (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 1)->bSynchAddress != 0)) { + snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", + dev->devnum, fmt->iface, fmt->altsetting); + return -EINVAL; + } + ep = get_endpoint(alts, 1)->bEndpointAddress; + if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + (( is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || + (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { + snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", + dev->devnum, fmt->iface, fmt->altsetting); + return -EINVAL; + } + ep &= USB_ENDPOINT_NUMBER_MASK; + if (is_playback) + subs->syncpipe = usb_rcvisocpipe(dev, ep); + else + subs->syncpipe = usb_sndisocpipe(dev, ep); + if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 1)->bRefresh >= 1 && + get_endpoint(alts, 1)->bRefresh <= 9) + subs->syncinterval = get_endpoint(alts, 1)->bRefresh; + else if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) + subs->syncinterval = 1; + else if (get_endpoint(alts, 1)->bInterval >= 1 && + get_endpoint(alts, 1)->bInterval <= 16) + subs->syncinterval = get_endpoint(alts, 1)->bInterval - 1; + else + subs->syncinterval = 3; + } + + /* always fill max packet size */ + if (fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX) + subs->fill_max = 1; + + if ((err = snd_usb_init_pitch(dev, subs->interface, alts, fmt)) < 0) + return err; + + subs->cur_audiofmt = fmt; + + snd_usb_set_format_quirk(subs, fmt); + +#if 0 + printk(KERN_DEBUG + "setting done: format = %d, rate = %d..%d, channels = %d\n", + fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels); + printk(KERN_DEBUG + " datapipe = 0x%0x, syncpipe = 0x%0x\n", + subs->datapipe, subs->syncpipe); +#endif + + return 0; +} + +/* + * hw_params callback + * + * allocate a buffer and set the given audio format. + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. + */ +static int snd_usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + struct audioformat *fmt; + unsigned int channels, rate, format; + int ret, changed; + + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + format = params_format(hw_params); + rate = params_rate(hw_params); + channels = params_channels(hw_params); + fmt = find_format(subs, format, rate, channels); + if (!fmt) { + snd_printd(KERN_DEBUG "cannot set format: format = %#x, rate = %d, channels = %d\n", + format, rate, channels); + return -EINVAL; + } + + changed = subs->cur_audiofmt != fmt || + subs->period_bytes != params_period_bytes(hw_params) || + subs->cur_rate != rate; + if ((ret = set_format(subs, fmt)) < 0) + return ret; + + if (subs->cur_rate != rate) { + struct usb_host_interface *alts; + struct usb_interface *iface; + iface = usb_ifnum_to_if(subs->dev, fmt->iface); + alts = &iface->altsetting[fmt->altset_idx]; + ret = snd_usb_init_sample_rate(subs->dev, subs->interface, alts, fmt, rate); + if (ret < 0) + return ret; + subs->cur_rate = rate; + } + + if (changed) { + /* format changed */ + snd_usb_release_substream_urbs(subs, 0); + /* influenced: period_bytes, channels, rate, format, */ + ret = snd_usb_init_substream_urbs(subs, params_period_bytes(hw_params), + params_rate(hw_params), + snd_pcm_format_physical_width(params_format(hw_params)) * + params_channels(hw_params)); + } + + return ret; +} + +/* + * hw_free callback + * + * reset the audio format and release the buffer + */ +static int snd_usb_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + subs->cur_audiofmt = NULL; + subs->cur_rate = 0; + subs->period_bytes = 0; + if (!subs->stream->chip->shutdown) + snd_usb_release_substream_urbs(subs, 0); + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +/* + * prepare callback + * + * only a few subtle things... + */ +static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usb_substream *subs = runtime->private_data; + + if (! subs->cur_audiofmt) { + snd_printk(KERN_ERR "usbaudio: no format is specified!\n"); + return -ENXIO; + } + + /* some unit conversions in runtime */ + subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize); + subs->curframesize = bytes_to_frames(runtime, subs->curpacksize); + + /* reset the pointer */ + subs->hwptr_done = 0; + subs->transfer_done = 0; + subs->phase = 0; + runtime->delay = 0; + + return snd_usb_substream_prepare(subs, runtime); +} + +static struct snd_pcm_hardware snd_usb_hardware = +{ + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +static int hw_check_valid_format(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *params, + struct audioformat *fp) +{ + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); + unsigned int ptime; + + /* check the format */ + if (!snd_mask_test(fmts, fp->format)) { + hwc_debug(" > check: no supported format %d\n", fp->format); + return 0; + } + /* check the channels */ + if (fp->channels < ct->min || fp->channels > ct->max) { + hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max); + return 0; + } + /* check the rate is within the range */ + if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) { + hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max); + return 0; + } + if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) { + hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min); + return 0; + } + /* check whether the period time is >= the data packet interval */ + if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) { + ptime = 125 * (1 << fp->datainterval); + if (ptime > pt->max || (ptime == pt->max && pt->openmax)) { + hwc_debug(" > check: ptime %u > max %u\n", ptime, pt->max); + return 0; + } + } + return 1; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct list_head *p; + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + unsigned int rmin, rmax; + int changed; + + hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max); + changed = 0; + rmin = rmax = 0; + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + if (!hw_check_valid_format(subs, params, fp)) + continue; + if (changed++) { + if (rmin > fp->rate_min) + rmin = fp->rate_min; + if (rmax < fp->rate_max) + rmax = fp->rate_max; + } else { + rmin = fp->rate_min; + rmax = fp->rate_max; + } + } + + if (!changed) { + hwc_debug(" --> get empty\n"); + it->empty = 1; + return -EINVAL; + } + + changed = 0; + if (it->min < rmin) { + it->min = rmin; + it->openmin = 0; + changed = 1; + } + if (it->max > rmax) { + it->max = rmax; + it->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct list_head *p; + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + unsigned int rmin, rmax; + int changed; + + hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max); + changed = 0; + rmin = rmax = 0; + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + if (!hw_check_valid_format(subs, params, fp)) + continue; + if (changed++) { + if (rmin > fp->channels) + rmin = fp->channels; + if (rmax < fp->channels) + rmax = fp->channels; + } else { + rmin = fp->channels; + rmax = fp->channels; + } + } + + if (!changed) { + hwc_debug(" --> get empty\n"); + it->empty = 1; + return -EINVAL; + } + + changed = 0; + if (it->min < rmin) { + it->min = rmin; + it->openmin = 0; + changed = 1; + } + if (it->max > rmax) { + it->max = rmax; + it->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + +static int hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct list_head *p; + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + u64 fbits; + u32 oldbits[2]; + int changed; + + hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]); + fbits = 0; + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + if (!hw_check_valid_format(subs, params, fp)) + continue; + fbits |= (1ULL << fp->format); + } + + oldbits[0] = fmt->bits[0]; + oldbits[1] = fmt->bits[1]; + fmt->bits[0] &= (u32)fbits; + fmt->bits[1] &= (u32)(fbits >> 32); + if (!fmt->bits[0] && !fmt->bits[1]) { + hwc_debug(" --> get empty\n"); + return -EINVAL; + } + changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); + hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); + return changed; +} + +static int hw_rule_period_time(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct audioformat *fp; + struct snd_interval *it; + unsigned char min_datainterval; + unsigned int pmin; + int changed; + + it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); + hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max); + min_datainterval = 0xff; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + min_datainterval = min(min_datainterval, fp->datainterval); + } + if (min_datainterval == 0xff) { + hwc_debug(" --> get emtpy\n"); + it->empty = 1; + return -EINVAL; + } + pmin = 125 * (1 << min_datainterval); + changed = 0; + if (it->min < pmin) { + it->min = pmin; + it->openmin = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%u,%u) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + +/* + * If the device supports unusual bit rates, does the request meet these? + */ +static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime, + struct snd_usb_substream *subs) +{ + struct audioformat *fp; + int count = 0, needs_knot = 0; + int err; + + list_for_each_entry(fp, &subs->fmt_list, list) { + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) + return 0; + count += fp->nr_rates; + if (fp->rates & SNDRV_PCM_RATE_KNOT) + needs_knot = 1; + } + if (!needs_knot) + return 0; + + subs->rate_list.count = count; + subs->rate_list.list = kmalloc(sizeof(int) * count, GFP_KERNEL); + subs->rate_list.mask = 0; + count = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + int i; + for (i = 0; i < fp->nr_rates; i++) + subs->rate_list.list[count++] = fp->rate_table[i]; + } + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &subs->rate_list); + if (err < 0) + return err; + + return 0; +} + + +/* + * set up the runtime hardware information. + */ + +static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs) +{ + struct list_head *p; + unsigned int pt, ptmin; + int param_period_time_if_needed; + int err; + + runtime->hw.formats = subs->formats; + + runtime->hw.rate_min = 0x7fffffff; + runtime->hw.rate_max = 0; + runtime->hw.channels_min = 256; + runtime->hw.channels_max = 0; + runtime->hw.rates = 0; + ptmin = UINT_MAX; + /* check min/max rates and channels */ + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + runtime->hw.rates |= fp->rates; + if (runtime->hw.rate_min > fp->rate_min) + runtime->hw.rate_min = fp->rate_min; + if (runtime->hw.rate_max < fp->rate_max) + runtime->hw.rate_max = fp->rate_max; + if (runtime->hw.channels_min > fp->channels) + runtime->hw.channels_min = fp->channels; + if (runtime->hw.channels_max < fp->channels) + runtime->hw.channels_max = fp->channels; + if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) { + /* FIXME: there might be more than one audio formats... */ + runtime->hw.period_bytes_min = runtime->hw.period_bytes_max = + fp->frame_size; + } + pt = 125 * (1 << fp->datainterval); + ptmin = min(ptmin, pt); + } + + param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME; + if (snd_usb_get_speed(subs->dev) != USB_SPEED_HIGH) + /* full speed devices have fixed data packet interval */ + ptmin = 1000; + if (ptmin == 1000) + /* if period time doesn't go below 1 ms, no rules needed */ + param_period_time_if_needed = -1; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + ptmin, UINT_MAX); + + if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1)) < 0) + return err; + if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_RATE, + param_period_time_if_needed, + -1)) < 0) + return err; + if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_format, subs, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1)) < 0) + return err; + if (param_period_time_if_needed >= 0) { + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + hw_rule_period_time, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + -1); + if (err < 0) + return err; + } + if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0) + return err; + return 0; +} + +static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) +{ + struct snd_usb_stream *as = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usb_substream *subs = &as->substream[direction]; + + subs->interface = -1; + subs->format = 0; + runtime->hw = snd_usb_hardware; + runtime->private_data = subs; + subs->pcm_substream = substream; + return setup_hw_info(runtime, subs); +} + +static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) +{ + struct snd_usb_stream *as = snd_pcm_substream_chip(substream); + struct snd_usb_substream *subs = &as->substream[direction]; + + if (!as->chip->shutdown && subs->interface >= 0) { + usb_set_interface(subs->dev, subs->interface, 0); + subs->interface = -1; + } + subs->pcm_substream = NULL; + return 0; +} + +static int snd_usb_playback_open(struct snd_pcm_substream *substream) +{ + return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK); +} + +static int snd_usb_playback_close(struct snd_pcm_substream *substream) +{ + return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK); +} + +static int snd_usb_capture_open(struct snd_pcm_substream *substream) +{ + return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE); +} + +static int snd_usb_capture_close(struct snd_pcm_substream *substream) +{ + return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE); +} + +static struct snd_pcm_ops snd_usb_playback_ops = { + .open = snd_usb_playback_open, + .close = snd_usb_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_playback_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static struct snd_pcm_ops snd_usb_capture_ops = { + .open = snd_usb_capture_open, + .close = snd_usb_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_capture_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream) +{ + snd_pcm_set_ops(pcm, stream, + stream == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_usb_playback_ops : &snd_usb_capture_ops); +} diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h new file mode 100644 index 00000000000..85856016e05 --- /dev/null +++ b/sound/usb/pcm.h @@ -0,0 +1,14 @@ +#ifndef __USBAUDIO_PCM_H +#define __USBAUDIO_PCM_H + +void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); + +int snd_usb_init_pitch(struct usb_device *dev, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt); + +int snd_usb_init_sample_rate(struct usb_device *dev, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate); + +#endif /* __USBAUDIO_PCM_H */ diff --git a/sound/usb/proc.c b/sound/usb/proc.c new file mode 100644 index 00000000000..be3065ea1af --- /dev/null +++ b/sound/usb/proc.c @@ -0,0 +1,163 @@ +/* + * 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. + * + * 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 + * + */ + +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "card.h" +#include "proc.h" + +/* convert our full speed USB rate into sampling rate in Hz */ +static inline unsigned get_full_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 12)) >> 13; +} + +/* convert our high speed USB rate into sampling rate in Hz */ +static inline unsigned get_high_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 9)) >> 10; +} + +/* + * common proc files to show the usb device info + */ +static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + if (!chip->shutdown) + snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum); +} + +static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + if (!chip->shutdown) + snd_iprintf(buffer, "%04x:%04x\n", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); +} + +void snd_usb_audio_create_proc(struct snd_usb_audio *chip) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(chip->card, "usbbus", &entry)) + snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read); + if (!snd_card_proc_new(chip->card, "usbid", &entry)) + snd_info_set_text_ops(entry, chip, proc_audio_usbid_read); +} + +/* + * proc interface for list the supported pcm formats + */ +static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) +{ + struct list_head *p; + static char *sync_types[4] = { + "NONE", "ASYNC", "ADAPTIVE", "SYNC" + }; + + list_for_each(p, &subs->fmt_list) { + struct audioformat *fp; + fp = list_entry(p, struct audioformat, list); + snd_iprintf(buffer, " Interface %d\n", fp->iface); + snd_iprintf(buffer, " Altset %d\n", fp->altsetting); + snd_iprintf(buffer, " Format: %s\n", + snd_pcm_format_name(fp->format)); + snd_iprintf(buffer, " Channels: %d\n", fp->channels); + snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", + fp->endpoint & USB_ENDPOINT_NUMBER_MASK, + fp->endpoint & USB_DIR_IN ? "IN" : "OUT", + sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]); + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { + snd_iprintf(buffer, " Rates: %d - %d (continuous)\n", + fp->rate_min, fp->rate_max); + } else { + unsigned int i; + snd_iprintf(buffer, " Rates: "); + for (i = 0; i < fp->nr_rates; i++) { + if (i > 0) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d", fp->rate_table[i]); + } + snd_iprintf(buffer, "\n"); + } + if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) + snd_iprintf(buffer, " Data packet interval: %d us\n", + 125 * (1 << fp->datainterval)); + // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize); + // snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes); + } +} + +static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) +{ + if (subs->running) { + unsigned int i; + snd_iprintf(buffer, " Status: Running\n"); + snd_iprintf(buffer, " Interface = %d\n", subs->interface); + snd_iprintf(buffer, " Altset = %d\n", subs->format); + snd_iprintf(buffer, " URBs = %d [ ", subs->nurbs); + for (i = 0; i < subs->nurbs; i++) + snd_iprintf(buffer, "%d ", subs->dataurb[i].packets); + snd_iprintf(buffer, "]\n"); + snd_iprintf(buffer, " Packet Size = %d\n", subs->curpacksize); + snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n", + snd_usb_get_speed(subs->dev) == USB_SPEED_FULL + ? get_full_speed_hz(subs->freqm) + : get_high_speed_hz(subs->freqm), + subs->freqm >> 16, subs->freqm & 0xffff); + } else { + snd_iprintf(buffer, " Status: Stop\n"); + } +} + +static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_stream *stream = entry->private_data; + + snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name); + + if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) { + snd_iprintf(buffer, "\nPlayback:\n"); + proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); + proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); + } + if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) { + snd_iprintf(buffer, "\nCapture:\n"); + proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); + proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); + } +} + +void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream) +{ + struct snd_info_entry *entry; + char name[32]; + struct snd_card *card = stream->chip->card; + + sprintf(name, "stream%d", stream->pcm_index); + if (!snd_card_proc_new(card, name, &entry)) + snd_info_set_text_ops(entry, stream, proc_pcm_format_read); +} + diff --git a/sound/usb/proc.h b/sound/usb/proc.h new file mode 100644 index 00000000000..a45b765e4cf --- /dev/null +++ b/sound/usb/proc.h @@ -0,0 +1,8 @@ +#ifndef __USBAUDIO_PROC_H +#define __USBAUDIO_PROC_H + +void snd_usb_audio_create_proc(struct snd_usb_audio *chip); +void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream); + +#endif /* __USBAUDIO_PROC_H */ + diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h new file mode 100644 index 00000000000..2b426c1fd0e --- /dev/null +++ b/sound/usb/quirks-table.h @@ -0,0 +1,2248 @@ +/* + * ALSA USB Audio Driver + * + * Copyright (c) 2002 by Takashi Iwai , + * Clemens Ladisch + * + * + * 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. + * + * 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 + */ + +/* + * The contents of this file are part of the driver's id_table. + * + * In a perfect world, this file would be empty. + */ + +/* + * Use this for devices where other interfaces are standard compliant, + * to prevent the quirk being applied to those interfaces. (To work with + * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.) + */ +#define USB_DEVICE_VENDOR_SPEC(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ + USB_DEVICE_ID_MATCH_PRODUCT | \ + USB_DEVICE_ID_MATCH_INT_CLASS, \ + .idVendor = vend, \ + .idProduct = prod, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC + +/* Creative/Toshiba Multimedia Center SB-0500 */ +{ + USB_DEVICE(0x041e, 0x3048), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Toshiba", + .product_name = "SB-0500", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* Creative/E-Mu devices */ +{ + USB_DEVICE(0x041e, 0x3010), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Creative Labs", + .product_name = "Sound Blaster MP3+", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + /* E-Mu 0202 USB */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f02, + .bInterfaceClass = USB_CLASS_AUDIO, +}, +{ + /* E-Mu 0404 USB */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f04, + .bInterfaceClass = USB_CLASS_AUDIO, +}, +{ + /* E-Mu Tracker Pre */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f0a, + .bInterfaceClass = USB_CLASS_AUDIO, +}, + +/* + * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface + * class matches do not take effect without an explicit ID match. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x0850, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08ae, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08c6, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f5, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f6, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + USB_DEVICE(0x046d, 0x0990), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Logitech, Inc.", + .product_name = "QuickCam Pro 9000", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* + * Yamaha devices + */ + +#define YAMAHA_DEVICE(id, name) { \ + USB_DEVICE(0x0499, id), \ + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + .vendor_name = "Yamaha", \ + .product_name = name, \ + .ifnum = QUIRK_ANY_INTERFACE, \ + .type = QUIRK_MIDI_YAMAHA \ + } \ +} +#define YAMAHA_INTERFACE(id, intf, name) { \ + USB_DEVICE_VENDOR_SPEC(0x0499, id), \ + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + .vendor_name = "Yamaha", \ + .product_name = name, \ + .ifnum = intf, \ + .type = QUIRK_MIDI_YAMAHA \ + } \ +} +YAMAHA_DEVICE(0x1000, "UX256"), +YAMAHA_DEVICE(0x1001, "MU1000"), +YAMAHA_DEVICE(0x1002, "MU2000"), +YAMAHA_DEVICE(0x1003, "MU500"), +YAMAHA_INTERFACE(0x1004, 3, "UW500"), +YAMAHA_DEVICE(0x1005, "MOTIF6"), +YAMAHA_DEVICE(0x1006, "MOTIF7"), +YAMAHA_DEVICE(0x1007, "MOTIF8"), +YAMAHA_DEVICE(0x1008, "UX96"), +YAMAHA_DEVICE(0x1009, "UX16"), +YAMAHA_INTERFACE(0x100a, 3, "EOS BX"), +YAMAHA_DEVICE(0x100c, "UC-MX"), +YAMAHA_DEVICE(0x100d, "UC-KX"), +YAMAHA_DEVICE(0x100e, "S08"), +YAMAHA_DEVICE(0x100f, "CLP-150"), +YAMAHA_DEVICE(0x1010, "CLP-170"), +YAMAHA_DEVICE(0x1011, "P-250"), +YAMAHA_DEVICE(0x1012, "TYROS"), +YAMAHA_DEVICE(0x1013, "PF-500"), +YAMAHA_DEVICE(0x1014, "S90"), +YAMAHA_DEVICE(0x1015, "MOTIF-R"), +YAMAHA_DEVICE(0x1016, "MDP-5"), +YAMAHA_DEVICE(0x1017, "CVP-204"), +YAMAHA_DEVICE(0x1018, "CVP-206"), +YAMAHA_DEVICE(0x1019, "CVP-208"), +YAMAHA_DEVICE(0x101a, "CVP-210"), +YAMAHA_DEVICE(0x101b, "PSR-1100"), +YAMAHA_DEVICE(0x101c, "PSR-2100"), +YAMAHA_DEVICE(0x101d, "CLP-175"), +YAMAHA_DEVICE(0x101e, "PSR-K1"), +YAMAHA_DEVICE(0x101f, "EZ-J24"), +YAMAHA_DEVICE(0x1020, "EZ-250i"), +YAMAHA_DEVICE(0x1021, "MOTIF ES 6"), +YAMAHA_DEVICE(0x1022, "MOTIF ES 7"), +YAMAHA_DEVICE(0x1023, "MOTIF ES 8"), +YAMAHA_DEVICE(0x1024, "CVP-301"), +YAMAHA_DEVICE(0x1025, "CVP-303"), +YAMAHA_DEVICE(0x1026, "CVP-305"), +YAMAHA_DEVICE(0x1027, "CVP-307"), +YAMAHA_DEVICE(0x1028, "CVP-309"), +YAMAHA_DEVICE(0x1029, "CVP-309GP"), +YAMAHA_DEVICE(0x102a, "PSR-1500"), +YAMAHA_DEVICE(0x102b, "PSR-3000"), +YAMAHA_DEVICE(0x102e, "ELS-01/01C"), +YAMAHA_DEVICE(0x1030, "PSR-295/293"), +YAMAHA_DEVICE(0x1031, "DGX-205/203"), +YAMAHA_DEVICE(0x1032, "DGX-305"), +YAMAHA_DEVICE(0x1033, "DGX-505"), +YAMAHA_DEVICE(0x1034, NULL), +YAMAHA_DEVICE(0x1035, NULL), +YAMAHA_DEVICE(0x1036, NULL), +YAMAHA_DEVICE(0x1037, NULL), +YAMAHA_DEVICE(0x1038, NULL), +YAMAHA_DEVICE(0x1039, NULL), +YAMAHA_DEVICE(0x103a, NULL), +YAMAHA_DEVICE(0x103b, NULL), +YAMAHA_DEVICE(0x103c, NULL), +YAMAHA_DEVICE(0x103d, NULL), +YAMAHA_DEVICE(0x103e, NULL), +YAMAHA_DEVICE(0x103f, NULL), +YAMAHA_DEVICE(0x1040, NULL), +YAMAHA_DEVICE(0x1041, NULL), +YAMAHA_DEVICE(0x1042, NULL), +YAMAHA_DEVICE(0x1043, NULL), +YAMAHA_DEVICE(0x1044, NULL), +YAMAHA_DEVICE(0x1045, NULL), +YAMAHA_INTERFACE(0x104e, 0, NULL), +YAMAHA_DEVICE(0x104f, NULL), +YAMAHA_DEVICE(0x1050, NULL), +YAMAHA_DEVICE(0x1051, NULL), +YAMAHA_DEVICE(0x1052, NULL), +YAMAHA_DEVICE(0x2000, "DGP-7"), +YAMAHA_DEVICE(0x2001, "DGP-5"), +YAMAHA_DEVICE(0x2002, NULL), +YAMAHA_DEVICE(0x5000, "CS1D"), +YAMAHA_DEVICE(0x5001, "DSP1D"), +YAMAHA_DEVICE(0x5002, "DME32"), +YAMAHA_DEVICE(0x5003, "DM2000"), +YAMAHA_DEVICE(0x5004, "02R96"), +YAMAHA_DEVICE(0x5005, "ACU16-C"), +YAMAHA_DEVICE(0x5006, "NHB32-C"), +YAMAHA_DEVICE(0x5007, "DM1000"), +YAMAHA_DEVICE(0x5008, "01V96"), +YAMAHA_DEVICE(0x5009, "SPX2000"), +YAMAHA_DEVICE(0x500a, "PM5D"), +YAMAHA_DEVICE(0x500b, "DME64N"), +YAMAHA_DEVICE(0x500c, "DME24N"), +YAMAHA_DEVICE(0x500d, NULL), +YAMAHA_DEVICE(0x500e, NULL), +YAMAHA_DEVICE(0x500f, NULL), +YAMAHA_DEVICE(0x7000, "DTX"), +YAMAHA_DEVICE(0x7010, "UB99"), +#undef YAMAHA_DEVICE +#undef YAMAHA_INTERFACE + +/* + * Roland/RolandED/Edirol/BOSS devices + */ +{ + USB_DEVICE(0x0582, 0x0000), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "UA-100", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S16_LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S16_LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_FILL_MAX, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-8850", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x003f, + .in_cables = 0x003f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0004), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "U-8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0005, + .in_cables = 0x0005 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Has ID 0x0099 when not in "Advanced Driver" mode. + * The UM-2EX has only one input, but we cannot detect this. */ + USB_DEVICE(0x0582, 0x0005), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0007), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-8820", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0013, + .in_cables = 0x0013 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0008), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "PC-300", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x009d when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0009), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-1", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x000b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SK-500", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0013, + .in_cables = 0x0013 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* thanks to Emiliano Grilli + * for helping researching this data */ + USB_DEVICE(0x0582, 0x000c), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-D70", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S24_3LE, + .channels = 2, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S24_3LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x81, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ /* + * This quirk is for the "Advanced Driver" mode of the Edirol UA-5. + * If the advanced mode switch at the back of the unit is off, the + * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks), + * but offers only 16-bit PCM. + * In advanced mode, the UA-5 will output S24_3LE samples (two + * channels) at the rate indicated on the front switch, including + * the 96kHz sample rate. + */ + USB_DEVICE(0x0582, 0x0010), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-5", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0013 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0012), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "XV-5050", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0015 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-880", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + /* has ID 0x0017 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0016), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-90", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x001c when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x001b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "MMP-2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x001e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x001d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "V-SYNTH", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0024 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0023), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-550", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x003f, + .in_cables = 0x003f + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-20 + * has ID 0x0026 and is standard compliant, but has only 16-bit PCM + * and no MIDI. + */ + USB_DEVICE(0x0582, 0x0025), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-20", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S24_3LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S24_3LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0028 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0027), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-20", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* has ID 0x002a when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0029), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-80", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + } +}, +{ /* + * This quirk is for the "Advanced" modes of the Edirol UA-700. + * If the sample format switch is not in an advanced setting, the + * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks), + * but offers only 16-bit PCM and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-700", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 3, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x002e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x002d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "XV-2020", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0030 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x002f), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "VariOS", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + } +}, +{ + /* has ID 0x0034 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0033), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, + /* TODO: add Roland M-1000 support */ +{ + /* + * Has ID 0x0038 when not in "Advanced Driver" mode; + * later revisions use IDs 0x0054 and 0x00a2. + */ + USB_DEVICE(0x0582, 0x0037), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "Digital Piano", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the GS-10 + * has ID 0x003c and is standard compliant, but has only 16-bit PCM + * and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "BOSS", + .product_name = "GS-10", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0041 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0040), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "GI-20", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0043 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0042), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "RS-70", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0049 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0047), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "UR-80", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + /* in the 96 kHz modes, only interface 1 is there */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x004a when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0048), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "UR-80", */ + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, + /* TODO: add Edirol M-100FX support */ +{ + /* has ID 0x004e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x004c), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR-A", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x004f when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x004d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR-A", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX + * is standard compliant, but has only 16-bit PCM. + */ + USB_DEVICE(0x0582, 0x0050), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-3FX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0052), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-1SX", + .ifnum = 0, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE(0x0582, 0x0060), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "EXR Series", + .ifnum = 0, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + /* has ID 0x0067 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0065), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR-1", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0003 + } + } +}, +{ + /* has ID 0x006b when not in "Advanced Driver" mode */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x006a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SP-606", + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x006e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x006d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "FANTOM-X", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ /* + * This quirk is for the "Advanced" modes of the Edirol UA-25. + * If the switch is not in an advanced setting, the UA-25 has + * ID 0x0582/0x0073 and is standard compliant (no quirks), but + * offers only 16-bit PCM at 44.1 kHz and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-25", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0076 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0075), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "BOSS", + .product_name = "DR-880", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x007b when not in "Advanced Driver" mode */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + /* "RD" or "RD-700SX"? */ + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + } +}, +{ + /* has ID 0x0081 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0080), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "G-70", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, + /* TODO: add Roland V-SYNTH XT support */ + /* TODO: add BOSS GT-PRO support */ +{ + /* has ID 0x008c when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x008b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PC-50", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, + /* TODO: add Edirol PC-80 support */ +{ + USB_DEVICE(0x0582, 0x0096), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-1EX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x009a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-3EX", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX + * is standard compliant, but has only 16-bit PCM and no MIDI. + */ + USB_DEVICE(0x0582, 0x00a3), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-4FX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, + /* TODO: add Edirol MD-P1 support */ +{ + USB_DEVICE(0x582, 0x00a6), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "Juno-G", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* Roland SH-201 */ + USB_DEVICE(0x0582, 0x00ad), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SH-201", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Roland SonicCell */ + USB_DEVICE(0x0582, 0x00c2), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SonicCell", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Edirol M-16DX */ + /* FIXME: This quirk gives a good-working capture stream but the + * playback seems problematic because of lacking of sync + * with capture stream. It needs to sync with the capture + * clock. As now, you'll get frequent sound distortions + * via the playback. + */ + USB_DEVICE(0x0582, 0x00c4), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* BOSS GT-10 */ + USB_DEVICE(0x0582, 0x00da), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Advanced modes of the Edirol UA-25EX. + * For the standard mode, UA-25EX has ID 0582:00e7, which + * offers only 16-bit PCM at 44.1 kHz and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-25EX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x00ea when not in Advanced Driver mode */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e9), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Roland", */ + /* .product_name = "UA-1G", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* Guillemot devices */ +{ + /* + * This is for the "Windows Edition" where the external MIDI ports are + * the only MIDI ports; the control data is reported through HID + * interfaces. The "Macintosh Edition" has ID 0xd002 and uses standard + * compliant USB MIDI ports for external MIDI and controls. + */ + USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Hercules", + .product_name = "DJ Console (WE)", + .ifnum = 4, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, + +/* Midiman/M-Audio devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 2x2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 1x1", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Keystation", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 4x4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + } +}, +{ + /* + * For hardware revision 1.05; in the later revisions (1.10 and + * 1.21), 0x1031 is the ID for the device without firmware. + * Thanks to Olaf Giesbrecht + */ + USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 8x8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 8x8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 2x4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x0003 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Quattro", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + /* + * Interfaces 0-2 are "Windows-compatible", 16-bit only, + * and share endpoints with the other interfaces. + * Ignore them. The other interfaces can do 24 bits, + * but captured samples are big-endian (see usbaudio.c). + */ + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 5, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 6, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 7, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 8, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 9, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "AudioPhile", + .ifnum = 6, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Ozone", + .ifnum = 3, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "OmniStudio", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 5, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 6, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 7, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 8, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 9, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0763, 0x2019), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Ozone Academic", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* Casio devices */ +{ + USB_DEVICE(0x07cf, 0x6801), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Casio", + .product_name = "PL-40R", + .ifnum = 0, + .type = QUIRK_MIDI_YAMAHA + } +}, +{ + /* this ID is used by several devices without a product ID */ + USB_DEVICE(0x07cf, 0x6802), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Casio", + .product_name = "Keyboard", + .ifnum = 0, + .type = QUIRK_MIDI_YAMAHA + } +}, + +/* Mark of the Unicorn devices */ +{ + /* thanks to Robert A. Lerche */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_PRODUCT | + USB_DEVICE_ID_MATCH_DEV_SUBCLASS, + .idVendor = 0x07fd, + .idProduct = 0x0001, + .bDeviceSubClass = 2, + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "MOTU", + .product_name = "Fastlane", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_MIDI_FASTLANE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* Emagic devices */ +{ + USB_DEVICE(0x086a, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "Unitor8", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x80ff, + .in_cables = 0x80ff + } + } +}, +{ + USB_DEVICE(0x086a, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "AMT8", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x80ff, + .in_cables = 0x80ff + } + } +}, +{ + USB_DEVICE(0x086a, 0x0003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "MT4", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x800f, + .in_cables = 0x8003 + } + } +}, + +/* TerraTec devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE(0x0ccd, 0x0028), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "Aureon5.1MkII", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + USB_DEVICE(0x0ccd, 0x0035), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Miditech", + .product_name = "Play'n Roll", + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ +{ + USB_DEVICE(0x103d, 0x0100), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Stanton", + .product_name = "ScratchAmp", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + USB_DEVICE(0x103d, 0x0101), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Stanton", + .product_name = "ScratchAmp", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* Novation EMS devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "ReMOTE Audio/XStation", + .ifnum = 4, + .type = QUIRK_MIDI_NOVATION + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "Speedio", + .ifnum = 3, + .type = QUIRK_MIDI_NOVATION + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "ReMOTE25", + .ifnum = 0, + .type = QUIRK_MIDI_NOVATION + } +}, + +/* Access Music devices */ +{ + /* VirusTI Desktop */ + USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &(const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* */ +{ + /* aka. Serato Scratch Live DJ Box */ + USB_DEVICE(0x13e5, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Rane", + .product_name = "SL-1", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* Miditech devices */ +{ + USB_DEVICE(0x4752, 0x0011), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Miditech", + .product_name = "Midistart-2", + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* Central Music devices */ +{ + /* this ID used by both Miditech MidiStudio-2 and CME UF-x */ + USB_DEVICE(0x7104, 0x2202), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* Hauppauge HVR-950Q and HVR-850 */ +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7200), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7201), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7202), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7203), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7204), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7205), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7250), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7230), + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Hauppauge", + .product_name = "HVR-850", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, + +/* Digidesign Mbox */ +{ + /* Thanks to Clemens Ladisch */ + USB_DEVICE(0x0dba, 0x1000), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Digidesign", + .product_name = "MBox", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]){ + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE, + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .format = SNDRV_PCM_FORMAT_S24_3BE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x02, + .ep_attr = 0x01, + .maxpacksize = 0x130, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + } + } + }, + { + .ifnum = -1 + } + } + + } +}, + +{ + /* + * Some USB MIDI devices don't have an audio control interface, + * so we have to grab MIDI streaming interfaces here. + */ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, + +#undef USB_DEVICE_VENDOR_SPEC diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c new file mode 100644 index 00000000000..4c16920844e --- /dev/null +++ b/sound/usb/quirks.c @@ -0,0 +1,592 @@ +/* + * 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. + * + * 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 + */ + +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "usbmixer.h" +#include "midi.h" +#include "quirks.h" +#include "helper.h" +#include "endpoint.h" +#include "pcm.h" + +/* + * handle the quirks for the contained interfaces + */ +static int create_composite_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; + int err; + + for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) { + iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); + if (!iface) + continue; + if (quirk->ifnum != probed_ifnum && + usb_interface_claimed(iface)) + continue; + err = snd_usb_create_quirk(chip, iface, driver, quirk); + if (err < 0) + return err; + if (quirk->ifnum != probed_ifnum) + usb_driver_claim_interface(driver, iface, (void *)-1L); + } + return 0; +} + +static int ignore_interface_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + return 0; +} + + +/* + * Allow alignment on audio sub-slot (channel samples) rather than + * on audio slots (audio frames) + */ +static int create_align_transfer_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + chip->txfr_quirk = 1; + return 1; /* Continue with creating streams and mixer */ +} + +static int create_any_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *intf, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); +} + +/* + * create a stream for an interface with proper descriptors + */ +static int create_standard_audio_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + int err; + + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + err = snd_usb_parse_audio_endpoints(chip, altsd->bInterfaceNumber); + if (err < 0) { + snd_printk(KERN_ERR "cannot setup if %d: error %d\n", + altsd->bInterfaceNumber, err); + return err; + } + /* reset the current interface */ + usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0); + return 0; +} + +/* + * create a stream for an endpoint/altsetting without proper descriptors + */ +static int create_fixed_stream_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + struct audioformat *fp; + struct usb_host_interface *alts; + int stream, err; + unsigned *rate_table = NULL; + + fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL); + if (! fp) { + snd_printk(KERN_ERR "cannot memdup\n"); + return -ENOMEM; + } + if (fp->nr_rates > 0) { + rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL); + if (!rate_table) { + kfree(fp); + return -ENOMEM; + } + memcpy(rate_table, fp->rate_table, sizeof(int) * fp->nr_rates); + fp->rate_table = rate_table; + } + + stream = (fp->endpoint & USB_DIR_IN) + ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + err = snd_usb_add_audio_endpoint(chip, stream, fp); + if (err < 0) { + kfree(fp); + kfree(rate_table); + return err; + } + if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber || + fp->altset_idx >= iface->num_altsetting) { + kfree(fp); + kfree(rate_table); + return -EINVAL; + } + alts = &iface->altsetting[fp->altset_idx]; + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + usb_set_interface(chip->dev, fp->iface, 0); + snd_usb_init_pitch(chip->dev, fp->iface, alts, fp); + snd_usb_init_sample_rate(chip->dev, fp->iface, alts, fp, fp->rate_max); + return 0; +} + +/* + * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface. + * The only way to detect the sample rate is by looking at wMaxPacketSize. + */ +static int create_uaxx_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + static const struct audioformat ua_format = { + .format = SNDRV_PCM_FORMAT_S24_3LE, + .channels = 2, + .fmt_type = UAC_FORMAT_TYPE_I, + .altsetting = 1, + .altset_idx = 1, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + }; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct audioformat *fp; + int stream, err; + + /* both PCM and MIDI interfaces have 2 or more altsettings */ + if (iface->num_altsetting < 2) + return -ENXIO; + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + if (altsd->bNumEndpoints == 2) { + static const struct snd_usb_midi_endpoint_info ua700_ep = { + .out_cables = 0x0003, + .in_cables = 0x0003 + }; + static const struct snd_usb_audio_quirk ua700_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &ua700_ep + }; + static const struct snd_usb_midi_endpoint_info uaxx_ep = { + .out_cables = 0x0001, + .in_cables = 0x0001 + }; + static const struct snd_usb_audio_quirk uaxx_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &uaxx_ep + }; + const struct snd_usb_audio_quirk *quirk = + chip->usb_id == USB_ID(0x0582, 0x002b) + ? &ua700_quirk : &uaxx_quirk; + return snd_usbmidi_create(chip->card, iface, + &chip->midi_list, quirk); + } + + if (altsd->bNumEndpoints != 1) + return -ENXIO; + + fp = kmalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return -ENOMEM; + memcpy(fp, &ua_format, sizeof(*fp)); + + fp->iface = altsd->bInterfaceNumber; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = 0; + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (fp->maxpacksize) { + case 0x120: + fp->rate_max = fp->rate_min = 44100; + break; + case 0x138: + case 0x140: + fp->rate_max = fp->rate_min = 48000; + break; + case 0x258: + case 0x260: + fp->rate_max = fp->rate_min = 96000; + break; + default: + snd_printk(KERN_ERR "unknown sample rate\n"); + kfree(fp); + return -ENXIO; + } + + stream = (fp->endpoint & USB_DIR_IN) + ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + err = snd_usb_add_audio_endpoint(chip, stream, fp); + if (err < 0) { + kfree(fp); + return err; + } + usb_set_interface(chip->dev, fp->iface, 0); + return 0; +} + +/* + * audio-interface quirks + * + * returns zero if no standard audio/MIDI parsing is needed. + * returns a postive value if standard audio/midi interfaces are parsed + * after this. + * returns a negative value at error. + */ +int snd_usb_create_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + typedef int (*quirk_func_t)(struct snd_usb_audio *, + struct usb_interface *, + struct usb_driver *, + const struct snd_usb_audio_quirk *); + static const quirk_func_t quirk_funcs[] = { + [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk, + [QUIRK_COMPOSITE] = create_composite_quirk, + [QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk, + [QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk, + [QUIRK_MIDI_YAMAHA] = create_any_midi_quirk, + [QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk, + [QUIRK_MIDI_NOVATION] = create_any_midi_quirk, + [QUIRK_MIDI_FASTLANE] = create_any_midi_quirk, + [QUIRK_MIDI_EMAGIC] = create_any_midi_quirk, + [QUIRK_MIDI_CME] = create_any_midi_quirk, + [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, + [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, + [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk, + [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk + }; + + if (quirk->type < QUIRK_TYPE_COUNT) { + return quirk_funcs[quirk->type](chip, iface, driver, quirk); + } else { + snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); + return -ENXIO; + } +} + +/* + * boot quirks + */ + +#define EXTIGY_FIRMWARE_SIZE_OLD 794 +#define EXTIGY_FIRMWARE_SIZE_NEW 483 + +static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf) +{ + struct usb_host_config *config = dev->actconfig; + int err; + + if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD || + le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) { + snd_printdd("sending Extigy boot sequence...\n"); + /* Send message to force it to reconnect with full interface. */ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0), + 0x10, 0x43, 0x0001, 0x000a, NULL, 0, 1000); + if (err < 0) snd_printdd("error sending boot message: %d\n", err); + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, + &dev->descriptor, sizeof(dev->descriptor)); + config = dev->actconfig; + if (err < 0) snd_printdd("error usb_get_descriptor: %d\n", err); + err = usb_reset_configuration(dev); + if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err); + snd_printdd("extigy_boot: new boot length = %d\n", + le16_to_cpu(get_cfg_desc(config)->wTotalLength)); + return -ENODEV; /* quit this anyway */ + } + return 0; +} + +static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev) +{ + u8 buf = 1; + + snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 0, 0, &buf, 1, 1000); + if (buf == 0) { + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 1, 2000, NULL, 0, 1000); + return -ENODEV; + } + return 0; +} + +/* + * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely + * documented in the device's data sheet. + */ +static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value) +{ + u8 buf[4]; + buf[0] = 0x20; + buf[1] = value & 0xff; + buf[2] = (value >> 8) & 0xff; + buf[3] = reg; + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, 0, &buf, 4, 1000); +} + +static int snd_usb_cm106_boot_quirk(struct usb_device *dev) +{ + /* + * Enable line-out driver mode, set headphone source to front + * channels, enable stereo mic. + */ + return snd_usb_cm106_write_int_reg(dev, 2, 0x8004); +} + +/* + * C-Media CM6206 is based on CM106 with two additional + * registers that are not documented in the data sheet. + * Values here are chosen based on sniffing USB traffic + * under Windows. + */ +static int snd_usb_cm6206_boot_quirk(struct usb_device *dev) +{ + int err, reg; + int val[] = {0x200c, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000}; + + for (reg = 0; reg < ARRAY_SIZE(val); reg++) { + err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]); + if (err < 0) + return err; + } + + return err; +} + +/* + * This call will put the synth in "USB send" mode, i.e it will send MIDI + * messages through USB (this is disabled at startup). The synth will + * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB + * sign on its LCD. Values here are chosen based on sniffing USB traffic + * under Windows. + */ +static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) +{ + int err, actual_length; + + /* "midi send" enable */ + static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 }; + + void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL); + if (!buf) + return -ENOMEM; + err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf, + ARRAY_SIZE(seq), &actual_length, 1000); + kfree(buf); + if (err < 0) + return err; + + return 0; +} + +/* + * Setup quirks + */ +#define AUDIOPHILE_SET 0x01 /* if set, parse device_setup */ +#define AUDIOPHILE_SET_DTS 0x02 /* if set, enable DTS Digital Output */ +#define AUDIOPHILE_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */ +#define AUDIOPHILE_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */ +#define AUDIOPHILE_SET_DI 0x10 /* if set, enable Digital Input */ +#define AUDIOPHILE_SET_MASK 0x1F /* bit mask for setup value */ +#define AUDIOPHILE_SET_24B_48K_DI 0x19 /* value for 24bits+48KHz+Digital Input */ +#define AUDIOPHILE_SET_24B_48K_NOTDI 0x09 /* value for 24bits+48KHz+No Digital Input */ +#define AUDIOPHILE_SET_16B_48K_DI 0x11 /* value for 16bits+48KHz+Digital Input */ +#define AUDIOPHILE_SET_16B_48K_NOTDI 0x01 /* value for 16bits+48KHz+No Digital Input */ + +static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, + int altno) +{ + /* Reset ALL ifaces to 0 altsetting. + * Call it for every possible altsetting of every interface. + */ + usb_set_interface(chip->dev, iface, 0); + + if (chip->setup & AUDIOPHILE_SET) { + if ((chip->setup & AUDIOPHILE_SET_DTS) + && altno != 6) + return 1; /* skip this altsetting */ + if ((chip->setup & AUDIOPHILE_SET_96K) + && altno != 1) + return 1; /* skip this altsetting */ + if ((chip->setup & AUDIOPHILE_SET_MASK) == + AUDIOPHILE_SET_24B_48K_DI && altno != 2) + return 1; /* skip this altsetting */ + if ((chip->setup & AUDIOPHILE_SET_MASK) == + AUDIOPHILE_SET_24B_48K_NOTDI && altno != 3) + return 1; /* skip this altsetting */ + if ((chip->setup & AUDIOPHILE_SET_MASK) == + AUDIOPHILE_SET_16B_48K_DI && altno != 4) + return 1; /* skip this altsetting */ + if ((chip->setup & AUDIOPHILE_SET_MASK) == + AUDIOPHILE_SET_16B_48K_NOTDI && altno != 5) + return 1; /* skip this altsetting */ + } + + return 0; /* keep this altsetting */ +} + +int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, + int iface, + int altno) +{ + /* audiophile usb: skip altsets incompatible with device_setup */ + if (chip->usb_id == USB_ID(0x0763, 0x2003)) + return audiophile_skip_setting_quirk(chip, iface, altno); + + return 0; +} + +int snd_usb_apply_boot_quirk(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk) +{ + u32 id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + /* SB Extigy needs special boot-up sequence */ + /* if more models come, this will go to the quirk list. */ + if (id == USB_ID(0x041e, 0x3000)) + return snd_usb_extigy_boot_quirk(dev, intf); + + /* SB Audigy 2 NX needs its own boot-up magic, too */ + if (id == USB_ID(0x041e, 0x3020)) + return snd_usb_audigy2nx_boot_quirk(dev); + + /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ + if (id == USB_ID(0x10f5, 0x0200)) + return snd_usb_cm106_boot_quirk(dev); + + /* C-Media CM6206 / CM106-Like Sound Device */ + if (id == USB_ID(0x0d8c, 0x0102)) + return snd_usb_cm6206_boot_quirk(dev); + + /* Access Music VirusTI Desktop */ + if (id == USB_ID(0x133e, 0x0815)) + return snd_usb_accessmusic_boot_quirk(dev); + + return 0; +} + +/* + * check if the device uses big-endian samples + */ +int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp) +{ + switch (chip->usb_id) { + case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */ + if (fp->endpoint & USB_DIR_IN) + return 1; + break; + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + if (chip->setup == 0x00 || + fp->altsetting==1 || fp->altsetting==2 || fp->altsetting==3) + return 1; + } + return 0; +} + +/* + * For E-Mu 0404USB/0202USB/TrackerPre sample rate should be set for device, + * not for interface. + */ + +enum { + EMU_QUIRK_SR_44100HZ = 0, + EMU_QUIRK_SR_48000HZ, + EMU_QUIRK_SR_88200HZ, + EMU_QUIRK_SR_96000HZ, + EMU_QUIRK_SR_176400HZ, + EMU_QUIRK_SR_192000HZ +}; + +static void set_format_emu_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt) +{ + unsigned char emu_samplerate_id = 0; + + /* When capture is active + * sample rate shouldn't be changed + * by playback substream + */ + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { + if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1) + return; + } + + switch (fmt->rate_min) { + case 48000: + emu_samplerate_id = EMU_QUIRK_SR_48000HZ; + break; + case 88200: + emu_samplerate_id = EMU_QUIRK_SR_88200HZ; + break; + case 96000: + emu_samplerate_id = EMU_QUIRK_SR_96000HZ; + break; + case 176400: + emu_samplerate_id = EMU_QUIRK_SR_176400HZ; + break; + case 192000: + emu_samplerate_id = EMU_QUIRK_SR_192000HZ; + break; + default: + emu_samplerate_id = EMU_QUIRK_SR_44100HZ; + break; + } + snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id); +} + +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt) +{ + switch (subs->stream->chip->usb_id) { + case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ + case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ + case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ + set_format_emu_quirk(subs, fmt); + break; + } +} + diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h new file mode 100644 index 00000000000..03e5e94098c --- /dev/null +++ b/sound/usb/quirks.h @@ -0,0 +1,23 @@ +#ifndef __USBAUDIO_QUIRKS_H +#define __USBAUDIO_QUIRKS_H + +int snd_usb_create_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk); + +int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, + int iface, + int altno); + +int snd_usb_apply_boot_quirk(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk); + +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt); + +int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, + struct audioformat *fp); + +#endif /* __USBAUDIO_QUIRKS_H */ diff --git a/sound/usb/urb.c b/sound/usb/urb.c new file mode 100644 index 00000000000..e9c339f7586 --- /dev/null +++ b/sound/usb/urb.c @@ -0,0 +1,989 @@ +/* + * 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. + * + * 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 + * + */ + +#include +#include +#include + +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "card.h" +#include "urb.h" +#include "pcm.h" + +/* + * convert a sampling rate into our full speed format (fs/1000 in Q16.16) + * this will overflow at approx 524 kHz + */ +static inline unsigned get_usb_full_speed_rate(unsigned int rate) +{ + return ((rate << 13) + 62) / 125; +} + +/* + * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) + * this will overflow at approx 4 MHz + */ +static inline unsigned get_usb_high_speed_rate(unsigned int rate) +{ + return ((rate << 10) + 62) / 125; +} + +/* + * unlink active urbs. + */ +static int deactivate_urbs(struct snd_usb_substream *subs, int force, int can_sleep) +{ + struct snd_usb_audio *chip = subs->stream->chip; + unsigned int i; + int async; + + subs->running = 0; + + if (!force && subs->stream->chip->shutdown) /* to be sure... */ + return -EBADFD; + + async = !can_sleep && chip->async_unlink; + + if (!async && in_interrupt()) + return 0; + + for (i = 0; i < subs->nurbs; i++) { + if (test_bit(i, &subs->active_mask)) { + if (!test_and_set_bit(i, &subs->unlink_mask)) { + struct urb *u = subs->dataurb[i].urb; + if (async) + usb_unlink_urb(u); + else + usb_kill_urb(u); + } + } + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if (test_bit(i+16, &subs->active_mask)) { + if (!test_and_set_bit(i+16, &subs->unlink_mask)) { + struct urb *u = subs->syncurb[i].urb; + if (async) + usb_unlink_urb(u); + else + usb_kill_urb(u); + } + } + } + } + return 0; +} + + +/* + * release a urb data + */ +static void release_urb_ctx(struct snd_urb_ctx *u) +{ + if (u->urb) { + if (u->buffer_size) + usb_buffer_free(u->subs->dev, u->buffer_size, + u->urb->transfer_buffer, + u->urb->transfer_dma); + usb_free_urb(u->urb); + u->urb = NULL; + } +} + +/* + * wait until all urbs are processed. + */ +static int wait_clear_urbs(struct snd_usb_substream *subs) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(1000); + unsigned int i; + int alive; + + do { + alive = 0; + for (i = 0; i < subs->nurbs; i++) { + if (test_bit(i, &subs->active_mask)) + alive++; + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if (test_bit(i + 16, &subs->active_mask)) + alive++; + } + } + if (! alive) + break; + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); + return 0; +} + +/* + * release a substream + */ +void snd_usb_release_substream_urbs(struct snd_usb_substream *subs, int force) +{ + int i; + + /* stop urbs (to be sure) */ + deactivate_urbs(subs, force, 1); + wait_clear_urbs(subs); + + for (i = 0; i < MAX_URBS; i++) + release_urb_ctx(&subs->dataurb[i]); + for (i = 0; i < SYNC_URBS; i++) + release_urb_ctx(&subs->syncurb[i]); + usb_buffer_free(subs->dev, SYNC_URBS * 4, + subs->syncbuf, subs->sync_dma); + subs->syncbuf = NULL; + subs->nurbs = 0; +} + +/* + * complete callback from data urb + */ +static void snd_complete_urb(struct urb *urb) +{ + struct snd_urb_ctx *ctx = urb->context; + struct snd_usb_substream *subs = ctx->subs; + struct snd_pcm_substream *substream = ctx->subs->pcm_substream; + int err = 0; + + if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) || + !subs->running || /* can be stopped during retire callback */ + (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 || + (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + clear_bit(ctx->index, &subs->active_mask); + if (err < 0) { + snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } + } +} + + +/* + * complete callback from sync urb + */ +static void snd_complete_sync_urb(struct urb *urb) +{ + struct snd_urb_ctx *ctx = urb->context; + struct snd_usb_substream *subs = ctx->subs; + struct snd_pcm_substream *substream = ctx->subs->pcm_substream; + int err = 0; + + if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) || + !subs->running || /* can be stopped during retire callback */ + (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 || + (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + clear_bit(ctx->index + 16, &subs->active_mask); + if (err < 0) { + snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } + } +} + + +/* + * initialize a substream for plaback/capture + */ +int snd_usb_init_substream_urbs(struct snd_usb_substream *subs, + unsigned int period_bytes, + unsigned int rate, + unsigned int frame_bits) +{ + unsigned int maxsize, i; + int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int urb_packs, total_packs, packs_per_ms; + struct snd_usb_audio *chip = subs->stream->chip; + + /* calculate the frequency in 16.16 format */ + if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) + subs->freqn = get_usb_full_speed_rate(rate); + else + subs->freqn = get_usb_high_speed_rate(rate); + subs->freqm = subs->freqn; + /* calculate max. frequency */ + if (subs->maxpacksize) { + /* whatever fits into a max. size packet */ + maxsize = subs->maxpacksize; + subs->freqmax = (maxsize / (frame_bits >> 3)) + << (16 - subs->datainterval); + } else { + /* no max. packet size: just take 25% higher than nominal */ + subs->freqmax = subs->freqn + (subs->freqn >> 2); + maxsize = ((subs->freqmax + 0xffff) * (frame_bits >> 3)) + >> (16 - subs->datainterval); + } + subs->phase = 0; + + if (subs->fill_max) + subs->curpacksize = subs->maxpacksize; + else + subs->curpacksize = maxsize; + + if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) + packs_per_ms = 8 >> subs->datainterval; + else + packs_per_ms = 1; + + if (is_playback) { + urb_packs = max(chip->nrpacks, 1); + urb_packs = min(urb_packs, (unsigned int)MAX_PACKS); + } else + urb_packs = 1; + urb_packs *= packs_per_ms; + if (subs->syncpipe) + urb_packs = min(urb_packs, 1U << subs->syncinterval); + + /* decide how many packets to be used */ + if (is_playback) { + unsigned int minsize, maxpacks; + /* determine how small a packet can be */ + minsize = (subs->freqn >> (16 - subs->datainterval)) + * (frame_bits >> 3); + /* with sync from device, assume it can be 12% lower */ + if (subs->syncpipe) + minsize -= minsize >> 3; + minsize = max(minsize, 1u); + total_packs = (period_bytes + minsize - 1) / minsize; + /* we need at least two URBs for queueing */ + if (total_packs < 2) { + total_packs = 2; + } else { + /* and we don't want too long a queue either */ + maxpacks = max(MAX_QUEUE * packs_per_ms, urb_packs * 2); + total_packs = min(total_packs, maxpacks); + } + } else { + while (urb_packs > 1 && urb_packs * maxsize >= period_bytes) + urb_packs >>= 1; + total_packs = MAX_URBS * urb_packs; + } + subs->nurbs = (total_packs + urb_packs - 1) / urb_packs; + if (subs->nurbs > MAX_URBS) { + /* too much... */ + subs->nurbs = MAX_URBS; + total_packs = MAX_URBS * urb_packs; + } else if (subs->nurbs < 2) { + /* too little - we need at least two packets + * to ensure contiguous playback/capture + */ + subs->nurbs = 2; + } + + /* allocate and initialize data urbs */ + for (i = 0; i < subs->nurbs; i++) { + struct snd_urb_ctx *u = &subs->dataurb[i]; + u->index = i; + u->subs = subs; + u->packets = (i + 1) * total_packs / subs->nurbs + - i * total_packs / subs->nurbs; + u->buffer_size = maxsize * u->packets; + if (subs->fmt_type == UAC_FORMAT_TYPE_II) + u->packets++; /* for transfer delimiter */ + u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); + if (!u->urb) + goto out_of_memory; + u->urb->transfer_buffer = + usb_buffer_alloc(subs->dev, u->buffer_size, GFP_KERNEL, + &u->urb->transfer_dma); + if (!u->urb->transfer_buffer) + goto out_of_memory; + u->urb->pipe = subs->datapipe; + u->urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + u->urb->interval = 1 << subs->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; + } + + if (subs->syncpipe) { + /* allocate and initialize sync urbs */ + subs->syncbuf = usb_buffer_alloc(subs->dev, SYNC_URBS * 4, + GFP_KERNEL, &subs->sync_dma); + if (!subs->syncbuf) + goto out_of_memory; + for (i = 0; i < SYNC_URBS; i++) { + struct snd_urb_ctx *u = &subs->syncurb[i]; + u->index = i; + u->subs = subs; + u->packets = 1; + u->urb = usb_alloc_urb(1, GFP_KERNEL); + if (!u->urb) + goto out_of_memory; + u->urb->transfer_buffer = subs->syncbuf + i * 4; + u->urb->transfer_dma = subs->sync_dma + i * 4; + u->urb->transfer_buffer_length = 4; + u->urb->pipe = subs->syncpipe; + u->urb->transfer_flags = URB_ISO_ASAP | + URB_NO_TRANSFER_DMA_MAP; + u->urb->number_of_packets = 1; + u->urb->interval = 1 << subs->syncinterval; + u->urb->context = u; + u->urb->complete = snd_complete_sync_urb; + } + } + return 0; + +out_of_memory: + snd_usb_release_substream_urbs(subs, 0); + return -ENOMEM; +} + +/* + * prepare urb for full speed capture sync pipe + * + * fill the length and offset of each urb descriptor. + * the fixed 10.14 frequency is passed through the pipe. + */ +static int prepare_capture_sync_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + struct snd_urb_ctx *ctx = urb->context; + + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->iso_frame_desc[0].length = 3; + urb->iso_frame_desc[0].offset = 0; + cp[0] = subs->freqn >> 2; + cp[1] = subs->freqn >> 10; + cp[2] = subs->freqn >> 18; + return 0; +} + +/* + * prepare urb for high speed capture sync pipe + * + * fill the length and offset of each urb descriptor. + * the fixed 12.13 frequency is passed as 16.16 through the pipe. + */ +static int prepare_capture_sync_urb_hs(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned char *cp = urb->transfer_buffer; + struct snd_urb_ctx *ctx = urb->context; + + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->iso_frame_desc[0].length = 4; + urb->iso_frame_desc[0].offset = 0; + cp[0] = subs->freqn; + cp[1] = subs->freqn >> 8; + cp[2] = subs->freqn >> 16; + cp[3] = subs->freqn >> 24; + return 0; +} + +/* + * process after capture sync complete + * - nothing to do + */ +static int retire_capture_sync_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + return 0; +} + +/* + * prepare urb for capture data pipe + * + * fill the offset and length of each descriptor. + * + * we use a temporary buffer to write the captured data. + * since the length of written data is determined by host, we cannot + * write onto the pcm buffer directly... the data is thus copied + * later at complete callback to the global buffer. + */ +static int prepare_capture_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + int i, offs; + struct snd_urb_ctx *ctx = urb->context; + + offs = 0; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + for (i = 0; i < ctx->packets; i++) { + urb->iso_frame_desc[i].offset = offs; + urb->iso_frame_desc[i].length = subs->curpacksize; + offs += subs->curpacksize; + } + urb->transfer_buffer_length = offs; + urb->number_of_packets = ctx->packets; + return 0; +} + +/* + * process after capture complete + * + * copy the data from each desctiptor to the pcm buffer, and + * update the current position. + */ +static int retire_capture_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned long flags; + unsigned char *cp; + int i; + unsigned int stride, frames, bytes, oldptr; + int period_elapsed = 0; + + stride = runtime->frame_bits >> 3; + + for (i = 0; i < urb->number_of_packets; i++) { + cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (urb->iso_frame_desc[i].status) { + snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status); + // continue; + } + bytes = urb->iso_frame_desc[i].actual_length; + frames = bytes / stride; + if (!subs->txfr_quirk) + bytes = frames * stride; + if (bytes % (runtime->sample_bits >> 3) != 0) { +#ifdef CONFIG_SND_DEBUG_VERBOSE + int oldbytes = bytes; +#endif + bytes = frames * stride; + snd_printdd(KERN_ERR "Corrected urb data len. %d->%d\n", + oldbytes, bytes); + } + /* update the current pointer */ + spin_lock_irqsave(&subs->lock, flags); + oldptr = subs->hwptr_done; + subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; + frames = (bytes + (oldptr % stride)) / stride; + subs->transfer_done += frames; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + period_elapsed = 1; + } + spin_unlock_irqrestore(&subs->lock, flags); + /* copy a data chunk */ + if (oldptr + bytes > runtime->buffer_size * stride) { + unsigned int bytes1 = + runtime->buffer_size * stride - oldptr; + memcpy(runtime->dma_area + oldptr, cp, bytes1); + memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1); + } else { + memcpy(runtime->dma_area + oldptr, cp, bytes); + } + } + if (period_elapsed) + snd_pcm_period_elapsed(subs->pcm_substream); + return 0; +} + +/* + * Process after capture complete when paused. Nothing to do. + */ +static int retire_paused_capture_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + return 0; +} + + +/* + * prepare urb for full speed playback sync pipe + * + * set up the offset and length to receive the current frequency. + */ + +static int prepare_playback_sync_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + struct snd_urb_ctx *ctx = urb->context; + + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->iso_frame_desc[0].length = 3; + urb->iso_frame_desc[0].offset = 0; + return 0; +} + +/* + * prepare urb for high speed playback sync pipe + * + * set up the offset and length to receive the current frequency. + */ + +static int prepare_playback_sync_urb_hs(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + struct snd_urb_ctx *ctx = urb->context; + + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->iso_frame_desc[0].length = 4; + urb->iso_frame_desc[0].offset = 0; + return 0; +} + +/* + * process after full speed playback sync complete + * + * retrieve the current 10.14 frequency from pipe, and set it. + * the value is referred in prepare_playback_urb(). + */ +static int retire_playback_sync_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned int f; + unsigned long flags; + + if (urb->iso_frame_desc[0].status == 0 && + urb->iso_frame_desc[0].actual_length == 3) { + f = combine_triple((u8*)urb->transfer_buffer) << 2; + if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = f; + spin_unlock_irqrestore(&subs->lock, flags); + } + } + + return 0; +} + +/* + * process after high speed playback sync complete + * + * retrieve the current 12.13 frequency from pipe, and set it. + * the value is referred in prepare_playback_urb(). + */ +static int retire_playback_sync_urb_hs(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned int f; + unsigned long flags; + + if (urb->iso_frame_desc[0].status == 0 && + urb->iso_frame_desc[0].actual_length == 4) { + f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; + if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = f; + spin_unlock_irqrestore(&subs->lock, flags); + } + } + + return 0; +} + +/* + * process after E-Mu 0202/0404/Tracker Pre high speed playback sync complete + * + * These devices return the number of samples per packet instead of the number + * of samples per microframe. + */ +static int retire_playback_sync_urb_hs_emu(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned int f; + unsigned long flags; + + if (urb->iso_frame_desc[0].status == 0 && + urb->iso_frame_desc[0].actual_length == 4) { + f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; + f >>= subs->datainterval; + if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { + spin_lock_irqsave(&subs->lock, flags); + subs->freqm = f; + spin_unlock_irqrestore(&subs->lock, flags); + } + } + + return 0; +} + +/* determine the number of frames in the next packet */ +static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs) +{ + if (subs->fill_max) + return subs->maxframesize; + else { + subs->phase = (subs->phase & 0xffff) + + (subs->freqm << subs->datainterval); + return min(subs->phase >> 16, subs->maxframesize); + } +} + +/* + * Prepare urb for streaming before playback starts or when paused. + * + * We don't have any data, so we send silence. + */ +static int prepare_nodata_playback_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned int i, offs, counts; + struct snd_urb_ctx *ctx = urb->context; + int stride = runtime->frame_bits >> 3; + + offs = 0; + urb->dev = ctx->subs->dev; + for (i = 0; i < ctx->packets; ++i) { + counts = snd_usb_audio_next_packet_size(subs); + urb->iso_frame_desc[i].offset = offs * stride; + urb->iso_frame_desc[i].length = counts * stride; + offs += counts; + } + urb->number_of_packets = ctx->packets; + urb->transfer_buffer_length = offs * stride; + memset(urb->transfer_buffer, + subs->cur_audiofmt->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0, + offs * stride); + return 0; +} + +/* + * prepare urb for playback data pipe + * + * Since a URB can handle only a single linear buffer, we must use double + * buffering when the data to be transferred overflows the buffer boundary. + * To avoid inconsistencies when updating hwptr_done, we use double buffering + * for all URBs. + */ +static int prepare_playback_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + int i, stride; + unsigned int counts, frames, bytes; + unsigned long flags; + int period_elapsed = 0; + struct snd_urb_ctx *ctx = urb->context; + + stride = runtime->frame_bits >> 3; + + frames = 0; + urb->dev = ctx->subs->dev; /* we need to set this at each time */ + urb->number_of_packets = 0; + spin_lock_irqsave(&subs->lock, flags); + for (i = 0; i < ctx->packets; i++) { + counts = snd_usb_audio_next_packet_size(subs); + /* set up descriptor */ + urb->iso_frame_desc[i].offset = frames * stride; + urb->iso_frame_desc[i].length = counts * stride; + frames += counts; + urb->number_of_packets++; + subs->transfer_done += counts; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + period_elapsed = 1; + if (subs->fmt_type == UAC_FORMAT_TYPE_II) { + if (subs->transfer_done > 0) { + /* FIXME: fill-max mode is not + * supported yet */ + frames -= subs->transfer_done; + counts -= subs->transfer_done; + urb->iso_frame_desc[i].length = + counts * stride; + subs->transfer_done = 0; + } + i++; + if (i < ctx->packets) { + /* add a transfer delimiter */ + urb->iso_frame_desc[i].offset = + frames * stride; + urb->iso_frame_desc[i].length = 0; + urb->number_of_packets++; + } + break; + } + } + if (period_elapsed) /* finish at the period boundary */ + break; + } + bytes = frames * stride; + if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { + /* err, the transferred area goes over buffer boundary. */ + unsigned int bytes1 = + runtime->buffer_size * stride - subs->hwptr_done; + memcpy(urb->transfer_buffer, + runtime->dma_area + subs->hwptr_done, bytes1); + memcpy(urb->transfer_buffer + bytes1, + runtime->dma_area, bytes - bytes1); + } else { + memcpy(urb->transfer_buffer, + runtime->dma_area + subs->hwptr_done, bytes); + } + subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; + runtime->delay += frames; + spin_unlock_irqrestore(&subs->lock, flags); + urb->transfer_buffer_length = bytes; + if (period_elapsed) + snd_pcm_period_elapsed(subs->pcm_substream); + return 0; +} + +/* + * process after playback data complete + * - decrease the delay count again + */ +static int retire_playback_urb(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime, + struct urb *urb) +{ + unsigned long flags; + int stride = runtime->frame_bits >> 3; + int processed = urb->transfer_buffer_length / stride; + + spin_lock_irqsave(&subs->lock, flags); + if (processed > runtime->delay) + runtime->delay = 0; + else + runtime->delay -= processed; + spin_unlock_irqrestore(&subs->lock, flags); + return 0; +} + +static const char *usb_error_string(int err) +{ + switch (err) { + case -ENODEV: + return "no device"; + case -ENOENT: + return "endpoint not enabled"; + case -EPIPE: + return "endpoint stalled"; + case -ENOSPC: + return "not enough bandwidth"; + case -ESHUTDOWN: + return "device disabled"; + case -EHOSTUNREACH: + return "device suspended"; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + return "internal error"; + default: + return "unknown error"; + } +} + +/* + * set up and start data/sync urbs + */ +static int start_urbs(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime) +{ + unsigned int i; + int err; + + if (subs->stream->chip->shutdown) + return -EBADFD; + + for (i = 0; i < subs->nurbs; i++) { + if (snd_BUG_ON(!subs->dataurb[i].urb)) + return -EINVAL; + if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) { + snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i); + goto __error; + } + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + if (snd_BUG_ON(!subs->syncurb[i].urb)) + return -EINVAL; + if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) { + snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i); + goto __error; + } + } + } + + subs->active_mask = 0; + subs->unlink_mask = 0; + subs->running = 1; + for (i = 0; i < subs->nurbs; i++) { + err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR "cannot submit datapipe " + "for urb %d, error %d: %s\n", + i, err, usb_error_string(err)); + goto __error; + } + set_bit(i, &subs->active_mask); + } + if (subs->syncpipe) { + for (i = 0; i < SYNC_URBS; i++) { + err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR "cannot submit syncpipe " + "for urb %d, error %d: %s\n", + i, err, usb_error_string(err)); + goto __error; + } + set_bit(i + 16, &subs->active_mask); + } + } + return 0; + + __error: + // snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN); + deactivate_urbs(subs, 0, 0); + return -EPIPE; +} + + +/* + */ +static struct snd_urb_ops audio_urb_ops[2] = { + { + .prepare = prepare_nodata_playback_urb, + .retire = retire_playback_urb, + .prepare_sync = prepare_playback_sync_urb, + .retire_sync = retire_playback_sync_urb, + }, + { + .prepare = prepare_capture_urb, + .retire = retire_capture_urb, + .prepare_sync = prepare_capture_sync_urb, + .retire_sync = retire_capture_sync_urb, + }, +}; + +static struct snd_urb_ops audio_urb_ops_high_speed[2] = { + { + .prepare = prepare_nodata_playback_urb, + .retire = retire_playback_urb, + .prepare_sync = prepare_playback_sync_urb_hs, + .retire_sync = retire_playback_sync_urb_hs, + }, + { + .prepare = prepare_capture_urb, + .retire = retire_capture_urb, + .prepare_sync = prepare_capture_sync_urb_hs, + .retire_sync = retire_capture_sync_urb, + }, +}; + +/* + * initialize the substream instance. + */ + +void snd_usb_init_substream(struct snd_usb_stream *as, + int stream, struct audioformat *fp) +{ + struct snd_usb_substream *subs = &as->substream[stream]; + + INIT_LIST_HEAD(&subs->fmt_list); + spin_lock_init(&subs->lock); + + subs->stream = as; + subs->direction = stream; + subs->dev = as->chip->dev; + subs->txfr_quirk = as->chip->txfr_quirk; + if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) { + subs->ops = audio_urb_ops[stream]; + } else { + subs->ops = audio_urb_ops_high_speed[stream]; + switch (as->chip->usb_id) { + case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ + case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ + case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ + subs->ops.retire_sync = retire_playback_sync_urb_hs_emu; + break; + } + } + + snd_usb_set_pcm_ops(as->pcm, stream); + + list_add_tail(&fp->list, &subs->fmt_list); + subs->formats |= 1ULL << fp->format; + subs->endpoint = fp->endpoint; + subs->num_formats++; + subs->fmt_type = fp->fmt_type; +} + +int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + subs->ops.prepare = prepare_playback_urb; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + return deactivate_urbs(subs, 0, 0); + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + subs->ops.prepare = prepare_nodata_playback_urb; + return 0; + } + + return -EINVAL; +} + +int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + subs->ops.retire = retire_capture_urb; + return start_urbs(subs, substream->runtime); + case SNDRV_PCM_TRIGGER_STOP: + return deactivate_urbs(subs, 0, 0); + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + subs->ops.retire = retire_paused_capture_urb; + return 0; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + subs->ops.retire = retire_capture_urb; + return 0; + } + + return -EINVAL; +} + +int snd_usb_substream_prepare(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime) +{ + /* clear urbs (to be sure) */ + deactivate_urbs(subs, 0, 1); + wait_clear_urbs(subs); + + /* for playback, submit the URBs now; otherwise, the first hwptr_done + * updates for all URBs would happen at the same time when starting */ + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { + subs->ops.prepare = prepare_nodata_playback_urb; + return start_urbs(subs, runtime); + } + + return 0; +} + diff --git a/sound/usb/urb.h b/sound/usb/urb.h new file mode 100644 index 00000000000..888da38079c --- /dev/null +++ b/sound/usb/urb.h @@ -0,0 +1,21 @@ +#ifndef __USBAUDIO_URB_H +#define __USBAUDIO_URB_H + +void snd_usb_init_substream(struct snd_usb_stream *as, + int stream, + struct audioformat *fp); + +int snd_usb_init_substream_urbs(struct snd_usb_substream *subs, + unsigned int period_bytes, + unsigned int rate, + unsigned int frame_bits); + +void snd_usb_release_substream_urbs(struct snd_usb_substream *subs, int force); + +int snd_usb_substream_prepare(struct snd_usb_substream *subs, + struct snd_pcm_runtime *runtime); + +int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd); +int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream, int cmd); + +#endif /* __USBAUDIO_URB_H */ diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c deleted file mode 100644 index 5b91aa02b40..00000000000 --- a/sound/usb/usbaudio.c +++ /dev/null @@ -1,4051 +0,0 @@ -/* - * (Tentative) USB Audio Driver for ALSA - * - * Main and PCM part - * - * Copyright (c) 2002 by Takashi Iwai - * - * Many codes borrowed from audio.c by - * Alan Cox (alan@lxorguk.ukuu.org.uk) - * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * 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. - * - * 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 - * - * - * NOTES: - * - * - async unlink should be used for avoiding the sleep inside lock. - * 2.4.22 usb-uhci seems buggy for async unlinking and results in - * oops. in such a cse, pass async_unlink=0 option. - * - the linked URBs would be preferred but not used so far because of - * the instability of unlinking. - * - type II is not supported properly. there is no device which supports - * this type *correctly*. SB extigy looks as if it supports, but it's - * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream). - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "usbaudio.h" -#include "usbmidi.h" -#include "usbmixer.h" - -MODULE_AUTHOR("Takashi Iwai "); -MODULE_DESCRIPTION("USB Audio"); -MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); - - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */ -/* Vendor/product IDs for this card */ -static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; -static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; -static int nrpacks = 8; /* max. number of packets per urb */ -static int async_unlink = 1; -static int device_setup[SNDRV_CARDS]; /* device parameter for this card*/ -static int ignore_ctl_error; - -module_param_array(index, int, NULL, 0444); -MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); -module_param_array(id, charp, NULL, 0444); -MODULE_PARM_DESC(id, "ID string for the USB audio adapter."); -module_param_array(enable, bool, NULL, 0444); -MODULE_PARM_DESC(enable, "Enable USB audio adapter."); -module_param_array(vid, int, NULL, 0444); -MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device."); -module_param_array(pid, int, NULL, 0444); -MODULE_PARM_DESC(pid, "Product ID for the USB audio device."); -module_param(nrpacks, int, 0644); -MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB."); -module_param(async_unlink, bool, 0444); -MODULE_PARM_DESC(async_unlink, "Use async unlink mode."); -module_param_array(device_setup, int, NULL, 0444); -MODULE_PARM_DESC(device_setup, "Specific device setup (if needed)."); -module_param(ignore_ctl_error, bool, 0444); -MODULE_PARM_DESC(ignore_ctl_error, - "Ignore errors from USB controller for mixer interfaces."); - -/* - * debug the h/w constraints - */ -/* #define HW_CONST_DEBUG */ - - -/* - * - */ - -#define MAX_PACKS 20 -#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */ -#define MAX_URBS 8 -#define SYNC_URBS 4 /* always four urbs for sync */ -#define MAX_QUEUE 24 /* try not to exceed this queue length, in ms */ - -struct audioformat { - struct list_head list; - snd_pcm_format_t format; /* format type */ - unsigned int channels; /* # channels */ - unsigned int fmt_type; /* USB audio format type (1-3) */ - unsigned int frame_size; /* samples per frame for non-audio */ - int iface; /* interface number */ - unsigned char altsetting; /* corresponding alternate setting */ - unsigned char altset_idx; /* array index of altenate setting */ - unsigned char attributes; /* corresponding attributes of cs endpoint */ - unsigned char endpoint; /* endpoint */ - unsigned char ep_attr; /* endpoint attributes */ - unsigned char datainterval; /* log_2 of data packet interval */ - unsigned int maxpacksize; /* max. packet size */ - unsigned int rates; /* rate bitmasks */ - unsigned int rate_min, rate_max; /* min/max rates */ - unsigned int nr_rates; /* number of rate table entries */ - unsigned int *rate_table; /* rate table */ -}; - -struct snd_usb_substream; - -struct snd_urb_ctx { - struct urb *urb; - unsigned int buffer_size; /* size of data buffer, if data URB */ - struct snd_usb_substream *subs; - int index; /* index for urb array */ - int packets; /* number of packets per urb */ -}; - -struct snd_urb_ops { - int (*prepare)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*retire)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*prepare_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*retire_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); -}; - -struct snd_usb_substream { - struct snd_usb_stream *stream; - struct usb_device *dev; - struct snd_pcm_substream *pcm_substream; - int direction; /* playback or capture */ - int interface; /* current interface */ - int endpoint; /* assigned endpoint */ - struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ - unsigned int cur_rate; /* current rate (for hw_params callback) */ - unsigned int period_bytes; /* current period bytes (for hw_params callback) */ - unsigned int format; /* USB data format */ - unsigned int datapipe; /* the data i/o pipe */ - unsigned int syncpipe; /* 1 - async out or adaptive in */ - unsigned int datainterval; /* log_2 of data packet interval */ - unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ - unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ - unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ - unsigned int freqmax; /* maximum sampling rate, used for buffer management */ - unsigned int phase; /* phase accumulator */ - unsigned int maxpacksize; /* max packet size in bytes */ - unsigned int maxframesize; /* max packet size in frames */ - unsigned int curpacksize; /* current packet size in bytes (for capture) */ - unsigned int curframesize; /* current packet size in frames (for capture) */ - unsigned int fill_max: 1; /* fill max packet size always */ - unsigned int txfr_quirk:1; /* allow sub-frame alignment */ - unsigned int fmt_type; /* USB audio format type (1-3) */ - - unsigned int running: 1; /* running status */ - - unsigned int hwptr_done; /* processed byte position in the buffer */ - unsigned int transfer_done; /* processed frames since last period update */ - unsigned long active_mask; /* bitmask of active urbs */ - unsigned long unlink_mask; /* bitmask of unlinked urbs */ - - unsigned int nurbs; /* # urbs */ - struct snd_urb_ctx dataurb[MAX_URBS]; /* data urb table */ - struct snd_urb_ctx syncurb[SYNC_URBS]; /* sync urb table */ - char *syncbuf; /* sync buffer for all sync URBs */ - dma_addr_t sync_dma; /* DMA address of syncbuf */ - - u64 formats; /* format bitmasks (all or'ed) */ - unsigned int num_formats; /* number of supported audio formats (list) */ - struct list_head fmt_list; /* format list */ - struct snd_pcm_hw_constraint_list rate_list; /* limited rates */ - spinlock_t lock; - - struct snd_urb_ops ops; /* callbacks (must be filled at init) */ -}; - - -struct snd_usb_stream { - struct snd_usb_audio *chip; - struct snd_pcm *pcm; - int pcm_index; - unsigned int fmt_type; /* USB audio format type (1-3) */ - struct snd_usb_substream substream[2]; - struct list_head list; -}; - - -/* - * we keep the snd_usb_audio_t instances by ourselves for merging - * the all interfaces on the same card as one sound device. - */ - -static DEFINE_MUTEX(register_mutex); -static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; - - -/* - * convert a sampling rate into our full speed format (fs/1000 in Q16.16) - * this will overflow at approx 524 kHz - */ -static inline unsigned get_usb_full_speed_rate(unsigned int rate) -{ - return ((rate << 13) + 62) / 125; -} - -/* - * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) - * this will overflow at approx 4 MHz - */ -static inline unsigned get_usb_high_speed_rate(unsigned int rate) -{ - return ((rate << 10) + 62) / 125; -} - -/* convert our full speed USB rate into sampling rate in Hz */ -static inline unsigned get_full_speed_hz(unsigned int usb_rate) -{ - return (usb_rate * 125 + (1 << 12)) >> 13; -} - -/* convert our high speed USB rate into sampling rate in Hz */ -static inline unsigned get_high_speed_hz(unsigned int usb_rate) -{ - return (usb_rate * 125 + (1 << 9)) >> 10; -} - - -/* - * prepare urb for full speed capture sync pipe - * - * fill the length and offset of each urb descriptor. - * the fixed 10.14 frequency is passed through the pipe. - */ -static int prepare_capture_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned char *cp = urb->transfer_buffer; - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 3; - urb->iso_frame_desc[0].offset = 0; - cp[0] = subs->freqn >> 2; - cp[1] = subs->freqn >> 10; - cp[2] = subs->freqn >> 18; - return 0; -} - -/* - * prepare urb for high speed capture sync pipe - * - * fill the length and offset of each urb descriptor. - * the fixed 12.13 frequency is passed as 16.16 through the pipe. - */ -static int prepare_capture_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned char *cp = urb->transfer_buffer; - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 4; - urb->iso_frame_desc[0].offset = 0; - cp[0] = subs->freqn; - cp[1] = subs->freqn >> 8; - cp[2] = subs->freqn >> 16; - cp[3] = subs->freqn >> 24; - return 0; -} - -/* - * process after capture sync complete - * - nothing to do - */ -static int retire_capture_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - return 0; -} - -/* - * prepare urb for capture data pipe - * - * fill the offset and length of each descriptor. - * - * we use a temporary buffer to write the captured data. - * since the length of written data is determined by host, we cannot - * write onto the pcm buffer directly... the data is thus copied - * later at complete callback to the global buffer. - */ -static int prepare_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - int i, offs; - struct snd_urb_ctx *ctx = urb->context; - - offs = 0; - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - for (i = 0; i < ctx->packets; i++) { - urb->iso_frame_desc[i].offset = offs; - urb->iso_frame_desc[i].length = subs->curpacksize; - offs += subs->curpacksize; - } - urb->transfer_buffer_length = offs; - urb->number_of_packets = ctx->packets; - return 0; -} - -/* - * process after capture complete - * - * copy the data from each desctiptor to the pcm buffer, and - * update the current position. - */ -static int retire_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned long flags; - unsigned char *cp; - int i; - unsigned int stride, frames, bytes, oldptr; - int period_elapsed = 0; - - stride = runtime->frame_bits >> 3; - - for (i = 0; i < urb->number_of_packets; i++) { - cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; - if (urb->iso_frame_desc[i].status) { - snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status); - // continue; - } - bytes = urb->iso_frame_desc[i].actual_length; - frames = bytes / stride; - if (!subs->txfr_quirk) - bytes = frames * stride; - if (bytes % (runtime->sample_bits >> 3) != 0) { -#ifdef CONFIG_SND_DEBUG_VERBOSE - int oldbytes = bytes; -#endif - bytes = frames * stride; - snd_printdd(KERN_ERR "Corrected urb data len. %d->%d\n", - oldbytes, bytes); - } - /* update the current pointer */ - spin_lock_irqsave(&subs->lock, flags); - oldptr = subs->hwptr_done; - subs->hwptr_done += bytes; - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; - frames = (bytes + (oldptr % stride)) / stride; - subs->transfer_done += frames; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - period_elapsed = 1; - } - spin_unlock_irqrestore(&subs->lock, flags); - /* copy a data chunk */ - if (oldptr + bytes > runtime->buffer_size * stride) { - unsigned int bytes1 = - runtime->buffer_size * stride - oldptr; - memcpy(runtime->dma_area + oldptr, cp, bytes1); - memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1); - } else { - memcpy(runtime->dma_area + oldptr, cp, bytes); - } - } - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); - return 0; -} - -/* - * Process after capture complete when paused. Nothing to do. - */ -static int retire_paused_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - return 0; -} - - -/* - * prepare urb for full speed playback sync pipe - * - * set up the offset and length to receive the current frequency. - */ - -static int prepare_playback_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 3; - urb->iso_frame_desc[0].offset = 0; - return 0; -} - -/* - * prepare urb for high speed playback sync pipe - * - * set up the offset and length to receive the current frequency. - */ - -static int prepare_playback_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 4; - urb->iso_frame_desc[0].offset = 0; - return 0; -} - -/* - * process after full speed playback sync complete - * - * retrieve the current 10.14 frequency from pipe, and set it. - * the value is referred in prepare_playback_urb(). - */ -static int retire_playback_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 3) { - f = combine_triple((u8*)urb->transfer_buffer) << 2; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* - * process after high speed playback sync complete - * - * retrieve the current 12.13 frequency from pipe, and set it. - * the value is referred in prepare_playback_urb(). - */ -static int retire_playback_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* - * process after E-Mu 0202/0404/Tracker Pre high speed playback sync complete - * - * These devices return the number of samples per packet instead of the number - * of samples per microframe. - */ -static int retire_playback_sync_urb_hs_emu(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - f >>= subs->datainterval; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* determine the number of frames in the next packet */ -static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs) -{ - if (subs->fill_max) - return subs->maxframesize; - else { - subs->phase = (subs->phase & 0xffff) - + (subs->freqm << subs->datainterval); - return min(subs->phase >> 16, subs->maxframesize); - } -} - -/* - * Prepare urb for streaming before playback starts or when paused. - * - * We don't have any data, so we send silence. - */ -static int prepare_nodata_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int i, offs, counts; - struct snd_urb_ctx *ctx = urb->context; - int stride = runtime->frame_bits >> 3; - - offs = 0; - urb->dev = ctx->subs->dev; - for (i = 0; i < ctx->packets; ++i) { - counts = snd_usb_audio_next_packet_size(subs); - urb->iso_frame_desc[i].offset = offs * stride; - urb->iso_frame_desc[i].length = counts * stride; - offs += counts; - } - urb->number_of_packets = ctx->packets; - urb->transfer_buffer_length = offs * stride; - memset(urb->transfer_buffer, - subs->cur_audiofmt->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0, - offs * stride); - return 0; -} - -/* - * prepare urb for playback data pipe - * - * Since a URB can handle only a single linear buffer, we must use double - * buffering when the data to be transferred overflows the buffer boundary. - * To avoid inconsistencies when updating hwptr_done, we use double buffering - * for all URBs. - */ -static int prepare_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - int i, stride; - unsigned int counts, frames, bytes; - unsigned long flags; - int period_elapsed = 0; - struct snd_urb_ctx *ctx = urb->context; - - stride = runtime->frame_bits >> 3; - - frames = 0; - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->number_of_packets = 0; - spin_lock_irqsave(&subs->lock, flags); - for (i = 0; i < ctx->packets; i++) { - counts = snd_usb_audio_next_packet_size(subs); - /* set up descriptor */ - urb->iso_frame_desc[i].offset = frames * stride; - urb->iso_frame_desc[i].length = counts * stride; - frames += counts; - urb->number_of_packets++; - subs->transfer_done += counts; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - period_elapsed = 1; - if (subs->fmt_type == UAC_FORMAT_TYPE_II) { - if (subs->transfer_done > 0) { - /* FIXME: fill-max mode is not - * supported yet */ - frames -= subs->transfer_done; - counts -= subs->transfer_done; - urb->iso_frame_desc[i].length = - counts * stride; - subs->transfer_done = 0; - } - i++; - if (i < ctx->packets) { - /* add a transfer delimiter */ - urb->iso_frame_desc[i].offset = - frames * stride; - urb->iso_frame_desc[i].length = 0; - urb->number_of_packets++; - } - break; - } - } - if (period_elapsed) /* finish at the period boundary */ - break; - } - bytes = frames * stride; - if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { - /* err, the transferred area goes over buffer boundary. */ - unsigned int bytes1 = - runtime->buffer_size * stride - subs->hwptr_done; - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done, bytes1); - memcpy(urb->transfer_buffer + bytes1, - runtime->dma_area, bytes - bytes1); - } else { - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done, bytes); - } - subs->hwptr_done += bytes; - if (subs->hwptr_done >= runtime->buffer_size * stride) - subs->hwptr_done -= runtime->buffer_size * stride; - runtime->delay += frames; - spin_unlock_irqrestore(&subs->lock, flags); - urb->transfer_buffer_length = bytes; - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); - return 0; -} - -/* - * process after playback data complete - * - decrease the delay count again - */ -static int retire_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned long flags; - int stride = runtime->frame_bits >> 3; - int processed = urb->transfer_buffer_length / stride; - - spin_lock_irqsave(&subs->lock, flags); - if (processed > runtime->delay) - runtime->delay = 0; - else - runtime->delay -= processed; - spin_unlock_irqrestore(&subs->lock, flags); - return 0; -} - - -/* - */ -static struct snd_urb_ops audio_urb_ops[2] = { - { - .prepare = prepare_nodata_playback_urb, - .retire = retire_playback_urb, - .prepare_sync = prepare_playback_sync_urb, - .retire_sync = retire_playback_sync_urb, - }, - { - .prepare = prepare_capture_urb, - .retire = retire_capture_urb, - .prepare_sync = prepare_capture_sync_urb, - .retire_sync = retire_capture_sync_urb, - }, -}; - -static struct snd_urb_ops audio_urb_ops_high_speed[2] = { - { - .prepare = prepare_nodata_playback_urb, - .retire = retire_playback_urb, - .prepare_sync = prepare_playback_sync_urb_hs, - .retire_sync = retire_playback_sync_urb_hs, - }, - { - .prepare = prepare_capture_urb, - .retire = retire_capture_urb, - .prepare_sync = prepare_capture_sync_urb_hs, - .retire_sync = retire_capture_sync_urb, - }, -}; - -/* - * complete callback from data urb - */ -static void snd_complete_urb(struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - struct snd_usb_substream *subs = ctx->subs; - struct snd_pcm_substream *substream = ctx->subs->pcm_substream; - int err = 0; - - if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) || - !subs->running || /* can be stopped during retire callback */ - (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 || - (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - clear_bit(ctx->index, &subs->active_mask); - if (err < 0) { - snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err); - snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - } - } -} - - -/* - * complete callback from sync urb - */ -static void snd_complete_sync_urb(struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - struct snd_usb_substream *subs = ctx->subs; - struct snd_pcm_substream *substream = ctx->subs->pcm_substream; - int err = 0; - - if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) || - !subs->running || /* can be stopped during retire callback */ - (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 || - (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - clear_bit(ctx->index + 16, &subs->active_mask); - if (err < 0) { - snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err); - snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - } - } -} - - -/* - * unlink active urbs. - */ -static int deactivate_urbs(struct snd_usb_substream *subs, int force, int can_sleep) -{ - unsigned int i; - int async; - - subs->running = 0; - - if (!force && subs->stream->chip->shutdown) /* to be sure... */ - return -EBADFD; - - async = !can_sleep && async_unlink; - - if (!async && in_interrupt()) - return 0; - - for (i = 0; i < subs->nurbs; i++) { - if (test_bit(i, &subs->active_mask)) { - if (!test_and_set_bit(i, &subs->unlink_mask)) { - struct urb *u = subs->dataurb[i].urb; - if (async) - usb_unlink_urb(u); - else - usb_kill_urb(u); - } - } - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - if (test_bit(i+16, &subs->active_mask)) { - if (!test_and_set_bit(i+16, &subs->unlink_mask)) { - struct urb *u = subs->syncurb[i].urb; - if (async) - usb_unlink_urb(u); - else - usb_kill_urb(u); - } - } - } - } - return 0; -} - - -static const char *usb_error_string(int err) -{ - switch (err) { - case -ENODEV: - return "no device"; - case -ENOENT: - return "endpoint not enabled"; - case -EPIPE: - return "endpoint stalled"; - case -ENOSPC: - return "not enough bandwidth"; - case -ESHUTDOWN: - return "device disabled"; - case -EHOSTUNREACH: - return "device suspended"; - case -EINVAL: - case -EAGAIN: - case -EFBIG: - case -EMSGSIZE: - return "internal error"; - default: - return "unknown error"; - } -} - -/* - * set up and start data/sync urbs - */ -static int start_urbs(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime) -{ - unsigned int i; - int err; - - if (subs->stream->chip->shutdown) - return -EBADFD; - - for (i = 0; i < subs->nurbs; i++) { - if (snd_BUG_ON(!subs->dataurb[i].urb)) - return -EINVAL; - if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) { - snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i); - goto __error; - } - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - if (snd_BUG_ON(!subs->syncurb[i].urb)) - return -EINVAL; - if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) { - snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i); - goto __error; - } - } - } - - subs->active_mask = 0; - subs->unlink_mask = 0; - subs->running = 1; - for (i = 0; i < subs->nurbs; i++) { - err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC); - if (err < 0) { - snd_printk(KERN_ERR "cannot submit datapipe " - "for urb %d, error %d: %s\n", - i, err, usb_error_string(err)); - goto __error; - } - set_bit(i, &subs->active_mask); - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC); - if (err < 0) { - snd_printk(KERN_ERR "cannot submit syncpipe " - "for urb %d, error %d: %s\n", - i, err, usb_error_string(err)); - goto __error; - } - set_bit(i + 16, &subs->active_mask); - } - } - return 0; - - __error: - // snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN); - deactivate_urbs(subs, 0, 0); - return -EPIPE; -} - - -/* - * wait until all urbs are processed. - */ -static int wait_clear_urbs(struct snd_usb_substream *subs) -{ - unsigned long end_time = jiffies + msecs_to_jiffies(1000); - unsigned int i; - int alive; - - do { - alive = 0; - for (i = 0; i < subs->nurbs; i++) { - if (test_bit(i, &subs->active_mask)) - alive++; - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - if (test_bit(i + 16, &subs->active_mask)) - alive++; - } - } - if (! alive) - break; - schedule_timeout_uninterruptible(1); - } while (time_before(jiffies, end_time)); - if (alive) - snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); - return 0; -} - - -/* - * return the current pcm pointer. just based on the hwptr_done value. - */ -static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_usb_substream *subs; - unsigned int hwptr_done; - - subs = (struct snd_usb_substream *)substream->runtime->private_data; - spin_lock(&subs->lock); - hwptr_done = subs->hwptr_done; - spin_unlock(&subs->lock); - return hwptr_done / (substream->runtime->frame_bits >> 3); -} - - -/* - * start/stop playback substream - */ -static int snd_usb_pcm_playback_trigger(struct snd_pcm_substream *substream, - int cmd) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->ops.prepare = prepare_playback_urb; - return 0; - case SNDRV_PCM_TRIGGER_STOP: - return deactivate_urbs(subs, 0, 0); - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->ops.prepare = prepare_nodata_playback_urb; - return 0; - default: - return -EINVAL; - } -} - -/* - * start/stop capture substream - */ -static int snd_usb_pcm_capture_trigger(struct snd_pcm_substream *substream, - int cmd) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - subs->ops.retire = retire_capture_urb; - return start_urbs(subs, substream->runtime); - case SNDRV_PCM_TRIGGER_STOP: - return deactivate_urbs(subs, 0, 0); - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->ops.retire = retire_paused_capture_urb; - return 0; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->ops.retire = retire_capture_urb; - return 0; - default: - return -EINVAL; - } -} - - -/* - * release a urb data - */ -static void release_urb_ctx(struct snd_urb_ctx *u) -{ - if (u->urb) { - if (u->buffer_size) - usb_buffer_free(u->subs->dev, u->buffer_size, - u->urb->transfer_buffer, - u->urb->transfer_dma); - usb_free_urb(u->urb); - u->urb = NULL; - } -} - -/* - * release a substream - */ -static void release_substream_urbs(struct snd_usb_substream *subs, int force) -{ - int i; - - /* stop urbs (to be sure) */ - deactivate_urbs(subs, force, 1); - wait_clear_urbs(subs); - - for (i = 0; i < MAX_URBS; i++) - release_urb_ctx(&subs->dataurb[i]); - for (i = 0; i < SYNC_URBS; i++) - release_urb_ctx(&subs->syncurb[i]); - usb_buffer_free(subs->dev, SYNC_URBS * 4, - subs->syncbuf, subs->sync_dma); - subs->syncbuf = NULL; - subs->nurbs = 0; -} - -/* - * initialize a substream for plaback/capture - */ -static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int period_bytes, - unsigned int rate, unsigned int frame_bits) -{ - unsigned int maxsize, i; - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - unsigned int urb_packs, total_packs, packs_per_ms; - - /* calculate the frequency in 16.16 format */ - if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) - subs->freqn = get_usb_full_speed_rate(rate); - else - subs->freqn = get_usb_high_speed_rate(rate); - subs->freqm = subs->freqn; - /* calculate max. frequency */ - if (subs->maxpacksize) { - /* whatever fits into a max. size packet */ - maxsize = subs->maxpacksize; - subs->freqmax = (maxsize / (frame_bits >> 3)) - << (16 - subs->datainterval); - } else { - /* no max. packet size: just take 25% higher than nominal */ - subs->freqmax = subs->freqn + (subs->freqn >> 2); - maxsize = ((subs->freqmax + 0xffff) * (frame_bits >> 3)) - >> (16 - subs->datainterval); - } - subs->phase = 0; - - if (subs->fill_max) - subs->curpacksize = subs->maxpacksize; - else - subs->curpacksize = maxsize; - - if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) - packs_per_ms = 8 >> subs->datainterval; - else - packs_per_ms = 1; - - if (is_playback) { - urb_packs = max(nrpacks, 1); - urb_packs = min(urb_packs, (unsigned int)MAX_PACKS); - } else - urb_packs = 1; - urb_packs *= packs_per_ms; - if (subs->syncpipe) - urb_packs = min(urb_packs, 1U << subs->syncinterval); - - /* decide how many packets to be used */ - if (is_playback) { - unsigned int minsize, maxpacks; - /* determine how small a packet can be */ - minsize = (subs->freqn >> (16 - subs->datainterval)) - * (frame_bits >> 3); - /* with sync from device, assume it can be 12% lower */ - if (subs->syncpipe) - minsize -= minsize >> 3; - minsize = max(minsize, 1u); - total_packs = (period_bytes + minsize - 1) / minsize; - /* we need at least two URBs for queueing */ - if (total_packs < 2) { - total_packs = 2; - } else { - /* and we don't want too long a queue either */ - maxpacks = max(MAX_QUEUE * packs_per_ms, urb_packs * 2); - total_packs = min(total_packs, maxpacks); - } - } else { - while (urb_packs > 1 && urb_packs * maxsize >= period_bytes) - urb_packs >>= 1; - total_packs = MAX_URBS * urb_packs; - } - subs->nurbs = (total_packs + urb_packs - 1) / urb_packs; - if (subs->nurbs > MAX_URBS) { - /* too much... */ - subs->nurbs = MAX_URBS; - total_packs = MAX_URBS * urb_packs; - } else if (subs->nurbs < 2) { - /* too little - we need at least two packets - * to ensure contiguous playback/capture - */ - subs->nurbs = 2; - } - - /* allocate and initialize data urbs */ - for (i = 0; i < subs->nurbs; i++) { - struct snd_urb_ctx *u = &subs->dataurb[i]; - u->index = i; - u->subs = subs; - u->packets = (i + 1) * total_packs / subs->nurbs - - i * total_packs / subs->nurbs; - u->buffer_size = maxsize * u->packets; - if (subs->fmt_type == UAC_FORMAT_TYPE_II) - u->packets++; /* for transfer delimiter */ - u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); - if (!u->urb) - goto out_of_memory; - u->urb->transfer_buffer = - usb_buffer_alloc(subs->dev, u->buffer_size, GFP_KERNEL, - &u->urb->transfer_dma); - if (!u->urb->transfer_buffer) - goto out_of_memory; - u->urb->pipe = subs->datapipe; - u->urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; - u->urb->interval = 1 << subs->datainterval; - u->urb->context = u; - u->urb->complete = snd_complete_urb; - } - - if (subs->syncpipe) { - /* allocate and initialize sync urbs */ - subs->syncbuf = usb_buffer_alloc(subs->dev, SYNC_URBS * 4, - GFP_KERNEL, &subs->sync_dma); - if (!subs->syncbuf) - goto out_of_memory; - for (i = 0; i < SYNC_URBS; i++) { - struct snd_urb_ctx *u = &subs->syncurb[i]; - u->index = i; - u->subs = subs; - u->packets = 1; - u->urb = usb_alloc_urb(1, GFP_KERNEL); - if (!u->urb) - goto out_of_memory; - u->urb->transfer_buffer = subs->syncbuf + i * 4; - u->urb->transfer_dma = subs->sync_dma + i * 4; - u->urb->transfer_buffer_length = 4; - u->urb->pipe = subs->syncpipe; - u->urb->transfer_flags = URB_ISO_ASAP | - URB_NO_TRANSFER_DMA_MAP; - u->urb->number_of_packets = 1; - u->urb->interval = 1 << subs->syncinterval; - u->urb->context = u; - u->urb->complete = snd_complete_sync_urb; - } - } - return 0; - -out_of_memory: - release_substream_urbs(subs, 0); - return -ENOMEM; -} - - -/* - * find a matching audio format - */ -static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned int format, - unsigned int rate, unsigned int channels) -{ - struct list_head *p; - struct audioformat *found = NULL; - int cur_attr = 0, attr; - - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (fp->format != format || fp->channels != channels) - continue; - if (rate < fp->rate_min || rate > fp->rate_max) - continue; - if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { - unsigned int i; - for (i = 0; i < fp->nr_rates; i++) - if (fp->rate_table[i] == rate) - break; - if (i >= fp->nr_rates) - continue; - } - attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE; - if (! found) { - found = fp; - cur_attr = attr; - continue; - } - /* avoid async out and adaptive in if the other method - * supports the same format. - * this is a workaround for the case like - * M-audio audiophile USB. - */ - if (attr != cur_attr) { - if ((attr == USB_ENDPOINT_SYNC_ASYNC && - subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || - (attr == USB_ENDPOINT_SYNC_ADAPTIVE && - subs->direction == SNDRV_PCM_STREAM_CAPTURE)) - continue; - if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC && - subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || - (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE && - subs->direction == SNDRV_PCM_STREAM_CAPTURE)) { - found = fp; - cur_attr = attr; - continue; - } - } - /* find the format with the largest max. packet size */ - if (fp->maxpacksize > found->maxpacksize) { - found = fp; - cur_attr = attr; - } - } - return found; -} - - -/* - * initialize the picth control and sample rate - */ -static int init_usb_pitch(struct usb_device *dev, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt) -{ - unsigned int ep; - unsigned char data[1]; - int err; - - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has pitch control, enable it */ - if (fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL) { - data[0] = 1; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", - dev->devnum, iface, ep); - return err; - } - } - return 0; -} - -static int init_usb_sample_rate(struct usb_device *dev, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) -{ - unsigned int ep; - unsigned char data[3]; - int err; - - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has sampling rate control, set it */ - if (fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE) { - int crate; - data[0] = rate; - data[1] = rate >> 8; - data[2] = rate >> 16; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", - dev->devnum, iface, fmt->altsetting, rate, ep); - return err; - } - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", - dev->devnum, iface, fmt->altsetting, ep); - return 0; /* some devices don't support reading */ - } - crate = data[0] | (data[1] << 8) | (data[2] << 16); - if (crate != rate) { - snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); - // runtime->rate = crate; - } - } - return 0; -} - -/* - * For E-Mu 0404USB/0202USB/TrackerPre sample rate should be set for device, - * not for interface. - */ -static void set_format_emu_quirk(struct snd_usb_substream *subs, - struct audioformat *fmt) -{ - unsigned char emu_samplerate_id = 0; - - /* When capture is active - * sample rate shouldn't be changed - * by playback substream - */ - if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { - if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1) - return; - } - - switch (fmt->rate_min) { - case 48000: - emu_samplerate_id = EMU_QUIRK_SR_48000HZ; - break; - case 88200: - emu_samplerate_id = EMU_QUIRK_SR_88200HZ; - break; - case 96000: - emu_samplerate_id = EMU_QUIRK_SR_96000HZ; - break; - case 176400: - emu_samplerate_id = EMU_QUIRK_SR_176400HZ; - break; - case 192000: - emu_samplerate_id = EMU_QUIRK_SR_192000HZ; - break; - default: - emu_samplerate_id = EMU_QUIRK_SR_44100HZ; - break; - } - snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id); -} - -/* - * find a matching format and set up the interface - */ -static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) -{ - struct usb_device *dev = subs->dev; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct usb_interface *iface; - unsigned int ep, attr; - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - int err; - - iface = usb_ifnum_to_if(dev, fmt->iface); - if (WARN_ON(!iface)) - return -EINVAL; - alts = &iface->altsetting[fmt->altset_idx]; - altsd = get_iface_desc(alts); - if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting)) - return -EINVAL; - - if (fmt == subs->cur_audiofmt) - return 0; - - /* close the old interface */ - if (subs->interface >= 0 && subs->interface != fmt->iface) { - if (usb_set_interface(subs->dev, subs->interface, 0) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: return to setting 0 failed\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EIO; - } - subs->interface = -1; - subs->format = 0; - } - - /* set interface */ - if (subs->interface != fmt->iface || subs->format != fmt->altset_idx) { - if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EIO; - } - snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting); - subs->interface = fmt->iface; - subs->format = fmt->altset_idx; - } - - /* create a data pipe */ - ep = fmt->endpoint & USB_ENDPOINT_NUMBER_MASK; - if (is_playback) - subs->datapipe = usb_sndisocpipe(dev, ep); - else - subs->datapipe = usb_rcvisocpipe(dev, ep); - subs->datainterval = fmt->datainterval; - subs->syncpipe = subs->syncinterval = 0; - subs->maxpacksize = fmt->maxpacksize; - subs->fill_max = 0; - - /* we need a sync pipe in async OUT or adaptive IN mode */ - /* check the number of EP, since some devices have broken - * descriptors which fool us. if it has only one EP, - * assume it as adaptive-out or sync-in. - */ - attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; - if (((is_playback && attr == USB_ENDPOINT_SYNC_ASYNC) || - (! is_playback && attr == USB_ENDPOINT_SYNC_ADAPTIVE)) && - altsd->bNumEndpoints >= 2) { - /* check sync-pipe endpoint */ - /* ... and check descriptor size before accessing bSynchAddress - because there is a version of the SB Audigy 2 NX firmware lacking - the audio fields in the endpoint descriptors */ - if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != 0x01 || - (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bSynchAddress != 0)) { - snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EINVAL; - } - ep = get_endpoint(alts, 1)->bEndpointAddress; - if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - (( is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || - (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { - snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EINVAL; - } - ep &= USB_ENDPOINT_NUMBER_MASK; - if (is_playback) - subs->syncpipe = usb_rcvisocpipe(dev, ep); - else - subs->syncpipe = usb_sndisocpipe(dev, ep); - if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bRefresh >= 1 && - get_endpoint(alts, 1)->bRefresh <= 9) - subs->syncinterval = get_endpoint(alts, 1)->bRefresh; - else if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) - subs->syncinterval = 1; - else if (get_endpoint(alts, 1)->bInterval >= 1 && - get_endpoint(alts, 1)->bInterval <= 16) - subs->syncinterval = get_endpoint(alts, 1)->bInterval - 1; - else - subs->syncinterval = 3; - } - - /* always fill max packet size */ - if (fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX) - subs->fill_max = 1; - - if ((err = init_usb_pitch(dev, subs->interface, alts, fmt)) < 0) - return err; - - subs->cur_audiofmt = fmt; - - switch (subs->stream->chip->usb_id) { - case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ - case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ - case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ - set_format_emu_quirk(subs, fmt); - break; - } - -#if 0 - printk(KERN_DEBUG - "setting done: format = %d, rate = %d..%d, channels = %d\n", - fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels); - printk(KERN_DEBUG - " datapipe = 0x%0x, syncpipe = 0x%0x\n", - subs->datapipe, subs->syncpipe); -#endif - - return 0; -} - -/* - * hw_params callback - * - * allocate a buffer and set the given audio format. - * - * so far we use a physically linear buffer although packetize transfer - * doesn't need a continuous area. - * if sg buffer is supported on the later version of alsa, we'll follow - * that. - */ -static int snd_usb_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - struct audioformat *fmt; - unsigned int channels, rate, format; - int ret, changed; - - ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) - return ret; - - format = params_format(hw_params); - rate = params_rate(hw_params); - channels = params_channels(hw_params); - fmt = find_format(subs, format, rate, channels); - if (!fmt) { - snd_printd(KERN_DEBUG "cannot set format: format = %#x, rate = %d, channels = %d\n", - format, rate, channels); - return -EINVAL; - } - - changed = subs->cur_audiofmt != fmt || - subs->period_bytes != params_period_bytes(hw_params) || - subs->cur_rate != rate; - if ((ret = set_format(subs, fmt)) < 0) - return ret; - - if (subs->cur_rate != rate) { - struct usb_host_interface *alts; - struct usb_interface *iface; - iface = usb_ifnum_to_if(subs->dev, fmt->iface); - alts = &iface->altsetting[fmt->altset_idx]; - ret = init_usb_sample_rate(subs->dev, subs->interface, alts, fmt, rate); - if (ret < 0) - return ret; - subs->cur_rate = rate; - } - - if (changed) { - /* format changed */ - release_substream_urbs(subs, 0); - /* influenced: period_bytes, channels, rate, format, */ - ret = init_substream_urbs(subs, params_period_bytes(hw_params), - params_rate(hw_params), - snd_pcm_format_physical_width(params_format(hw_params)) * params_channels(hw_params)); - } - - return ret; -} - -/* - * hw_free callback - * - * reset the audio format and release the buffer - */ -static int snd_usb_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - - subs->cur_audiofmt = NULL; - subs->cur_rate = 0; - subs->period_bytes = 0; - if (!subs->stream->chip->shutdown) - release_substream_urbs(subs, 0); - return snd_pcm_lib_free_vmalloc_buffer(substream); -} - -/* - * prepare callback - * - * only a few subtle things... - */ -static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usb_substream *subs = runtime->private_data; - - if (! subs->cur_audiofmt) { - snd_printk(KERN_ERR "usbaudio: no format is specified!\n"); - return -ENXIO; - } - - /* some unit conversions in runtime */ - subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize); - subs->curframesize = bytes_to_frames(runtime, subs->curpacksize); - - /* reset the pointer */ - subs->hwptr_done = 0; - subs->transfer_done = 0; - subs->phase = 0; - runtime->delay = 0; - - /* clear urbs (to be sure) */ - deactivate_urbs(subs, 0, 1); - wait_clear_urbs(subs); - - /* for playback, submit the URBs now; otherwise, the first hwptr_done - * updates for all URBs would happen at the same time when starting */ - if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { - subs->ops.prepare = prepare_nodata_playback_urb; - return start_urbs(subs, runtime); - } else - return 0; -} - -static struct snd_pcm_hardware snd_usb_hardware = -{ - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_PAUSE, - .buffer_bytes_max = 1024 * 1024, - .period_bytes_min = 64, - .period_bytes_max = 512 * 1024, - .periods_min = 2, - .periods_max = 1024, -}; - -/* - * h/w constraints - */ - -#ifdef HW_CONST_DEBUG -#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args) -#else -#define hwc_debug(fmt, args...) /**/ -#endif - -static int hw_check_valid_format(struct snd_usb_substream *subs, - struct snd_pcm_hw_params *params, - struct audioformat *fp) -{ - struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); - struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); - unsigned int ptime; - - /* check the format */ - if (!snd_mask_test(fmts, fp->format)) { - hwc_debug(" > check: no supported format %d\n", fp->format); - return 0; - } - /* check the channels */ - if (fp->channels < ct->min || fp->channels > ct->max) { - hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max); - return 0; - } - /* check the rate is within the range */ - if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) { - hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max); - return 0; - } - if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) { - hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min); - return 0; - } - /* check whether the period time is >= the data packet interval */ - if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) { - ptime = 125 * (1 << fp->datainterval); - if (ptime > pt->max || (ptime == pt->max && pt->openmax)) { - hwc_debug(" > check: ptime %u > max %u\n", ptime, pt->max); - return 0; - } - } - return 1; -} - -static int hw_rule_rate(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - unsigned int rmin, rmax; - int changed; - - hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max); - changed = 0; - rmin = rmax = 0; - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (!hw_check_valid_format(subs, params, fp)) - continue; - if (changed++) { - if (rmin > fp->rate_min) - rmin = fp->rate_min; - if (rmax < fp->rate_max) - rmax = fp->rate_max; - } else { - rmin = fp->rate_min; - rmax = fp->rate_max; - } - } - - if (!changed) { - hwc_debug(" --> get empty\n"); - it->empty = 1; - return -EINVAL; - } - - changed = 0; - if (it->min < rmin) { - it->min = rmin; - it->openmin = 0; - changed = 1; - } - if (it->max > rmax) { - it->max = rmax; - it->openmax = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); - return changed; -} - - -static int hw_rule_channels(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - unsigned int rmin, rmax; - int changed; - - hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max); - changed = 0; - rmin = rmax = 0; - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (!hw_check_valid_format(subs, params, fp)) - continue; - if (changed++) { - if (rmin > fp->channels) - rmin = fp->channels; - if (rmax < fp->channels) - rmax = fp->channels; - } else { - rmin = fp->channels; - rmax = fp->channels; - } - } - - if (!changed) { - hwc_debug(" --> get empty\n"); - it->empty = 1; - return -EINVAL; - } - - changed = 0; - if (it->min < rmin) { - it->min = rmin; - it->openmin = 0; - changed = 1; - } - if (it->max > rmax) { - it->max = rmax; - it->openmax = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); - return changed; -} - -static int hw_rule_format(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); - u64 fbits; - u32 oldbits[2]; - int changed; - - hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]); - fbits = 0; - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (!hw_check_valid_format(subs, params, fp)) - continue; - fbits |= (1ULL << fp->format); - } - - oldbits[0] = fmt->bits[0]; - oldbits[1] = fmt->bits[1]; - fmt->bits[0] &= (u32)fbits; - fmt->bits[1] &= (u32)(fbits >> 32); - if (!fmt->bits[0] && !fmt->bits[1]) { - hwc_debug(" --> get empty\n"); - return -EINVAL; - } - changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); - hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); - return changed; -} - -static int hw_rule_period_time(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct audioformat *fp; - struct snd_interval *it; - unsigned char min_datainterval; - unsigned int pmin; - int changed; - - it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); - hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max); - min_datainterval = 0xff; - list_for_each_entry(fp, &subs->fmt_list, list) { - if (!hw_check_valid_format(subs, params, fp)) - continue; - min_datainterval = min(min_datainterval, fp->datainterval); - } - if (min_datainterval == 0xff) { - hwc_debug(" --> get emtpy\n"); - it->empty = 1; - return -EINVAL; - } - pmin = 125 * (1 << min_datainterval); - changed = 0; - if (it->min < pmin) { - it->min = pmin; - it->openmin = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%u,%u) (changed = %d)\n", it->min, it->max, changed); - return changed; -} - -/* - * If the device supports unusual bit rates, does the request meet these? - */ -static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime, - struct snd_usb_substream *subs) -{ - struct audioformat *fp; - int count = 0, needs_knot = 0; - int err; - - list_for_each_entry(fp, &subs->fmt_list, list) { - if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) - return 0; - count += fp->nr_rates; - if (fp->rates & SNDRV_PCM_RATE_KNOT) - needs_knot = 1; - } - if (!needs_knot) - return 0; - - subs->rate_list.count = count; - subs->rate_list.list = kmalloc(sizeof(int) * count, GFP_KERNEL); - subs->rate_list.mask = 0; - count = 0; - list_for_each_entry(fp, &subs->fmt_list, list) { - int i; - for (i = 0; i < fp->nr_rates; i++) - subs->rate_list.list[count++] = fp->rate_table[i]; - } - err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - &subs->rate_list); - if (err < 0) - return err; - - return 0; -} - - -/* - * set up the runtime hardware information. - */ - -static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs) -{ - struct list_head *p; - unsigned int pt, ptmin; - int param_period_time_if_needed; - int err; - - runtime->hw.formats = subs->formats; - - runtime->hw.rate_min = 0x7fffffff; - runtime->hw.rate_max = 0; - runtime->hw.channels_min = 256; - runtime->hw.channels_max = 0; - runtime->hw.rates = 0; - ptmin = UINT_MAX; - /* check min/max rates and channels */ - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - runtime->hw.rates |= fp->rates; - if (runtime->hw.rate_min > fp->rate_min) - runtime->hw.rate_min = fp->rate_min; - if (runtime->hw.rate_max < fp->rate_max) - runtime->hw.rate_max = fp->rate_max; - if (runtime->hw.channels_min > fp->channels) - runtime->hw.channels_min = fp->channels; - if (runtime->hw.channels_max < fp->channels) - runtime->hw.channels_max = fp->channels; - if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) { - /* FIXME: there might be more than one audio formats... */ - runtime->hw.period_bytes_min = runtime->hw.period_bytes_max = - fp->frame_size; - } - pt = 125 * (1 << fp->datainterval); - ptmin = min(ptmin, pt); - } - - param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME; - if (snd_usb_get_speed(subs->dev) != USB_SPEED_HIGH) - /* full speed devices have fixed data packet interval */ - ptmin = 1000; - if (ptmin == 1000) - /* if period time doesn't go below 1 ms, no rules needed */ - param_period_time_if_needed = -1; - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - ptmin, UINT_MAX); - - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - hw_rule_rate, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_CHANNELS, - param_period_time_if_needed, - -1)) < 0) - return err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - hw_rule_channels, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_RATE, - param_period_time_if_needed, - -1)) < 0) - return err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, - hw_rule_format, subs, - SNDRV_PCM_HW_PARAM_RATE, - SNDRV_PCM_HW_PARAM_CHANNELS, - param_period_time_if_needed, - -1)) < 0) - return err; - if (param_period_time_if_needed >= 0) { - err = snd_pcm_hw_rule_add(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - hw_rule_period_time, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_CHANNELS, - SNDRV_PCM_HW_PARAM_RATE, - -1); - if (err < 0) - return err; - } - if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0) - return err; - return 0; -} - -static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) -{ - struct snd_usb_stream *as = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_usb_substream *subs = &as->substream[direction]; - - subs->interface = -1; - subs->format = 0; - runtime->hw = snd_usb_hardware; - runtime->private_data = subs; - subs->pcm_substream = substream; - return setup_hw_info(runtime, subs); -} - -static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) -{ - struct snd_usb_stream *as = snd_pcm_substream_chip(substream); - struct snd_usb_substream *subs = &as->substream[direction]; - - if (!as->chip->shutdown && subs->interface >= 0) { - usb_set_interface(subs->dev, subs->interface, 0); - subs->interface = -1; - } - subs->pcm_substream = NULL; - return 0; -} - -static int snd_usb_playback_open(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK); -} - -static int snd_usb_playback_close(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK); -} - -static int snd_usb_capture_open(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE); -} - -static int snd_usb_capture_close(struct snd_pcm_substream *substream) -{ - return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE); -} - -static struct snd_pcm_ops snd_usb_playback_ops = { - .open = snd_usb_playback_open, - .close = snd_usb_playback_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, - .prepare = snd_usb_pcm_prepare, - .trigger = snd_usb_pcm_playback_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, -}; - -static struct snd_pcm_ops snd_usb_capture_ops = { - .open = snd_usb_capture_open, - .close = snd_usb_capture_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_usb_hw_params, - .hw_free = snd_usb_hw_free, - .prepare = snd_usb_pcm_prepare, - .trigger = snd_usb_pcm_capture_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, - .mmap = snd_pcm_lib_mmap_vmalloc, -}; - - - -/* - * helper functions - */ - -/* - * combine bytes and get an integer value - */ -unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size) -{ - switch (size) { - case 1: return *bytes; - case 2: return combine_word(bytes); - case 3: return combine_triple(bytes); - case 4: return combine_quad(bytes); - default: return 0; - } -} - -/* - * parse descriptor buffer and return the pointer starting the given - * descriptor type. - */ -void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype) -{ - u8 *p, *end, *next; - - p = descstart; - end = p + desclen; - for (; p < end;) { - if (p[0] < 2) - return NULL; - next = p + p[0]; - if (next > end) - return NULL; - if (p[1] == dtype && (!after || (void *)p > after)) { - return p; - } - p = next; - } - return NULL; -} - -/* - * find a class-specified interface descriptor with the given subtype. - */ -void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype) -{ - unsigned char *p = after; - - while ((p = snd_usb_find_desc(buffer, buflen, p, - USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 3 && p[2] == dsubtype) - return p; - } - return NULL; -} - -/* - * Wrapper for usb_control_msg(). - * Allocates a temp buffer to prevent dmaing from/to the stack. - */ -int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, - __u8 requesttype, __u16 value, __u16 index, void *data, - __u16 size, int timeout) -{ - int err; - void *buf = NULL; - - if (size > 0) { - buf = kmemdup(data, size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - } - err = usb_control_msg(dev, pipe, request, requesttype, - value, index, buf, size, timeout); - if (size > 0) { - memcpy(data, buf, size); - kfree(buf); - } - return err; -} - - -/* - * entry point for linux usb interface - */ - -static int usb_audio_probe(struct usb_interface *intf, - const struct usb_device_id *id); -static void usb_audio_disconnect(struct usb_interface *intf); - -#ifdef CONFIG_PM -static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message); -static int usb_audio_resume(struct usb_interface *intf); -#else -#define usb_audio_suspend NULL -#define usb_audio_resume NULL -#endif - -static struct usb_device_id usb_audio_ids [] = { -#include "usbquirks.h" - { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, - { } /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE (usb, usb_audio_ids); - -static struct usb_driver usb_audio_driver = { - .name = "snd-usb-audio", - .probe = usb_audio_probe, - .disconnect = usb_audio_disconnect, - .suspend = usb_audio_suspend, - .resume = usb_audio_resume, - .id_table = usb_audio_ids, -}; - - -#if defined(CONFIG_PROC_FS) && defined(CONFIG_SND_VERBOSE_PROCFS) - -/* - * proc interface for list the supported pcm formats - */ -static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) -{ - struct list_head *p; - static char *sync_types[4] = { - "NONE", "ASYNC", "ADAPTIVE", "SYNC" - }; - - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - snd_iprintf(buffer, " Interface %d\n", fp->iface); - snd_iprintf(buffer, " Altset %d\n", fp->altsetting); - snd_iprintf(buffer, " Format: %s\n", - snd_pcm_format_name(fp->format)); - snd_iprintf(buffer, " Channels: %d\n", fp->channels); - snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", - fp->endpoint & USB_ENDPOINT_NUMBER_MASK, - fp->endpoint & USB_DIR_IN ? "IN" : "OUT", - sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]); - if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { - snd_iprintf(buffer, " Rates: %d - %d (continuous)\n", - fp->rate_min, fp->rate_max); - } else { - unsigned int i; - snd_iprintf(buffer, " Rates: "); - for (i = 0; i < fp->nr_rates; i++) { - if (i > 0) - snd_iprintf(buffer, ", "); - snd_iprintf(buffer, "%d", fp->rate_table[i]); - } - snd_iprintf(buffer, "\n"); - } - if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) - snd_iprintf(buffer, " Data packet interval: %d us\n", - 125 * (1 << fp->datainterval)); - // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize); - // snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes); - } -} - -static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) -{ - if (subs->running) { - unsigned int i; - snd_iprintf(buffer, " Status: Running\n"); - snd_iprintf(buffer, " Interface = %d\n", subs->interface); - snd_iprintf(buffer, " Altset = %d\n", subs->format); - snd_iprintf(buffer, " URBs = %d [ ", subs->nurbs); - for (i = 0; i < subs->nurbs; i++) - snd_iprintf(buffer, "%d ", subs->dataurb[i].packets); - snd_iprintf(buffer, "]\n"); - snd_iprintf(buffer, " Packet Size = %d\n", subs->curpacksize); - snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n", - snd_usb_get_speed(subs->dev) == USB_SPEED_FULL - ? get_full_speed_hz(subs->freqm) - : get_high_speed_hz(subs->freqm), - subs->freqm >> 16, subs->freqm & 0xffff); - } else { - snd_iprintf(buffer, " Status: Stop\n"); - } -} - -static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) -{ - struct snd_usb_stream *stream = entry->private_data; - - snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name); - - if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) { - snd_iprintf(buffer, "\nPlayback:\n"); - proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); - proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); - } - if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) { - snd_iprintf(buffer, "\nCapture:\n"); - proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); - proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); - } -} - -static void proc_pcm_format_add(struct snd_usb_stream *stream) -{ - struct snd_info_entry *entry; - char name[32]; - struct snd_card *card = stream->chip->card; - - sprintf(name, "stream%d", stream->pcm_index); - if (!snd_card_proc_new(card, name, &entry)) - snd_info_set_text_ops(entry, stream, proc_pcm_format_read); -} - -#else - -static inline void proc_pcm_format_add(struct snd_usb_stream *stream) -{ -} - -#endif - -/* - * initialize the substream instance. - */ - -static void init_substream(struct snd_usb_stream *as, int stream, struct audioformat *fp) -{ - struct snd_usb_substream *subs = &as->substream[stream]; - - INIT_LIST_HEAD(&subs->fmt_list); - spin_lock_init(&subs->lock); - - subs->stream = as; - subs->direction = stream; - subs->dev = as->chip->dev; - subs->txfr_quirk = as->chip->txfr_quirk; - if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) { - subs->ops = audio_urb_ops[stream]; - } else { - subs->ops = audio_urb_ops_high_speed[stream]; - switch (as->chip->usb_id) { - case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ - case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ - case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ - subs->ops.retire_sync = retire_playback_sync_urb_hs_emu; - break; - } - } - snd_pcm_set_ops(as->pcm, stream, - stream == SNDRV_PCM_STREAM_PLAYBACK ? - &snd_usb_playback_ops : &snd_usb_capture_ops); - - list_add_tail(&fp->list, &subs->fmt_list); - subs->formats |= 1ULL << fp->format; - subs->endpoint = fp->endpoint; - subs->num_formats++; - subs->fmt_type = fp->fmt_type; -} - - -/* - * free a substream - */ -static void free_substream(struct snd_usb_substream *subs) -{ - struct list_head *p, *n; - - if (!subs->num_formats) - return; /* not initialized */ - list_for_each_safe(p, n, &subs->fmt_list) { - struct audioformat *fp = list_entry(p, struct audioformat, list); - kfree(fp->rate_table); - kfree(fp); - } - kfree(subs->rate_list.list); -} - - -/* - * free a usb stream instance - */ -static void snd_usb_audio_stream_free(struct snd_usb_stream *stream) -{ - free_substream(&stream->substream[0]); - free_substream(&stream->substream[1]); - list_del(&stream->list); - kfree(stream); -} - -static void snd_usb_audio_pcm_free(struct snd_pcm *pcm) -{ - struct snd_usb_stream *stream = pcm->private_data; - if (stream) { - stream->pcm = NULL; - snd_usb_audio_stream_free(stream); - } -} - - -/* - * add this endpoint to the chip instance. - * if a stream with the same endpoint already exists, append to it. - * if not, create a new pcm stream. - */ -static int add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct audioformat *fp) -{ - struct list_head *p; - struct snd_usb_stream *as; - struct snd_usb_substream *subs; - struct snd_pcm *pcm; - int err; - - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - if (as->fmt_type != fp->fmt_type) - continue; - subs = &as->substream[stream]; - if (!subs->endpoint) - continue; - if (subs->endpoint == fp->endpoint) { - list_add_tail(&fp->list, &subs->fmt_list); - subs->num_formats++; - subs->formats |= 1ULL << fp->format; - return 0; - } - } - /* look for an empty stream */ - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - if (as->fmt_type != fp->fmt_type) - continue; - subs = &as->substream[stream]; - if (subs->endpoint) - continue; - err = snd_pcm_new_stream(as->pcm, stream, 1); - if (err < 0) - return err; - init_substream(as, stream, fp); - return 0; - } - - /* create a new pcm */ - as = kzalloc(sizeof(*as), GFP_KERNEL); - if (!as) - return -ENOMEM; - as->pcm_index = chip->pcm_devs; - as->chip = chip; - as->fmt_type = fp->fmt_type; - err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs, - stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0, - stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1, - &pcm); - if (err < 0) { - kfree(as); - return err; - } - as->pcm = pcm; - pcm->private_data = as; - pcm->private_free = snd_usb_audio_pcm_free; - pcm->info_flags = 0; - if (chip->pcm_devs > 0) - sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs); - else - strcpy(pcm->name, "USB Audio"); - - init_substream(as, stream, fp); - - list_add(&as->list, &chip->pcm_list); - chip->pcm_devs++; - - proc_pcm_format_add(as); - - return 0; -} - - -/* - * check if the device uses big-endian samples - */ -static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp) -{ - switch (chip->usb_id) { - case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */ - if (fp->endpoint & USB_DIR_IN) - return 1; - break; - case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - if (device_setup[chip->index] == 0x00 || - fp->altsetting==1 || fp->altsetting==2 || fp->altsetting==3) - return 1; - } - return 0; -} - -/* - * parse the audio format type I descriptor - * and returns the corresponding pcm format - * - * @dev: usb device - * @fp: audioformat record - * @format: the format tag (wFormatTag) - * @fmt: the format type descriptor - */ -static int parse_audio_format_i_type(struct snd_usb_audio *chip, - struct audioformat *fp, - int format, void *_fmt, - int protocol) -{ - int pcm_format, i; - int sample_width, sample_bytes; - - switch (protocol) { - case UAC_VERSION_1: { - struct uac_format_type_i_discrete_descriptor *fmt = _fmt; - sample_width = fmt->bBitResolution; - sample_bytes = fmt->bSubframeSize; - break; - } - - case UAC_VERSION_2: { - struct uac_format_type_i_ext_descriptor *fmt = _fmt; - sample_width = fmt->bBitResolution; - sample_bytes = fmt->bSubslotSize; - - /* - * FIXME - * USB audio class v2 devices specify a bitmap of possible - * audio formats rather than one fix value. For now, we just - * pick one of them and report that as the only possible - * value for this setting. - * The bit allocation map is in fact compatible to the - * wFormatTag of the v1 AS streaming descriptors, which is why - * we can simply map the matrix. - */ - - for (i = 0; i < 5; i++) - if (format & (1UL << i)) { - format = i + 1; - break; - } - - break; - } - - default: - return -EINVAL; - } - - /* FIXME: correct endianess and sign? */ - pcm_format = -1; - - switch (format) { - case UAC_FORMAT_TYPE_I_UNDEFINED: /* some devices don't define this correctly... */ - snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n", - chip->dev->devnum, fp->iface, fp->altsetting); - /* fall-through */ - case UAC_FORMAT_TYPE_I_PCM: - if (sample_width > sample_bytes * 8) { - snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, - sample_width, sample_bytes); - } - /* check the format byte size */ - switch (sample_bytes) { - case 1: - pcm_format = SNDRV_PCM_FORMAT_S8; - break; - case 2: - if (is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S16_BE; /* grrr, big endian!! */ - else - pcm_format = SNDRV_PCM_FORMAT_S16_LE; - break; - case 3: - if (is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S24_3BE; /* grrr, big endian!! */ - else - pcm_format = SNDRV_PCM_FORMAT_S24_3LE; - break; - case 4: - pcm_format = SNDRV_PCM_FORMAT_S32_LE; - break; - default: - snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n", - chip->dev->devnum, fp->iface, fp->altsetting, - sample_width, sample_bytes); - break; - } - break; - case UAC_FORMAT_TYPE_I_PCM8: - pcm_format = SNDRV_PCM_FORMAT_U8; - - /* Dallas DS4201 workaround: it advertises U8 format, but really - supports S8. */ - if (chip->usb_id == USB_ID(0x04fa, 0x4201)) - pcm_format = SNDRV_PCM_FORMAT_S8; - break; - case UAC_FORMAT_TYPE_I_IEEE_FLOAT: - pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE; - break; - case UAC_FORMAT_TYPE_I_ALAW: - pcm_format = SNDRV_PCM_FORMAT_A_LAW; - break; - case UAC_FORMAT_TYPE_I_MULAW: - pcm_format = SNDRV_PCM_FORMAT_MU_LAW; - break; - default: - snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, format); - break; - } - return pcm_format; -} - - -/* - * parse the format descriptor and stores the possible sample rates - * on the audioformat table (audio class v1). - * - * @dev: usb device - * @fp: audioformat record - * @fmt: the format descriptor - * @offset: the start offset of descriptor pointing the rate type - * (7 for type I and II, 8 for type II) - */ -static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp, - unsigned char *fmt, int offset) -{ - int nr_rates = fmt[offset]; - - if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) { - snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n", - chip->dev->devnum, fp->iface, fp->altsetting); - return -1; - } - - if (nr_rates) { - /* - * build the rate table and bitmap flags - */ - int r, idx; - - fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL); - if (fp->rate_table == NULL) { - snd_printk(KERN_ERR "cannot malloc\n"); - return -1; - } - - fp->nr_rates = 0; - fp->rate_min = fp->rate_max = 0; - for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) { - unsigned int rate = combine_triple(&fmt[idx]); - if (!rate) - continue; - /* C-Media CM6501 mislabels its 96 kHz altsetting */ - if (rate == 48000 && nr_rates == 1 && - (chip->usb_id == USB_ID(0x0d8c, 0x0201) || - chip->usb_id == USB_ID(0x0d8c, 0x0102)) && - fp->altsetting == 5 && fp->maxpacksize == 392) - rate = 96000; - /* Creative VF0470 Live Cam reports 16 kHz instead of 8kHz */ - if (rate == 16000 && chip->usb_id == USB_ID(0x041e, 0x4068)) - rate = 8000; - fp->rate_table[fp->nr_rates] = rate; - if (!fp->rate_min || rate < fp->rate_min) - fp->rate_min = rate; - if (!fp->rate_max || rate > fp->rate_max) - fp->rate_max = rate; - fp->rates |= snd_pcm_rate_to_rate_bit(rate); - fp->nr_rates++; - } - if (!fp->nr_rates) { - hwc_debug("All rates were zero. Skipping format!\n"); - return -1; - } - } else { - /* continuous rates */ - fp->rates = SNDRV_PCM_RATE_CONTINUOUS; - fp->rate_min = combine_triple(&fmt[offset + 1]); - fp->rate_max = combine_triple(&fmt[offset + 4]); - } - return 0; -} - -/* - * parse the format descriptor and stores the possible sample rates - * on the audioformat table (audio class v2). - */ -static int parse_audio_format_rates_v2(struct snd_usb_audio *chip, - struct audioformat *fp, - struct usb_host_interface *iface) -{ - struct usb_device *dev = chip->dev; - unsigned char tmp[2], *data; - int i, nr_rates, data_size, ret = 0; - - /* get the number of sample rates first by only fetching 2 bytes */ - ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - 0x0100, chip->clock_id << 8, tmp, sizeof(tmp), 1000); - - if (ret < 0) { - snd_printk(KERN_ERR "unable to retrieve number of sample rates\n"); - goto err; - } - - nr_rates = (tmp[1] << 8) | tmp[0]; - data_size = 2 + 12 * nr_rates; - data = kzalloc(data_size, GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto err; - } - - /* now get the full information */ - ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - 0x0100, chip->clock_id << 8, data, data_size, 1000); - - if (ret < 0) { - snd_printk(KERN_ERR "unable to retrieve sample rate range\n"); - ret = -EINVAL; - goto err_free; - } - - fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL); - if (!fp->rate_table) { - ret = -ENOMEM; - goto err_free; - } - - fp->nr_rates = 0; - fp->rate_min = fp->rate_max = 0; - - for (i = 0; i < nr_rates; i++) { - int rate = combine_quad(&data[2 + 12 * i]); - - fp->rate_table[fp->nr_rates] = rate; - if (!fp->rate_min || rate < fp->rate_min) - fp->rate_min = rate; - if (!fp->rate_max || rate > fp->rate_max) - fp->rate_max = rate; - fp->rates |= snd_pcm_rate_to_rate_bit(rate); - fp->nr_rates++; - } - -err_free: - kfree(data); -err: - return ret; -} - -/* - * parse the format type I and III descriptors - */ -static int parse_audio_format_i(struct snd_usb_audio *chip, - struct audioformat *fp, - int format, void *_fmt, - struct usb_host_interface *iface) -{ - struct usb_interface_descriptor *altsd = get_iface_desc(iface); - struct uac_format_type_i_discrete_descriptor *fmt = _fmt; - int protocol = altsd->bInterfaceProtocol; - int pcm_format, ret; - - if (fmt->bFormatType == UAC_FORMAT_TYPE_III) { - /* FIXME: the format type is really IECxxx - * but we give normal PCM format to get the existing - * apps working... - */ - switch (chip->usb_id) { - - case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - if (device_setup[chip->index] == 0x00 && - fp->altsetting == 6) - pcm_format = SNDRV_PCM_FORMAT_S16_BE; - else - pcm_format = SNDRV_PCM_FORMAT_S16_LE; - break; - default: - pcm_format = SNDRV_PCM_FORMAT_S16_LE; - } - } else { - pcm_format = parse_audio_format_i_type(chip, fp, format, fmt, protocol); - if (pcm_format < 0) - return -1; - } - - fp->format = pcm_format; - - /* gather possible sample rates */ - /* audio class v1 reports possible sample rates as part of the - * proprietary class specific descriptor. - * audio class v2 uses class specific EP0 range requests for that. - */ - switch (protocol) { - case UAC_VERSION_1: - fp->channels = fmt->bNrChannels; - ret = parse_audio_format_rates_v1(chip, fp, _fmt, 7); - break; - case UAC_VERSION_2: - /* fp->channels is already set in this case */ - ret = parse_audio_format_rates_v2(chip, fp, iface); - break; - } - - if (fp->channels < 1) { - snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, fp->channels); - return -1; - } - - return ret; -} - -/* - * parse the format type II descriptor - */ -static int parse_audio_format_ii(struct snd_usb_audio *chip, - struct audioformat *fp, - int format, void *_fmt, - struct usb_host_interface *iface) -{ - int brate, framesize, ret; - struct usb_interface_descriptor *altsd = get_iface_desc(iface); - int protocol = altsd->bInterfaceProtocol; - - switch (format) { - case UAC_FORMAT_TYPE_II_AC3: - /* FIXME: there is no AC3 format defined yet */ - // fp->format = SNDRV_PCM_FORMAT_AC3; - fp->format = SNDRV_PCM_FORMAT_U8; /* temporarily hack to receive byte streams */ - break; - case UAC_FORMAT_TYPE_II_MPEG: - fp->format = SNDRV_PCM_FORMAT_MPEG; - break; - default: - snd_printd(KERN_INFO "%d:%u:%d : unknown format tag %#x is detected. processed as MPEG.\n", - chip->dev->devnum, fp->iface, fp->altsetting, format); - fp->format = SNDRV_PCM_FORMAT_MPEG; - break; - } - - fp->channels = 1; - - switch (protocol) { - case UAC_VERSION_1: { - struct uac_format_type_ii_discrete_descriptor *fmt = _fmt; - brate = le16_to_cpu(fmt->wMaxBitRate); - framesize = le16_to_cpu(fmt->wSamplesPerFrame); - snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); - fp->frame_size = framesize; - ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */ - break; - } - case UAC_VERSION_2: { - struct uac_format_type_ii_ext_descriptor *fmt = _fmt; - brate = le16_to_cpu(fmt->wMaxBitRate); - framesize = le16_to_cpu(fmt->wSamplesPerFrame); - snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); - fp->frame_size = framesize; - ret = parse_audio_format_rates_v2(chip, fp, iface); - break; - } - } - - return ret; -} - -static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp, - int format, unsigned char *fmt, int stream, - struct usb_host_interface *iface) -{ - int err; - - switch (fmt[3]) { - case UAC_FORMAT_TYPE_I: - case UAC_FORMAT_TYPE_III: - err = parse_audio_format_i(chip, fp, format, fmt, iface); - break; - case UAC_FORMAT_TYPE_II: - err = parse_audio_format_ii(chip, fp, format, fmt, iface); - break; - default: - snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n", - chip->dev->devnum, fp->iface, fp->altsetting, fmt[3]); - return -1; - } - fp->fmt_type = fmt[3]; - if (err < 0) - return err; -#if 1 - /* FIXME: temporary hack for extigy/audigy 2 nx/zs */ - /* extigy apparently supports sample rates other than 48k - * but not in ordinary way. so we enable only 48k atm. - */ - if (chip->usb_id == USB_ID(0x041e, 0x3000) || - chip->usb_id == USB_ID(0x041e, 0x3020) || - chip->usb_id == USB_ID(0x041e, 0x3061)) { - if (fmt[3] == UAC_FORMAT_TYPE_I && - fp->rates != SNDRV_PCM_RATE_48000 && - fp->rates != SNDRV_PCM_RATE_96000) - return -1; - } -#endif - return 0; -} - -static unsigned char parse_datainterval(struct snd_usb_audio *chip, - struct usb_host_interface *alts) -{ - if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH && - get_endpoint(alts, 0)->bInterval >= 1 && - get_endpoint(alts, 0)->bInterval <= 4) - return get_endpoint(alts, 0)->bInterval - 1; - else - return 0; -} - -static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, - int iface, int altno); -static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) -{ - struct usb_device *dev; - struct usb_interface *iface; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - int i, altno, err, stream; - int format = 0, num_channels = 0; - struct audioformat *fp = NULL; - unsigned char *fmt, *csep; - int num, protocol; - - dev = chip->dev; - - /* parse the interface's altsettings */ - iface = usb_ifnum_to_if(dev, iface_no); - - num = iface->num_altsetting; - - /* - * Dallas DS4201 workaround: It presents 5 altsettings, but the last - * one misses syncpipe, and does not produce any sound. - */ - if (chip->usb_id == USB_ID(0x04fa, 0x4201)) - num = 4; - - for (i = 0; i < num; i++) { - alts = &iface->altsetting[i]; - altsd = get_iface_desc(alts); - protocol = altsd->bInterfaceProtocol; - /* skip invalid one */ - if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && - altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || - (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING && - altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC) || - altsd->bNumEndpoints < 1 || - le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0) - continue; - /* must be isochronous */ - if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != - USB_ENDPOINT_XFER_ISOC) - continue; - /* check direction */ - stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ? - SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - altno = altsd->bAlternateSetting; - - /* audiophile usb: skip altsets incompatible with device_setup - */ - if (chip->usb_id == USB_ID(0x0763, 0x2003) && - audiophile_skip_setting_quirk(chip, iface_no, altno)) - continue; - - /* get audio formats */ - switch (protocol) { - case UAC_VERSION_1: { - struct uac_as_header_descriptor_v1 *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - - if (!as) { - snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n", - dev->devnum, iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n", - dev->devnum, iface_no, altno); - continue; - } - - format = le16_to_cpu(as->wFormatTag); /* remember the format value */ - break; - } - - case UAC_VERSION_2: { - struct uac_as_header_descriptor_v2 *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - - if (!as) { - snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n", - dev->devnum, iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n", - dev->devnum, iface_no, altno); - continue; - } - - num_channels = as->bNrChannels; - format = le32_to_cpu(as->bmFormats); - - break; - } - - default: - snd_printk(KERN_ERR "%d:%u:%d : unknown interface protocol %04x\n", - dev->devnum, iface_no, altno, protocol); - continue; - } - - /* get format type */ - fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE); - if (!fmt) { - snd_printk(KERN_ERR "%d:%u:%d : no UAC_FORMAT_TYPE desc\n", - dev->devnum, iface_no, altno); - continue; - } - if (((protocol == UAC_VERSION_1) && (fmt[0] < 8)) || - ((protocol == UAC_VERSION_2) && (fmt[0] != 6))) { - snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n", - dev->devnum, iface_no, altno); - continue; - } - - /* - * Blue Microphones workaround: The last altsetting is identical - * with the previous one, except for a larger packet size, but - * is actually a mislabeled two-channel setting; ignore it. - */ - if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 && - fp && fp->altsetting == 1 && fp->channels == 1 && - fp->format == SNDRV_PCM_FORMAT_S16_LE && - protocol == UAC_VERSION_1 && - le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == - fp->maxpacksize * 2) - continue; - - csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT); - /* Creamware Noah has this descriptor after the 2nd endpoint */ - if (!csep && altsd->bNumEndpoints >= 2) - csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT); - if (!csep || csep[0] < 7 || csep[2] != UAC_EP_GENERAL) { - snd_printk(KERN_WARNING "%d:%u:%d : no or invalid" - " class specific endpoint descriptor\n", - dev->devnum, iface_no, altno); - csep = NULL; - } - - fp = kzalloc(sizeof(*fp), GFP_KERNEL); - if (! fp) { - snd_printk(KERN_ERR "cannot malloc\n"); - return -ENOMEM; - } - - fp->iface = iface_no; - fp->altsetting = altno; - fp->altset_idx = i; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->datainterval = parse_datainterval(chip, alts); - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - /* num_channels is only set for v2 interfaces */ - fp->channels = num_channels; - if (snd_usb_get_speed(dev) == USB_SPEED_HIGH) - fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) - * (fp->maxpacksize & 0x7ff); - fp->attributes = csep ? csep[3] : 0; - - /* some quirks for attributes here */ - - switch (chip->usb_id) { - case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ - /* Optoplay sets the sample rate attribute although - * it seems not supporting it in fact. - */ - fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE; - break; - case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */ - case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - /* doesn't set the sample rate attribute, but supports it */ - fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; - break; - case USB_ID(0x047f, 0x0ca1): /* plantronics headset */ - case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is - an older model 77d:223) */ - /* - * plantronics headset and Griffin iMic have set adaptive-in - * although it's really not... - */ - fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE; - else - fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC; - break; - } - - /* ok, let's parse further... */ - if (parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) { - kfree(fp->rate_table); - kfree(fp); - continue; - } - - snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint %#x\n", dev->devnum, iface_no, altno, fp->endpoint); - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp->rate_table); - kfree(fp); - return err; - } - /* try to set the interface... */ - usb_set_interface(chip->dev, iface_no, altno); - init_usb_pitch(chip->dev, iface_no, alts, fp); - init_usb_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max); - } - return 0; -} - - -/* - * disconnect streams - * called from snd_usb_audio_disconnect() - */ -static void snd_usb_stream_disconnect(struct list_head *head) -{ - int idx; - struct snd_usb_stream *as; - struct snd_usb_substream *subs; - - as = list_entry(head, struct snd_usb_stream, list); - for (idx = 0; idx < 2; idx++) { - subs = &as->substream[idx]; - if (!subs->num_formats) - return; - release_substream_urbs(subs, 1); - subs->interface = -1; - } -} - -static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface) -{ - struct usb_device *dev = chip->dev; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct usb_interface *iface = usb_ifnum_to_if(dev, interface); - - if (!iface) { - snd_printk(KERN_ERR "%d:%u:%d : does not exist\n", - dev->devnum, ctrlif, interface); - return -EINVAL; - } - - if (usb_interface_claimed(iface)) { - snd_printdd(KERN_INFO "%d:%d:%d: skipping, already claimed\n", - dev->devnum, ctrlif, interface); - return -EINVAL; - } - - alts = &iface->altsetting[0]; - altsd = get_iface_desc(alts); - if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || - altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && - altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { - int err = snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL); - if (err < 0) { - snd_printk(KERN_ERR "%d:%u:%d: cannot create sequencer device\n", - dev->devnum, ctrlif, interface); - return -EINVAL; - } - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - - return 0; - } - - if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && - altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || - altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) { - snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n", - dev->devnum, ctrlif, interface, altsd->bInterfaceClass); - /* skip non-supported classes */ - return -EINVAL; - } - - if (snd_usb_get_speed(dev) == USB_SPEED_LOW) { - snd_printk(KERN_ERR "low speed audio streaming not supported\n"); - return -EINVAL; - } - - if (! parse_audio_endpoints(chip, interface)) { - usb_set_interface(dev, interface, 0); /* reset the current interface */ - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - return -EINVAL; - } - - return 0; -} - -/* - * parse audio control descriptor and create pcm/midi streams - */ -static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) -{ - struct usb_device *dev = chip->dev; - struct usb_host_interface *host_iface; - struct usb_interface_descriptor *altsd; - void *control_header; - int i, protocol; - - /* find audiocontrol interface */ - host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; - control_header = snd_usb_find_csint_desc(host_iface->extra, - host_iface->extralen, - NULL, UAC_HEADER); - altsd = get_iface_desc(host_iface); - protocol = altsd->bInterfaceProtocol; - - if (!control_header) { - snd_printk(KERN_ERR "cannot find UAC_HEADER\n"); - return -EINVAL; - } - - switch (protocol) { - case UAC_VERSION_1: { - struct uac_ac_header_descriptor_v1 *h1 = control_header; - - if (!h1->bInCollection) { - snd_printk(KERN_INFO "skipping empty audio interface (v1)\n"); - return -EINVAL; - } - - if (h1->bLength < sizeof(*h1) + h1->bInCollection) { - snd_printk(KERN_ERR "invalid UAC_HEADER (v1)\n"); - return -EINVAL; - } - - for (i = 0; i < h1->bInCollection; i++) - snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]); - - break; - } - - case UAC_VERSION_2: { - struct uac_clock_source_descriptor *cs; - struct usb_interface_assoc_descriptor *assoc = - usb_ifnum_to_if(dev, ctrlif)->intf_assoc; - - if (!assoc) { - snd_printk(KERN_ERR "Audio class v2 interfaces need an interface association\n"); - return -EINVAL; - } - - /* FIXME: for now, we expect there is at least one clock source - * descriptor and we always take the first one. - * We should properly support devices with multiple clock sources, - * clock selectors and sample rate conversion units. */ - - cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, - NULL, UAC_CLOCK_SOURCE); - - if (!cs) { - snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n"); - return -EINVAL; - } - - chip->clock_id = cs->bClockID; - - for (i = 0; i < assoc->bInterfaceCount; i++) { - int intf = assoc->bFirstInterface + i; - - if (intf != ctrlif) - snd_usb_create_stream(chip, ctrlif, intf); - } - - break; - } - - default: - snd_printk(KERN_ERR "unknown protocol version 0x%02x\n", protocol); - return -EINVAL; - } - - return 0; -} - -/* - * create a stream for an endpoint/altsetting without proper descriptors - */ -static int create_fixed_stream_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - struct audioformat *fp; - struct usb_host_interface *alts; - int stream, err; - unsigned *rate_table = NULL; - - fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL); - if (! fp) { - snd_printk(KERN_ERR "cannot memdup\n"); - return -ENOMEM; - } - if (fp->nr_rates > 0) { - rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL); - if (!rate_table) { - kfree(fp); - return -ENOMEM; - } - memcpy(rate_table, fp->rate_table, sizeof(int) * fp->nr_rates); - fp->rate_table = rate_table; - } - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - kfree(rate_table); - return err; - } - if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber || - fp->altset_idx >= iface->num_altsetting) { - kfree(fp); - kfree(rate_table); - return -EINVAL; - } - alts = &iface->altsetting[fp->altset_idx]; - fp->datainterval = parse_datainterval(chip, alts); - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - usb_set_interface(chip->dev, fp->iface, 0); - init_usb_pitch(chip->dev, fp->iface, alts, fp); - init_usb_sample_rate(chip->dev, fp->iface, alts, fp, fp->rate_max); - return 0; -} - -/* - * create a stream for an interface with proper descriptors - */ -static int create_standard_audio_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - int err; - - alts = &iface->altsetting[0]; - altsd = get_iface_desc(alts); - err = parse_audio_endpoints(chip, altsd->bInterfaceNumber); - if (err < 0) { - snd_printk(KERN_ERR "cannot setup if %d: error %d\n", - altsd->bInterfaceNumber, err); - return err; - } - /* reset the current interface */ - usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0); - return 0; -} - -/* - * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface. - * The only way to detect the sample rate is by looking at wMaxPacketSize. - */ -static int create_uaxx_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - static const struct audioformat ua_format = { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .fmt_type = UAC_FORMAT_TYPE_I, - .altsetting = 1, - .altset_idx = 1, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - }; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct audioformat *fp; - int stream, err; - - /* both PCM and MIDI interfaces have 2 or more altsettings */ - if (iface->num_altsetting < 2) - return -ENXIO; - alts = &iface->altsetting[1]; - altsd = get_iface_desc(alts); - - if (altsd->bNumEndpoints == 2) { - static const struct snd_usb_midi_endpoint_info ua700_ep = { - .out_cables = 0x0003, - .in_cables = 0x0003 - }; - static const struct snd_usb_audio_quirk ua700_quirk = { - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &ua700_ep - }; - static const struct snd_usb_midi_endpoint_info uaxx_ep = { - .out_cables = 0x0001, - .in_cables = 0x0001 - }; - static const struct snd_usb_audio_quirk uaxx_quirk = { - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &uaxx_ep - }; - const struct snd_usb_audio_quirk *quirk = - chip->usb_id == USB_ID(0x0582, 0x002b) - ? &ua700_quirk : &uaxx_quirk; - return snd_usbmidi_create(chip->card, iface, - &chip->midi_list, quirk); - } - - if (altsd->bNumEndpoints != 1) - return -ENXIO; - - fp = kmalloc(sizeof(*fp), GFP_KERNEL); - if (!fp) - return -ENOMEM; - memcpy(fp, &ua_format, sizeof(*fp)); - - fp->iface = altsd->bInterfaceNumber; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->datainterval = 0; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - - switch (fp->maxpacksize) { - case 0x120: - fp->rate_max = fp->rate_min = 44100; - break; - case 0x138: - case 0x140: - fp->rate_max = fp->rate_min = 48000; - break; - case 0x258: - case 0x260: - fp->rate_max = fp->rate_min = 96000; - break; - default: - snd_printk(KERN_ERR "unknown sample rate\n"); - kfree(fp); - return -ENXIO; - } - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - return err; - } - usb_set_interface(chip->dev, fp->iface, 0); - return 0; -} - -static int snd_usb_create_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk); - -/* - * handle the quirks for the contained interfaces - */ -static int create_composite_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; - int err; - - for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) { - iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); - if (!iface) - continue; - if (quirk->ifnum != probed_ifnum && - usb_interface_claimed(iface)) - continue; - err = snd_usb_create_quirk(chip, iface, quirk); - if (err < 0) - return err; - if (quirk->ifnum != probed_ifnum) - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - } - return 0; -} - -static int ignore_interface_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - return 0; -} - -/* - * Allow alignment on audio sub-slot (channel samples) rather than - * on audio slots (audio frames) - */ -static int create_align_transfer_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - chip->txfr_quirk = 1; - return 1; /* Continue with creating streams and mixer */ -} - - -/* - * boot quirks - */ - -#define EXTIGY_FIRMWARE_SIZE_OLD 794 -#define EXTIGY_FIRMWARE_SIZE_NEW 483 - -static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf) -{ - struct usb_host_config *config = dev->actconfig; - int err; - - if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD || - le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) { - snd_printdd("sending Extigy boot sequence...\n"); - /* Send message to force it to reconnect with full interface. */ - err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0), - 0x10, 0x43, 0x0001, 0x000a, NULL, 0, 1000); - if (err < 0) snd_printdd("error sending boot message: %d\n", err); - err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, - &dev->descriptor, sizeof(dev->descriptor)); - config = dev->actconfig; - if (err < 0) snd_printdd("error usb_get_descriptor: %d\n", err); - err = usb_reset_configuration(dev); - if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err); - snd_printdd("extigy_boot: new boot length = %d\n", - le16_to_cpu(get_cfg_desc(config)->wTotalLength)); - return -ENODEV; /* quit this anyway */ - } - return 0; -} - -static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev) -{ - u8 buf = 1; - - snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, - 0, 0, &buf, 1, 1000); - if (buf == 0) { - snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - 1, 2000, NULL, 0, 1000); - return -ENODEV; - } - return 0; -} - -/* - * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely - * documented in the device's data sheet. - */ -static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value) -{ - u8 buf[4]; - buf[0] = 0x20; - buf[1] = value & 0xff; - buf[2] = (value >> 8) & 0xff; - buf[3] = reg; - return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, - USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, - 0, 0, &buf, 4, 1000); -} - -static int snd_usb_cm106_boot_quirk(struct usb_device *dev) -{ - /* - * Enable line-out driver mode, set headphone source to front - * channels, enable stereo mic. - */ - return snd_usb_cm106_write_int_reg(dev, 2, 0x8004); -} - -/* - * C-Media CM6206 is based on CM106 with two additional - * registers that are not documented in the data sheet. - * Values here are chosen based on sniffing USB traffic - * under Windows. - */ -static int snd_usb_cm6206_boot_quirk(struct usb_device *dev) -{ - int err, reg; - int val[] = {0x200c, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000}; - - for (reg = 0; reg < ARRAY_SIZE(val); reg++) { - err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]); - if (err < 0) - return err; - } - - return err; -} - -/* - * This call will put the synth in "USB send" mode, i.e it will send MIDI - * messages through USB (this is disabled at startup). The synth will - * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB - * sign on its LCD. Values here are chosen based on sniffing USB traffic - * under Windows. - */ -static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) -{ - int err, actual_length; - - /* "midi send" enable */ - static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 }; - - void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL); - if (!buf) - return -ENOMEM; - err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf, - ARRAY_SIZE(seq), &actual_length, 1000); - kfree(buf); - if (err < 0) - return err; - - return 0; -} - -/* - * Setup quirks - */ -#define AUDIOPHILE_SET 0x01 /* if set, parse device_setup */ -#define AUDIOPHILE_SET_DTS 0x02 /* if set, enable DTS Digital Output */ -#define AUDIOPHILE_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */ -#define AUDIOPHILE_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */ -#define AUDIOPHILE_SET_DI 0x10 /* if set, enable Digital Input */ -#define AUDIOPHILE_SET_MASK 0x1F /* bit mask for setup value */ -#define AUDIOPHILE_SET_24B_48K_DI 0x19 /* value for 24bits+48KHz+Digital Input */ -#define AUDIOPHILE_SET_24B_48K_NOTDI 0x09 /* value for 24bits+48KHz+No Digital Input */ -#define AUDIOPHILE_SET_16B_48K_DI 0x11 /* value for 16bits+48KHz+Digital Input */ -#define AUDIOPHILE_SET_16B_48K_NOTDI 0x01 /* value for 16bits+48KHz+No Digital Input */ - -static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, - int iface, int altno) -{ - /* Reset ALL ifaces to 0 altsetting. - * Call it for every possible altsetting of every interface. - */ - usb_set_interface(chip->dev, iface, 0); - - if (device_setup[chip->index] & AUDIOPHILE_SET) { - if ((device_setup[chip->index] & AUDIOPHILE_SET_DTS) - && altno != 6) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_96K) - && altno != 1) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_24B_48K_DI && altno != 2) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_24B_48K_NOTDI && altno != 3) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_16B_48K_DI && altno != 4) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_16B_48K_NOTDI && altno != 5) - return 1; /* skip this altsetting */ - } - return 0; /* keep this altsetting */ -} - -static int create_any_midi_quirk(struct snd_usb_audio *chip, - struct usb_interface *intf, - const struct snd_usb_audio_quirk *quirk) -{ - return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); -} - -/* - * audio-interface quirks - * - * returns zero if no standard audio/MIDI parsing is needed. - * returns a postive value if standard audio/midi interfaces are parsed - * after this. - * returns a negative value at error. - */ -static int snd_usb_create_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - typedef int (*quirk_func_t)(struct snd_usb_audio *, struct usb_interface *, - const struct snd_usb_audio_quirk *); - static const quirk_func_t quirk_funcs[] = { - [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk, - [QUIRK_COMPOSITE] = create_composite_quirk, - [QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk, - [QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk, - [QUIRK_MIDI_YAMAHA] = create_any_midi_quirk, - [QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk, - [QUIRK_MIDI_NOVATION] = create_any_midi_quirk, - [QUIRK_MIDI_FASTLANE] = create_any_midi_quirk, - [QUIRK_MIDI_EMAGIC] = create_any_midi_quirk, - [QUIRK_MIDI_CME] = create_any_midi_quirk, - [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, - [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, - [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk, - [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk - }; - - if (quirk->type < QUIRK_TYPE_COUNT) { - return quirk_funcs[quirk->type](chip, iface, quirk); - } else { - snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); - return -ENXIO; - } -} - - -/* - * common proc files to show the usb device info - */ -static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) -{ - struct snd_usb_audio *chip = entry->private_data; - if (!chip->shutdown) - snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum); -} - -static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) -{ - struct snd_usb_audio *chip = entry->private_data; - if (!chip->shutdown) - snd_iprintf(buffer, "%04x:%04x\n", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); -} - -static void snd_usb_audio_create_proc(struct snd_usb_audio *chip) -{ - struct snd_info_entry *entry; - if (!snd_card_proc_new(chip->card, "usbbus", &entry)) - snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read); - if (!snd_card_proc_new(chip->card, "usbid", &entry)) - snd_info_set_text_ops(entry, chip, proc_audio_usbid_read); -} - -/* - * free the chip instance - * - * here we have to do not much, since pcm and controls are already freed - * - */ - -static int snd_usb_audio_free(struct snd_usb_audio *chip) -{ - kfree(chip); - return 0; -} - -static int snd_usb_audio_dev_free(struct snd_device *device) -{ - struct snd_usb_audio *chip = device->device_data; - return snd_usb_audio_free(chip); -} - - -/* - * create a chip instance and set its names. - */ -static int snd_usb_audio_create(struct usb_device *dev, int idx, - const struct snd_usb_audio_quirk *quirk, - struct snd_usb_audio **rchip) -{ - struct snd_card *card; - struct snd_usb_audio *chip; - int err, len; - char component[14]; - static struct snd_device_ops ops = { - .dev_free = snd_usb_audio_dev_free, - }; - - *rchip = NULL; - - if (snd_usb_get_speed(dev) != USB_SPEED_LOW && - snd_usb_get_speed(dev) != USB_SPEED_FULL && - snd_usb_get_speed(dev) != USB_SPEED_HIGH) { - snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev)); - return -ENXIO; - } - - err = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card); - if (err < 0) { - snd_printk(KERN_ERR "cannot create card instance %d\n", idx); - return err; - } - - chip = kzalloc(sizeof(*chip), GFP_KERNEL); - if (! chip) { - snd_card_free(card); - return -ENOMEM; - } - - chip->index = idx; - chip->dev = dev; - chip->card = card; - chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); - INIT_LIST_HEAD(&chip->pcm_list); - INIT_LIST_HEAD(&chip->midi_list); - INIT_LIST_HEAD(&chip->mixer_list); - - if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { - snd_usb_audio_free(chip); - snd_card_free(card); - return err; - } - - strcpy(card->driver, "USB-Audio"); - sprintf(component, "USB%04x:%04x", - USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); - snd_component_add(card, component); - - /* retrieve the device string as shortname */ - if (quirk && quirk->product_name) { - strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname)); - } else { - if (!dev->descriptor.iProduct || - usb_string(dev, dev->descriptor.iProduct, - card->shortname, sizeof(card->shortname)) <= 0) { - /* no name available from anywhere, so use ID */ - sprintf(card->shortname, "USB Device %#04x:%#04x", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); - } - } - - /* retrieve the vendor and device strings as longname */ - if (quirk && quirk->vendor_name) { - len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); - } else { - if (dev->descriptor.iManufacturer) - len = usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - else - len = 0; - /* we don't really care if there isn't any vendor string */ - } - if (len > 0) - strlcat(card->longname, " ", sizeof(card->longname)); - - strlcat(card->longname, card->shortname, sizeof(card->longname)); - - len = strlcat(card->longname, " at ", sizeof(card->longname)); - - if (len < sizeof(card->longname)) - usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); - - strlcat(card->longname, - snd_usb_get_speed(dev) == USB_SPEED_LOW ? ", low speed" : - snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" : - ", high speed", - sizeof(card->longname)); - - snd_usb_audio_create_proc(chip); - - *rchip = chip; - return 0; -} - - -/* - * probe the active usb device - * - * note that this can be called multiple times per a device, when it - * includes multiple audio control interfaces. - * - * thus we check the usb device pointer and creates the card instance - * only at the first time. the successive calls of this function will - * append the pcm interface to the corresponding card. - */ -static void *snd_usb_audio_probe(struct usb_device *dev, - struct usb_interface *intf, - const struct usb_device_id *usb_id) -{ - const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info; - int i, err; - struct snd_usb_audio *chip; - struct usb_host_interface *alts; - int ifnum; - u32 id; - - alts = &intf->altsetting[0]; - ifnum = get_iface_desc(alts)->bInterfaceNumber; - id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); - if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) - goto __err_val; - - /* SB Extigy needs special boot-up sequence */ - /* if more models come, this will go to the quirk list. */ - if (id == USB_ID(0x041e, 0x3000)) { - if (snd_usb_extigy_boot_quirk(dev, intf) < 0) - goto __err_val; - } - /* SB Audigy 2 NX needs its own boot-up magic, too */ - if (id == USB_ID(0x041e, 0x3020)) { - if (snd_usb_audigy2nx_boot_quirk(dev) < 0) - goto __err_val; - } - - /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ - if (id == USB_ID(0x10f5, 0x0200)) { - if (snd_usb_cm106_boot_quirk(dev) < 0) - goto __err_val; - } - - /* C-Media CM6206 / CM106-Like Sound Device */ - if (id == USB_ID(0x0d8c, 0x0102)) { - if (snd_usb_cm6206_boot_quirk(dev) < 0) - goto __err_val; - } - - /* Access Music VirusTI Desktop */ - if (id == USB_ID(0x133e, 0x0815)) { - if (snd_usb_accessmusic_boot_quirk(dev) < 0) - goto __err_val; - } - - /* - * found a config. now register to ALSA - */ - - /* check whether it's already registered */ - chip = NULL; - mutex_lock(®ister_mutex); - for (i = 0; i < SNDRV_CARDS; i++) { - if (usb_chip[i] && usb_chip[i]->dev == dev) { - if (usb_chip[i]->shutdown) { - snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n"); - goto __error; - } - chip = usb_chip[i]; - break; - } - } - if (! chip) { - /* it's a fresh one. - * now look for an empty slot and create a new card instance - */ - for (i = 0; i < SNDRV_CARDS; i++) - if (enable[i] && ! usb_chip[i] && - (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && - (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { - if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) { - goto __error; - } - snd_card_set_dev(chip->card, &intf->dev); - break; - } - if (!chip) { - printk(KERN_ERR "no available usb audio device\n"); - goto __error; - } - } - - chip->txfr_quirk = 0; - err = 1; /* continue */ - if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) { - /* need some special handlings */ - if ((err = snd_usb_create_quirk(chip, intf, quirk)) < 0) - goto __error; - } - - if (err > 0) { - /* create normal USB audio interfaces */ - if (snd_usb_create_streams(chip, ifnum) < 0 || - snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) { - goto __error; - } - } - - /* we are allowed to call snd_card_register() many times */ - if (snd_card_register(chip->card) < 0) { - goto __error; - } - - usb_chip[chip->index] = chip; - chip->num_interfaces++; - mutex_unlock(®ister_mutex); - return chip; - - __error: - if (chip && !chip->num_interfaces) - snd_card_free(chip->card); - mutex_unlock(®ister_mutex); - __err_val: - return NULL; -} - -/* - * we need to take care of counter, since disconnection can be called also - * many times as well as usb_audio_probe(). - */ -static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr) -{ - struct snd_usb_audio *chip; - struct snd_card *card; - struct list_head *p; - - if (ptr == (void *)-1L) - return; - - chip = ptr; - card = chip->card; - mutex_lock(®ister_mutex); - chip->shutdown = 1; - chip->num_interfaces--; - if (chip->num_interfaces <= 0) { - snd_card_disconnect(card); - /* release the pcm resources */ - list_for_each(p, &chip->pcm_list) { - snd_usb_stream_disconnect(p); - } - /* release the midi resources */ - list_for_each(p, &chip->midi_list) { - snd_usbmidi_disconnect(p); - } - /* release mixer resources */ - list_for_each(p, &chip->mixer_list) { - snd_usb_mixer_disconnect(p); - } - usb_chip[chip->index] = NULL; - mutex_unlock(®ister_mutex); - snd_card_free_when_closed(card); - } else { - mutex_unlock(®ister_mutex); - } -} - -/* - * new 2.5 USB kernel API - */ -static int usb_audio_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - void *chip; - chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id); - if (chip) { - usb_set_intfdata(intf, chip); - return 0; - } else - return -EIO; -} - -static void usb_audio_disconnect(struct usb_interface *intf) -{ - snd_usb_audio_disconnect(interface_to_usbdev(intf), - usb_get_intfdata(intf)); -} - -#ifdef CONFIG_PM -static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) -{ - struct snd_usb_audio *chip = usb_get_intfdata(intf); - struct list_head *p; - struct snd_usb_stream *as; - - if (chip == (void *)-1L) - return 0; - - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); - if (!chip->num_suspended_intf++) { - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - snd_pcm_suspend_all(as->pcm); - } - } - - return 0; -} - -static int usb_audio_resume(struct usb_interface *intf) -{ - struct snd_usb_audio *chip = usb_get_intfdata(intf); - - if (chip == (void *)-1L) - return 0; - if (--chip->num_suspended_intf) - return 0; - /* - * ALSA leaves material resumption to user space - * we just notify - */ - - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); - - return 0; -} -#endif /* CONFIG_PM */ - -static int __init snd_usb_audio_init(void) -{ - if (nrpacks < 1 || nrpacks > MAX_PACKS) { - printk(KERN_WARNING "invalid nrpacks value.\n"); - return -EINVAL; - } - return usb_register(&usb_audio_driver); -} - - -static void __exit snd_usb_audio_cleanup(void) -{ - usb_deregister(&usb_audio_driver); -} - -module_init(snd_usb_audio_init); -module_exit(snd_usb_audio_cleanup); diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 49a691a0b28..d679e72a3e5 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -27,6 +27,7 @@ #define USB_ID_PRODUCT(id) ((u16)(id)) /* + * */ struct snd_usb_audio { @@ -48,6 +49,10 @@ struct snd_usb_audio { struct list_head midi_list; /* list of midi interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ + + int setup; /* from the 'device_setup' module param */ + int nrpacks; /* from the 'nrpacks' module param */ + int async_unlink; /* from the 'async_unlink' module param */ }; /* @@ -86,45 +91,8 @@ struct snd_usb_audio_quirk { const void *data; }; -/* - */ - -/*E-mu USB samplerate control quirk*/ -enum { - EMU_QUIRK_SR_44100HZ = 0, - EMU_QUIRK_SR_48000HZ, - EMU_QUIRK_SR_88200HZ, - EMU_QUIRK_SR_96000HZ, - EMU_QUIRK_SR_176400HZ, - EMU_QUIRK_SR_192000HZ -}; - #define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8)) #define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16)) #define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24)) -unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size); - -void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype); -void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype); - -int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, - __u8 request, __u8 requesttype, __u16 value, __u16 index, - void *data, __u16 size, int timeout); - -/* - * retrieve usb_interface descriptor from the host interface - * (conditional for compatibility with the older API) - */ -#ifndef get_iface_desc -#define get_iface_desc(iface) (&(iface)->desc) -#define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc) -#define get_ep_desc(ep) (&(ep)->desc) -#define get_cfg_desc(cfg) (&(cfg)->desc) -#endif - -#ifndef snd_usb_get_speed -#define snd_usb_get_speed(dev) ((dev)->speed) -#endif - #endif /* __USBAUDIO_H */ diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c deleted file mode 100644 index 5915a04cdb9..00000000000 --- a/sound/usb/usbmidi.c +++ /dev/null @@ -1,2069 +0,0 @@ -/* - * usbmidi.c - ALSA USB MIDI driver - * - * Copyright (c) 2002-2009 Clemens Ladisch - * All rights reserved. - * - * Based on the OSS usb-midi driver by NAGANO Daisuke, - * NetBSD's umidi driver by Takuya SHIOZAKI, - * the "USB Device Class Definition for MIDI Devices" by Roland - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer, - * without modification. - * 2. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * Alternatively, this software may be distributed and/or modified 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. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include "usbaudio.h" -#include "usbmidi.h" - - -/* - * define this to log all USB packets - */ -/* #define DUMP_PACKETS */ - -/* - * how long to wait after some USB errors, so that khubd can disconnect() us - * without too many spurious errors - */ -#define ERROR_DELAY_JIFFIES (HZ / 10) - -#define OUTPUT_URBS 7 -#define INPUT_URBS 7 - - -MODULE_AUTHOR("Clemens Ladisch "); -MODULE_DESCRIPTION("USB Audio/MIDI helper module"); -MODULE_LICENSE("Dual BSD/GPL"); - - -struct usb_ms_header_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bcdMSC[2]; - __le16 wTotalLength; -} __attribute__ ((packed)); - -struct usb_ms_endpoint_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bNumEmbMIDIJack; - __u8 baAssocJackID[0]; -} __attribute__ ((packed)); - -struct snd_usb_midi_in_endpoint; -struct snd_usb_midi_out_endpoint; -struct snd_usb_midi_endpoint; - -struct usb_protocol_ops { - void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int); - void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb); - void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t); - void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint*); - void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint*); -}; - -struct snd_usb_midi { - struct usb_device *dev; - struct snd_card *card; - struct usb_interface *iface; - const struct snd_usb_audio_quirk *quirk; - struct snd_rawmidi *rmidi; - struct usb_protocol_ops* usb_protocol_ops; - struct list_head list; - struct timer_list error_timer; - spinlock_t disc_lock; - struct mutex mutex; - u32 usb_id; - int next_midi_device; - - struct snd_usb_midi_endpoint { - struct snd_usb_midi_out_endpoint *out; - struct snd_usb_midi_in_endpoint *in; - } endpoints[MIDI_MAX_ENDPOINTS]; - unsigned long input_triggered; - unsigned int opened; - unsigned char disconnected; - - struct snd_kcontrol *roland_load_ctl; -}; - -struct snd_usb_midi_out_endpoint { - struct snd_usb_midi* umidi; - struct out_urb_context { - struct urb *urb; - struct snd_usb_midi_out_endpoint *ep; - } urbs[OUTPUT_URBS]; - unsigned int active_urbs; - unsigned int drain_urbs; - int max_transfer; /* size of urb buffer */ - struct tasklet_struct tasklet; - unsigned int next_urb; - spinlock_t buffer_lock; - - struct usbmidi_out_port { - struct snd_usb_midi_out_endpoint* ep; - struct snd_rawmidi_substream *substream; - int active; - uint8_t cable; /* cable number << 4 */ - uint8_t state; -#define STATE_UNKNOWN 0 -#define STATE_1PARAM 1 -#define STATE_2PARAM_1 2 -#define STATE_2PARAM_2 3 -#define STATE_SYSEX_0 4 -#define STATE_SYSEX_1 5 -#define STATE_SYSEX_2 6 - uint8_t data[2]; - } ports[0x10]; - int current_port; - - wait_queue_head_t drain_wait; -}; - -struct snd_usb_midi_in_endpoint { - struct snd_usb_midi* umidi; - struct urb* urbs[INPUT_URBS]; - struct usbmidi_in_port { - struct snd_rawmidi_substream *substream; - u8 running_status_length; - } ports[0x10]; - u8 seen_f5; - u8 error_resubmit; - int current_port; -}; - -static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep); - -static const uint8_t snd_usbmidi_cin_length[] = { - 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 -}; - -/* - * Submits the URB, with error handling. - */ -static int snd_usbmidi_submit_urb(struct urb* urb, gfp_t flags) -{ - int err = usb_submit_urb(urb, flags); - if (err < 0 && err != -ENODEV) - snd_printk(KERN_ERR "usb_submit_urb: %d\n", err); - return err; -} - -/* - * Error handling for URB completion functions. - */ -static int snd_usbmidi_urb_error(int status) -{ - switch (status) { - /* manually unlinked, or device gone */ - case -ENOENT: - case -ECONNRESET: - case -ESHUTDOWN: - case -ENODEV: - return -ENODEV; - /* errors that might occur during unplugging */ - case -EPROTO: - case -ETIME: - case -EILSEQ: - return -EIO; - default: - snd_printk(KERN_ERR "urb status %d\n", status); - return 0; /* continue */ - } -} - -/* - * Receives a chunk of MIDI data. - */ -static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint* ep, int portidx, - uint8_t* data, int length) -{ - struct usbmidi_in_port* port = &ep->ports[portidx]; - - if (!port->substream) { - snd_printd("unexpected port %d!\n", portidx); - return; - } - if (!test_bit(port->substream->number, &ep->umidi->input_triggered)) - return; - snd_rawmidi_receive(port->substream, data, length); -} - -#ifdef DUMP_PACKETS -static void dump_urb(const char *type, const u8 *data, int length) -{ - snd_printk(KERN_DEBUG "%s packet: [", type); - for (; length > 0; ++data, --length) - printk(" %02x", *data); - printk(" ]\n"); -} -#else -#define dump_urb(type, data, length) /* nothing */ -#endif - -/* - * Processes the data read from the device. - */ -static void snd_usbmidi_in_urb_complete(struct urb* urb) -{ - struct snd_usb_midi_in_endpoint* ep = urb->context; - - if (urb->status == 0) { - dump_urb("received", urb->transfer_buffer, urb->actual_length); - ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer, - urb->actual_length); - } else { - int err = snd_usbmidi_urb_error(urb->status); - if (err < 0) { - if (err != -ENODEV) { - ep->error_resubmit = 1; - mod_timer(&ep->umidi->error_timer, - jiffies + ERROR_DELAY_JIFFIES); - } - return; - } - } - - urb->dev = ep->umidi->dev; - snd_usbmidi_submit_urb(urb, GFP_ATOMIC); -} - -static void snd_usbmidi_out_urb_complete(struct urb* urb) -{ - struct out_urb_context *context = urb->context; - struct snd_usb_midi_out_endpoint* ep = context->ep; - unsigned int urb_index; - - spin_lock(&ep->buffer_lock); - urb_index = context - ep->urbs; - ep->active_urbs &= ~(1 << urb_index); - if (unlikely(ep->drain_urbs)) { - ep->drain_urbs &= ~(1 << urb_index); - wake_up(&ep->drain_wait); - } - spin_unlock(&ep->buffer_lock); - if (urb->status < 0) { - int err = snd_usbmidi_urb_error(urb->status); - if (err < 0) { - if (err != -ENODEV) - mod_timer(&ep->umidi->error_timer, - jiffies + ERROR_DELAY_JIFFIES); - return; - } - } - snd_usbmidi_do_output(ep); -} - -/* - * This is called when some data should be transferred to the device - * (from one or more substreams). - */ -static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep) -{ - unsigned int urb_index; - struct urb* urb; - unsigned long flags; - - spin_lock_irqsave(&ep->buffer_lock, flags); - if (ep->umidi->disconnected) { - spin_unlock_irqrestore(&ep->buffer_lock, flags); - return; - } - - urb_index = ep->next_urb; - for (;;) { - if (!(ep->active_urbs & (1 << urb_index))) { - urb = ep->urbs[urb_index].urb; - urb->transfer_buffer_length = 0; - ep->umidi->usb_protocol_ops->output(ep, urb); - if (urb->transfer_buffer_length == 0) - break; - - dump_urb("sending", urb->transfer_buffer, - urb->transfer_buffer_length); - urb->dev = ep->umidi->dev; - if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0) - break; - ep->active_urbs |= 1 << urb_index; - } - if (++urb_index >= OUTPUT_URBS) - urb_index = 0; - if (urb_index == ep->next_urb) - break; - } - ep->next_urb = urb_index; - spin_unlock_irqrestore(&ep->buffer_lock, flags); -} - -static void snd_usbmidi_out_tasklet(unsigned long data) -{ - struct snd_usb_midi_out_endpoint* ep = (struct snd_usb_midi_out_endpoint *) data; - - snd_usbmidi_do_output(ep); -} - -/* called after transfers had been interrupted due to some USB error */ -static void snd_usbmidi_error_timer(unsigned long data) -{ - struct snd_usb_midi *umidi = (struct snd_usb_midi *)data; - unsigned int i, j; - - spin_lock(&umidi->disc_lock); - if (umidi->disconnected) { - spin_unlock(&umidi->disc_lock); - return; - } - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in; - if (in && in->error_resubmit) { - in->error_resubmit = 0; - for (j = 0; j < INPUT_URBS; ++j) { - in->urbs[j]->dev = umidi->dev; - snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC); - } - } - if (umidi->endpoints[i].out) - snd_usbmidi_do_output(umidi->endpoints[i].out); - } - spin_unlock(&umidi->disc_lock); -} - -/* helper function to send static data that may not DMA-able */ -static int send_bulk_static_data(struct snd_usb_midi_out_endpoint* ep, - const void *data, int len) -{ - int err = 0; - void *buf = kmemdup(data, len, GFP_KERNEL); - if (!buf) - return -ENOMEM; - dump_urb("sending", buf, len); - if (ep->urbs[0].urb) - err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe, - buf, len, NULL, 250); - kfree(buf); - return err; -} - -/* - * Standard USB MIDI protocol: see the spec. - * Midiman protocol: like the standard protocol, but the control byte is the - * fourth byte in each packet, and uses length instead of CIN. - */ - -static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - int i; - - for (i = 0; i + 3 < buffer_length; i += 4) - if (buffer[i] != 0) { - int cable = buffer[i] >> 4; - int length = snd_usbmidi_cin_length[buffer[i] & 0x0f]; - snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length); - } -} - -static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - int i; - - for (i = 0; i + 3 < buffer_length; i += 4) - if (buffer[i + 3] != 0) { - int port = buffer[i + 3] >> 4; - int length = buffer[i + 3] & 3; - snd_usbmidi_input_data(ep, port, &buffer[i], length); - } -} - -/* - * Buggy M-Audio device: running status on input results in a packet that has - * the data bytes but not the status byte and that is marked with CIN 4. - */ -static void snd_usbmidi_maudio_broken_running_status_input( - struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - int i; - - for (i = 0; i + 3 < buffer_length; i += 4) - if (buffer[i] != 0) { - int cable = buffer[i] >> 4; - u8 cin = buffer[i] & 0x0f; - struct usbmidi_in_port *port = &ep->ports[cable]; - int length; - - length = snd_usbmidi_cin_length[cin]; - if (cin == 0xf && buffer[i + 1] >= 0xf8) - ; /* realtime msg: no running status change */ - else if (cin >= 0x8 && cin <= 0xe) - /* channel msg */ - port->running_status_length = length - 1; - else if (cin == 0x4 && - port->running_status_length != 0 && - buffer[i + 1] < 0x80) - /* CIN 4 that is not a SysEx */ - length = port->running_status_length; - else - /* - * All other msgs cannot begin running status. - * (A channel msg sent as two or three CIN 0xF - * packets could in theory, but this device - * doesn't use this format.) - */ - port->running_status_length = 0; - snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length); - } -} - -/* - * CME protocol: like the standard protocol, but SysEx commands are sent as a - * single USB packet preceded by a 0x0F byte. - */ -static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep, - uint8_t *buffer, int buffer_length) -{ - if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f) - snd_usbmidi_standard_input(ep, buffer, buffer_length); - else - snd_usbmidi_input_data(ep, buffer[0] >> 4, - &buffer[1], buffer_length - 1); -} - -/* - * Adds one USB MIDI packet to the output buffer. - */ -static void snd_usbmidi_output_standard_packet(struct urb* urb, uint8_t p0, - uint8_t p1, uint8_t p2, uint8_t p3) -{ - - uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length; - buf[0] = p0; - buf[1] = p1; - buf[2] = p2; - buf[3] = p3; - urb->transfer_buffer_length += 4; -} - -/* - * Adds one Midiman packet to the output buffer. - */ -static void snd_usbmidi_output_midiman_packet(struct urb* urb, uint8_t p0, - uint8_t p1, uint8_t p2, uint8_t p3) -{ - - uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length; - buf[0] = p1; - buf[1] = p2; - buf[2] = p3; - buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f]; - urb->transfer_buffer_length += 4; -} - -/* - * Converts MIDI commands to USB MIDI packets. - */ -static void snd_usbmidi_transmit_byte(struct usbmidi_out_port* port, - uint8_t b, struct urb* urb) -{ - uint8_t p0 = port->cable; - void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) = - port->ep->umidi->usb_protocol_ops->output_packet; - - if (b >= 0xf8) { - output_packet(urb, p0 | 0x0f, b, 0, 0); - } else if (b >= 0xf0) { - switch (b) { - case 0xf0: - port->data[0] = b; - port->state = STATE_SYSEX_1; - break; - case 0xf1: - case 0xf3: - port->data[0] = b; - port->state = STATE_1PARAM; - break; - case 0xf2: - port->data[0] = b; - port->state = STATE_2PARAM_1; - break; - case 0xf4: - case 0xf5: - port->state = STATE_UNKNOWN; - break; - case 0xf6: - output_packet(urb, p0 | 0x05, 0xf6, 0, 0); - port->state = STATE_UNKNOWN; - break; - case 0xf7: - switch (port->state) { - case STATE_SYSEX_0: - output_packet(urb, p0 | 0x05, 0xf7, 0, 0); - break; - case STATE_SYSEX_1: - output_packet(urb, p0 | 0x06, port->data[0], 0xf7, 0); - break; - case STATE_SYSEX_2: - output_packet(urb, p0 | 0x07, port->data[0], port->data[1], 0xf7); - break; - } - port->state = STATE_UNKNOWN; - break; - } - } else if (b >= 0x80) { - port->data[0] = b; - if (b >= 0xc0 && b <= 0xdf) - port->state = STATE_1PARAM; - else - port->state = STATE_2PARAM_1; - } else { /* b < 0x80 */ - switch (port->state) { - case STATE_1PARAM: - if (port->data[0] < 0xf0) { - p0 |= port->data[0] >> 4; - } else { - p0 |= 0x02; - port->state = STATE_UNKNOWN; - } - output_packet(urb, p0, port->data[0], b, 0); - break; - case STATE_2PARAM_1: - port->data[1] = b; - port->state = STATE_2PARAM_2; - break; - case STATE_2PARAM_2: - if (port->data[0] < 0xf0) { - p0 |= port->data[0] >> 4; - port->state = STATE_2PARAM_1; - } else { - p0 |= 0x03; - port->state = STATE_UNKNOWN; - } - output_packet(urb, p0, port->data[0], port->data[1], b); - break; - case STATE_SYSEX_0: - port->data[0] = b; - port->state = STATE_SYSEX_1; - break; - case STATE_SYSEX_1: - port->data[1] = b; - port->state = STATE_SYSEX_2; - break; - case STATE_SYSEX_2: - output_packet(urb, p0 | 0x04, port->data[0], port->data[1], b); - port->state = STATE_SYSEX_0; - break; - } - } -} - -static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint* ep, - struct urb *urb) -{ - int p; - - /* FIXME: lower-numbered ports can starve higher-numbered ports */ - for (p = 0; p < 0x10; ++p) { - struct usbmidi_out_port* port = &ep->ports[p]; - if (!port->active) - continue; - while (urb->transfer_buffer_length + 3 < ep->max_transfer) { - uint8_t b; - if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { - port->active = 0; - break; - } - snd_usbmidi_transmit_byte(port, b, urb); - } - } -} - -static struct usb_protocol_ops snd_usbmidi_standard_ops = { - .input = snd_usbmidi_standard_input, - .output = snd_usbmidi_standard_output, - .output_packet = snd_usbmidi_output_standard_packet, -}; - -static struct usb_protocol_ops snd_usbmidi_midiman_ops = { - .input = snd_usbmidi_midiman_input, - .output = snd_usbmidi_standard_output, - .output_packet = snd_usbmidi_output_midiman_packet, -}; - -static struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = { - .input = snd_usbmidi_maudio_broken_running_status_input, - .output = snd_usbmidi_standard_output, - .output_packet = snd_usbmidi_output_standard_packet, -}; - -static struct usb_protocol_ops snd_usbmidi_cme_ops = { - .input = snd_usbmidi_cme_input, - .output = snd_usbmidi_standard_output, - .output_packet = snd_usbmidi_output_standard_packet, -}; - -/* - * Novation USB MIDI protocol: number of data bytes is in the first byte - * (when receiving) (+1!) or in the second byte (when sending); data begins - * at the third byte. - */ - -static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1) - return; - snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1); -} - -static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint* ep, - struct urb *urb) -{ - uint8_t* transfer_buffer; - int count; - - if (!ep->ports[0].active) - return; - transfer_buffer = urb->transfer_buffer; - count = snd_rawmidi_transmit(ep->ports[0].substream, - &transfer_buffer[2], - ep->max_transfer - 2); - if (count < 1) { - ep->ports[0].active = 0; - return; - } - transfer_buffer[0] = 0; - transfer_buffer[1] = count; - urb->transfer_buffer_length = 2 + count; -} - -static struct usb_protocol_ops snd_usbmidi_novation_ops = { - .input = snd_usbmidi_novation_input, - .output = snd_usbmidi_novation_output, -}; - -/* - * "raw" protocol: used by the MOTU FastLane. - */ - -static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - snd_usbmidi_input_data(ep, 0, buffer, buffer_length); -} - -static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint* ep, - struct urb *urb) -{ - int count; - - if (!ep->ports[0].active) - return; - count = snd_rawmidi_transmit(ep->ports[0].substream, - urb->transfer_buffer, - ep->max_transfer); - if (count < 1) { - ep->ports[0].active = 0; - return; - } - urb->transfer_buffer_length = count; -} - -static struct usb_protocol_ops snd_usbmidi_raw_ops = { - .input = snd_usbmidi_raw_input, - .output = snd_usbmidi_raw_output, -}; - -static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep, - uint8_t *buffer, int buffer_length) -{ - if (buffer_length != 9) - return; - buffer_length = 8; - while (buffer_length && buffer[buffer_length - 1] == 0xFD) - buffer_length--; - if (buffer_length) - snd_usbmidi_input_data(ep, 0, buffer, buffer_length); -} - -static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep, - struct urb *urb) -{ - int count; - - if (!ep->ports[0].active) - return; - count = snd_usb_get_speed(ep->umidi->dev) == USB_SPEED_HIGH ? 1 : 2; - count = snd_rawmidi_transmit(ep->ports[0].substream, - urb->transfer_buffer, - count); - if (count < 1) { - ep->ports[0].active = 0; - return; - } - - memset(urb->transfer_buffer + count, 0xFD, 9 - count); - urb->transfer_buffer_length = count; -} - -static struct usb_protocol_ops snd_usbmidi_122l_ops = { - .input = snd_usbmidi_us122l_input, - .output = snd_usbmidi_us122l_output, -}; - -/* - * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching. - */ - -static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint* ep) -{ - static const u8 init_data[] = { - /* initialization magic: "get version" */ - 0xf0, - 0x00, 0x20, 0x31, /* Emagic */ - 0x64, /* Unitor8 */ - 0x0b, /* version number request */ - 0x00, /* command version */ - 0x00, /* EEPROM, box 0 */ - 0xf7 - }; - send_bulk_static_data(ep, init_data, sizeof(init_data)); - /* while we're at it, pour on more magic */ - send_bulk_static_data(ep, init_data, sizeof(init_data)); -} - -static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint* ep) -{ - static const u8 finish_data[] = { - /* switch to patch mode with last preset */ - 0xf0, - 0x00, 0x20, 0x31, /* Emagic */ - 0x64, /* Unitor8 */ - 0x10, /* patch switch command */ - 0x00, /* command version */ - 0x7f, /* to all boxes */ - 0x40, /* last preset in EEPROM */ - 0xf7 - }; - send_bulk_static_data(ep, finish_data, sizeof(finish_data)); -} - -static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint* ep, - uint8_t* buffer, int buffer_length) -{ - int i; - - /* FF indicates end of valid data */ - for (i = 0; i < buffer_length; ++i) - if (buffer[i] == 0xff) { - buffer_length = i; - break; - } - - /* handle F5 at end of last buffer */ - if (ep->seen_f5) - goto switch_port; - - while (buffer_length > 0) { - /* determine size of data until next F5 */ - for (i = 0; i < buffer_length; ++i) - if (buffer[i] == 0xf5) - break; - snd_usbmidi_input_data(ep, ep->current_port, buffer, i); - buffer += i; - buffer_length -= i; - - if (buffer_length <= 0) - break; - /* assert(buffer[0] == 0xf5); */ - ep->seen_f5 = 1; - ++buffer; - --buffer_length; - - switch_port: - if (buffer_length <= 0) - break; - if (buffer[0] < 0x80) { - ep->current_port = (buffer[0] - 1) & 15; - ++buffer; - --buffer_length; - } - ep->seen_f5 = 0; - } -} - -static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep, - struct urb *urb) -{ - int port0 = ep->current_port; - uint8_t* buf = urb->transfer_buffer; - int buf_free = ep->max_transfer; - int length, i; - - for (i = 0; i < 0x10; ++i) { - /* round-robin, starting at the last current port */ - int portnum = (port0 + i) & 15; - struct usbmidi_out_port* port = &ep->ports[portnum]; - - if (!port->active) - continue; - if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) { - port->active = 0; - continue; - } - - if (portnum != ep->current_port) { - if (buf_free < 2) - break; - ep->current_port = portnum; - buf[0] = 0xf5; - buf[1] = (portnum + 1) & 15; - buf += 2; - buf_free -= 2; - } - - if (buf_free < 1) - break; - length = snd_rawmidi_transmit(port->substream, buf, buf_free); - if (length > 0) { - buf += length; - buf_free -= length; - if (buf_free < 1) - break; - } - } - if (buf_free < ep->max_transfer && buf_free > 0) { - *buf = 0xff; - --buf_free; - } - urb->transfer_buffer_length = ep->max_transfer - buf_free; -} - -static struct usb_protocol_ops snd_usbmidi_emagic_ops = { - .input = snd_usbmidi_emagic_input, - .output = snd_usbmidi_emagic_output, - .init_out_endpoint = snd_usbmidi_emagic_init_out, - .finish_out_endpoint = snd_usbmidi_emagic_finish_out, -}; - - -static void update_roland_altsetting(struct snd_usb_midi* umidi) -{ - struct usb_interface *intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor *intfd; - int is_light_load; - - intf = umidi->iface; - is_light_load = intf->cur_altsetting != intf->altsetting; - if (umidi->roland_load_ctl->private_value == is_light_load) - return; - hostif = &intf->altsetting[umidi->roland_load_ctl->private_value]; - intfd = get_iface_desc(hostif); - snd_usbmidi_input_stop(&umidi->list); - usb_set_interface(umidi->dev, intfd->bInterfaceNumber, - intfd->bAlternateSetting); - snd_usbmidi_input_start(&umidi->list); -} - -static void substream_open(struct snd_rawmidi_substream *substream, int open) -{ - struct snd_usb_midi* umidi = substream->rmidi->private_data; - struct snd_kcontrol *ctl; - - mutex_lock(&umidi->mutex); - if (open) { - if (umidi->opened++ == 0 && umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - update_roland_altsetting(umidi); - } - } else { - if (--umidi->opened == 0 && umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - } - } - mutex_unlock(&umidi->mutex); -} - -static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) -{ - struct snd_usb_midi* umidi = substream->rmidi->private_data; - struct usbmidi_out_port* port = NULL; - int i, j; - - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) - if (umidi->endpoints[i].out) - for (j = 0; j < 0x10; ++j) - if (umidi->endpoints[i].out->ports[j].substream == substream) { - port = &umidi->endpoints[i].out->ports[j]; - break; - } - if (!port) { - snd_BUG(); - return -ENXIO; - } - substream->runtime->private_data = port; - port->state = STATE_UNKNOWN; - substream_open(substream, 1); - return 0; -} - -static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) -{ - substream_open(substream, 0); - return 0; -} - -static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) -{ - struct usbmidi_out_port* port = (struct usbmidi_out_port*)substream->runtime->private_data; - - port->active = up; - if (up) { - if (port->ep->umidi->disconnected) { - /* gobble up remaining bytes to prevent wait in - * snd_rawmidi_drain_output */ - while (!snd_rawmidi_transmit_empty(substream)) - snd_rawmidi_transmit_ack(substream, 1); - return; - } - tasklet_schedule(&port->ep->tasklet); - } -} - -static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream) -{ - struct usbmidi_out_port* port = substream->runtime->private_data; - struct snd_usb_midi_out_endpoint *ep = port->ep; - unsigned int drain_urbs; - DEFINE_WAIT(wait); - long timeout = msecs_to_jiffies(50); - - /* - * The substream buffer is empty, but some data might still be in the - * currently active URBs, so we have to wait for those to complete. - */ - spin_lock_irq(&ep->buffer_lock); - drain_urbs = ep->active_urbs; - if (drain_urbs) { - ep->drain_urbs |= drain_urbs; - do { - prepare_to_wait(&ep->drain_wait, &wait, - TASK_UNINTERRUPTIBLE); - spin_unlock_irq(&ep->buffer_lock); - timeout = schedule_timeout(timeout); - spin_lock_irq(&ep->buffer_lock); - drain_urbs &= ep->drain_urbs; - } while (drain_urbs && timeout); - finish_wait(&ep->drain_wait, &wait); - } - spin_unlock_irq(&ep->buffer_lock); -} - -static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream) -{ - substream_open(substream, 1); - return 0; -} - -static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream) -{ - substream_open(substream, 0); - return 0; -} - -static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) -{ - struct snd_usb_midi* umidi = substream->rmidi->private_data; - - if (up) - set_bit(substream->number, &umidi->input_triggered); - else - clear_bit(substream->number, &umidi->input_triggered); -} - -static struct snd_rawmidi_ops snd_usbmidi_output_ops = { - .open = snd_usbmidi_output_open, - .close = snd_usbmidi_output_close, - .trigger = snd_usbmidi_output_trigger, - .drain = snd_usbmidi_output_drain, -}; - -static struct snd_rawmidi_ops snd_usbmidi_input_ops = { - .open = snd_usbmidi_input_open, - .close = snd_usbmidi_input_close, - .trigger = snd_usbmidi_input_trigger -}; - -static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb, - unsigned int buffer_length) -{ - usb_buffer_free(umidi->dev, buffer_length, - urb->transfer_buffer, urb->transfer_dma); - usb_free_urb(urb); -} - -/* - * Frees an input endpoint. - * May be called when ep hasn't been initialized completely. - */ -static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint* ep) -{ - unsigned int i; - - for (i = 0; i < INPUT_URBS; ++i) - if (ep->urbs[i]) - free_urb_and_buffer(ep->umidi, ep->urbs[i], - ep->urbs[i]->transfer_buffer_length); - kfree(ep); -} - -/* - * Creates an input endpoint. - */ -static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* ep_info, - struct snd_usb_midi_endpoint* rep) -{ - struct snd_usb_midi_in_endpoint* ep; - void* buffer; - unsigned int pipe; - int length; - unsigned int i; - - rep->in = NULL; - ep = kzalloc(sizeof(*ep), GFP_KERNEL); - if (!ep) - return -ENOMEM; - ep->umidi = umidi; - - for (i = 0; i < INPUT_URBS; ++i) { - ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); - if (!ep->urbs[i]) { - snd_usbmidi_in_endpoint_delete(ep); - return -ENOMEM; - } - } - if (ep_info->in_interval) - pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep); - else - pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep); - length = usb_maxpacket(umidi->dev, pipe, 0); - for (i = 0; i < INPUT_URBS; ++i) { - buffer = usb_buffer_alloc(umidi->dev, length, GFP_KERNEL, - &ep->urbs[i]->transfer_dma); - if (!buffer) { - snd_usbmidi_in_endpoint_delete(ep); - return -ENOMEM; - } - if (ep_info->in_interval) - usb_fill_int_urb(ep->urbs[i], umidi->dev, - pipe, buffer, length, - snd_usbmidi_in_urb_complete, - ep, ep_info->in_interval); - else - usb_fill_bulk_urb(ep->urbs[i], umidi->dev, - pipe, buffer, length, - snd_usbmidi_in_urb_complete, ep); - ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; - } - - rep->in = ep; - return 0; -} - -/* - * Frees an output endpoint. - * May be called when ep hasn't been initialized completely. - */ -static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint* ep) -{ - unsigned int i; - - for (i = 0; i < OUTPUT_URBS; ++i) - if (ep->urbs[i].urb) - free_urb_and_buffer(ep->umidi, ep->urbs[i].urb, - ep->max_transfer); - kfree(ep); -} - -/* - * Creates an output endpoint, and initializes output ports. - */ -static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* ep_info, - struct snd_usb_midi_endpoint* rep) -{ - struct snd_usb_midi_out_endpoint* ep; - unsigned int i; - unsigned int pipe; - void* buffer; - - rep->out = NULL; - ep = kzalloc(sizeof(*ep), GFP_KERNEL); - if (!ep) - return -ENOMEM; - ep->umidi = umidi; - - for (i = 0; i < OUTPUT_URBS; ++i) { - ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ep->urbs[i].urb) { - snd_usbmidi_out_endpoint_delete(ep); - return -ENOMEM; - } - ep->urbs[i].ep = ep; - } - if (ep_info->out_interval) - pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep); - else - pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep); - switch (umidi->usb_id) { - default: - ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1); - break; - /* - * Various chips declare a packet size larger than 4 bytes, but - * do not actually work with larger packets: - */ - case USB_ID(0x0a92, 0x1020): /* ESI M4U */ - case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */ - case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */ - case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */ - case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */ - ep->max_transfer = 4; - break; - } - for (i = 0; i < OUTPUT_URBS; ++i) { - buffer = usb_buffer_alloc(umidi->dev, - ep->max_transfer, GFP_KERNEL, - &ep->urbs[i].urb->transfer_dma); - if (!buffer) { - snd_usbmidi_out_endpoint_delete(ep); - return -ENOMEM; - } - if (ep_info->out_interval) - usb_fill_int_urb(ep->urbs[i].urb, umidi->dev, - pipe, buffer, ep->max_transfer, - snd_usbmidi_out_urb_complete, - &ep->urbs[i], ep_info->out_interval); - else - usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev, - pipe, buffer, ep->max_transfer, - snd_usbmidi_out_urb_complete, - &ep->urbs[i]); - ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; - } - - spin_lock_init(&ep->buffer_lock); - tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep); - init_waitqueue_head(&ep->drain_wait); - - for (i = 0; i < 0x10; ++i) - if (ep_info->out_cables & (1 << i)) { - ep->ports[i].ep = ep; - ep->ports[i].cable = i << 4; - } - - if (umidi->usb_protocol_ops->init_out_endpoint) - umidi->usb_protocol_ops->init_out_endpoint(ep); - - rep->out = ep; - return 0; -} - -/* - * Frees everything. - */ -static void snd_usbmidi_free(struct snd_usb_midi* umidi) -{ - int i; - - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; - if (ep->out) - snd_usbmidi_out_endpoint_delete(ep->out); - if (ep->in) - snd_usbmidi_in_endpoint_delete(ep->in); - } - mutex_destroy(&umidi->mutex); - kfree(umidi); -} - -/* - * Unlinks all URBs (must be done before the usb_device is deleted). - */ -void snd_usbmidi_disconnect(struct list_head* p) -{ - struct snd_usb_midi* umidi; - unsigned int i, j; - - umidi = list_entry(p, struct snd_usb_midi, list); - /* - * an URB's completion handler may start the timer and - * a timer may submit an URB. To reliably break the cycle - * a flag under lock must be used - */ - spin_lock_irq(&umidi->disc_lock); - umidi->disconnected = 1; - spin_unlock_irq(&umidi->disc_lock); - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; - if (ep->out) - tasklet_kill(&ep->out->tasklet); - if (ep->out) { - for (j = 0; j < OUTPUT_URBS; ++j) - usb_kill_urb(ep->out->urbs[j].urb); - if (umidi->usb_protocol_ops->finish_out_endpoint) - umidi->usb_protocol_ops->finish_out_endpoint(ep->out); - } - if (ep->in) - for (j = 0; j < INPUT_URBS; ++j) - usb_kill_urb(ep->in->urbs[j]); - /* free endpoints here; later call can result in Oops */ - if (ep->out) { - snd_usbmidi_out_endpoint_delete(ep->out); - ep->out = NULL; - } - if (ep->in) { - snd_usbmidi_in_endpoint_delete(ep->in); - ep->in = NULL; - } - } - del_timer_sync(&umidi->error_timer); -} - -static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi) -{ - struct snd_usb_midi* umidi = rmidi->private_data; - snd_usbmidi_free(umidi); -} - -static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi* umidi, - int stream, int number) -{ - struct list_head* list; - - list_for_each(list, &umidi->rmidi->streams[stream].substreams) { - struct snd_rawmidi_substream *substream = list_entry(list, struct snd_rawmidi_substream, list); - if (substream->number == number) - return substream; - } - return NULL; -} - -/* - * This list specifies names for ports that do not fit into the standard - * "(product) MIDI (n)" schema because they aren't external MIDI ports, - * such as internal control or synthesizer ports. - */ -static struct port_info { - u32 id; - short int port; - short int voices; - const char *name; - unsigned int seq_flags; -} snd_usbmidi_port_info[] = { -#define PORT_INFO(vendor, product, num, name_, voices_, flags) \ - { .id = USB_ID(vendor, product), \ - .port = num, .voices = voices_, \ - .name = name_, .seq_flags = flags } -#define EXTERNAL_PORT(vendor, product, num, name) \ - PORT_INFO(vendor, product, num, name, 0, \ - SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ - SNDRV_SEQ_PORT_TYPE_HARDWARE | \ - SNDRV_SEQ_PORT_TYPE_PORT) -#define CONTROL_PORT(vendor, product, num, name) \ - PORT_INFO(vendor, product, num, name, 0, \ - SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ - SNDRV_SEQ_PORT_TYPE_HARDWARE) -#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \ - PORT_INFO(vendor, product, num, name, voices, \ - SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ - SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ - SNDRV_SEQ_PORT_TYPE_HARDWARE | \ - SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) -#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \ - PORT_INFO(vendor, product, num, name, voices, \ - SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ - SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ - SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ - SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \ - SNDRV_SEQ_PORT_TYPE_HARDWARE | \ - SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) - /* Roland UA-100 */ - CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"), - /* Roland SC-8850 */ - SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128), - SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128), - SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128), - SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128), - EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"), - EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"), - /* Roland U-8 */ - EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"), - CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"), - /* Roland SC-8820 */ - SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64), - SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64), - EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"), - /* Roland SK-500 */ - SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64), - SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64), - EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"), - /* Roland SC-D70 */ - SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64), - SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64), - EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"), - /* Edirol UM-880 */ - CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"), - /* Edirol SD-90 */ - ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128), - ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128), - EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"), - EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"), - /* Edirol UM-550 */ - CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"), - /* Edirol SD-20 */ - ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64), - ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64), - EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"), - /* Edirol SD-80 */ - ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128), - ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128), - EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"), - EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"), - /* Edirol UA-700 */ - EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"), - CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"), - /* Roland VariOS */ - EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"), - EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"), - EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"), - /* Edirol PCR */ - EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"), - EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"), - EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"), - /* BOSS GS-10 */ - EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"), - CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"), - /* Edirol UA-1000 */ - EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"), - CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"), - /* Edirol UR-80 */ - EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"), - EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"), - EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"), - /* Edirol PCR-A */ - EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"), - EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"), - EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"), - /* Edirol UM-3EX */ - CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"), - /* M-Audio MidiSport 8x8 */ - CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"), - CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"), - /* MOTU Fastlane */ - EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"), - EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"), - /* Emagic Unitor8/AMT8/MT4 */ - EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"), - EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"), - EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"), - /* Access Music Virus TI */ - EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"), - PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0, - SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | - SNDRV_SEQ_PORT_TYPE_HARDWARE | - SNDRV_SEQ_PORT_TYPE_SYNTHESIZER), -}; - -static struct port_info *find_port_info(struct snd_usb_midi* umidi, int number) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) { - if (snd_usbmidi_port_info[i].id == umidi->usb_id && - snd_usbmidi_port_info[i].port == number) - return &snd_usbmidi_port_info[i]; - } - return NULL; -} - -static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number, - struct snd_seq_port_info *seq_port_info) -{ - struct snd_usb_midi *umidi = rmidi->private_data; - struct port_info *port_info; - - /* TODO: read port flags from descriptors */ - port_info = find_port_info(umidi, number); - if (port_info) { - seq_port_info->type = port_info->seq_flags; - seq_port_info->midi_voices = port_info->voices; - } -} - -static void snd_usbmidi_init_substream(struct snd_usb_midi* umidi, - int stream, int number, - struct snd_rawmidi_substream ** rsubstream) -{ - struct port_info *port_info; - const char *name_format; - - struct snd_rawmidi_substream *substream = snd_usbmidi_find_substream(umidi, stream, number); - if (!substream) { - snd_printd(KERN_ERR "substream %d:%d not found\n", stream, number); - return; - } - - /* TODO: read port name from jack descriptor */ - port_info = find_port_info(umidi, number); - name_format = port_info ? port_info->name : "%s MIDI %d"; - snprintf(substream->name, sizeof(substream->name), - name_format, umidi->card->shortname, number + 1); - - *rsubstream = substream; -} - -/* - * Creates the endpoints and their ports. - */ -static int snd_usbmidi_create_endpoints(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoints) -{ - int i, j, err; - int out_ports = 0, in_ports = 0; - - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - if (endpoints[i].out_cables) { - err = snd_usbmidi_out_endpoint_create(umidi, &endpoints[i], - &umidi->endpoints[i]); - if (err < 0) - return err; - } - if (endpoints[i].in_cables) { - err = snd_usbmidi_in_endpoint_create(umidi, &endpoints[i], - &umidi->endpoints[i]); - if (err < 0) - return err; - } - - for (j = 0; j < 0x10; ++j) { - if (endpoints[i].out_cables & (1 << j)) { - snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, out_ports, - &umidi->endpoints[i].out->ports[j].substream); - ++out_ports; - } - if (endpoints[i].in_cables & (1 << j)) { - snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, in_ports, - &umidi->endpoints[i].in->ports[j].substream); - ++in_ports; - } - } - } - snd_printdd(KERN_INFO "created %d output and %d input ports\n", - out_ports, in_ports); - return 0; -} - -/* - * Returns MIDIStreaming device capabilities. - */ -static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoints) -{ - struct usb_interface* intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor* intfd; - struct usb_ms_header_descriptor* ms_header; - struct usb_host_endpoint *hostep; - struct usb_endpoint_descriptor* ep; - struct usb_ms_endpoint_descriptor* ms_ep; - int i, epidx; - - intf = umidi->iface; - if (!intf) - return -ENXIO; - hostif = &intf->altsetting[0]; - intfd = get_iface_desc(hostif); - ms_header = (struct usb_ms_header_descriptor*)hostif->extra; - if (hostif->extralen >= 7 && - ms_header->bLength >= 7 && - ms_header->bDescriptorType == USB_DT_CS_INTERFACE && - ms_header->bDescriptorSubtype == UAC_HEADER) - snd_printdd(KERN_INFO "MIDIStreaming version %02x.%02x\n", - ms_header->bcdMSC[1], ms_header->bcdMSC[0]); - else - snd_printk(KERN_WARNING "MIDIStreaming interface descriptor not found\n"); - - epidx = 0; - for (i = 0; i < intfd->bNumEndpoints; ++i) { - hostep = &hostif->endpoint[i]; - ep = get_ep_desc(hostep); - if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep)) - continue; - ms_ep = (struct usb_ms_endpoint_descriptor*)hostep->extra; - if (hostep->extralen < 4 || - ms_ep->bLength < 4 || - ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT || - ms_ep->bDescriptorSubtype != UAC_MS_GENERAL) - continue; - if (usb_endpoint_dir_out(ep)) { - if (endpoints[epidx].out_ep) { - if (++epidx >= MIDI_MAX_ENDPOINTS) { - snd_printk(KERN_WARNING "too many endpoints\n"); - break; - } - } - endpoints[epidx].out_ep = usb_endpoint_num(ep); - if (usb_endpoint_xfer_int(ep)) - endpoints[epidx].out_interval = ep->bInterval; - else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) - /* - * Low speed bulk transfers don't exist, so - * force interrupt transfers for devices like - * ESI MIDI Mate that try to use them anyway. - */ - endpoints[epidx].out_interval = 1; - endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; - snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", - ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); - } else { - if (endpoints[epidx].in_ep) { - if (++epidx >= MIDI_MAX_ENDPOINTS) { - snd_printk(KERN_WARNING "too many endpoints\n"); - break; - } - } - endpoints[epidx].in_ep = usb_endpoint_num(ep); - if (usb_endpoint_xfer_int(ep)) - endpoints[epidx].in_interval = ep->bInterval; - else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) - endpoints[epidx].in_interval = 1; - endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; - snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", - ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); - } - } - return 0; -} - -static int roland_load_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *info) -{ - static const char *const names[] = { "High Load", "Light Load" }; - - info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - info->count = 1; - info->value.enumerated.items = 2; - if (info->value.enumerated.item > 1) - info->value.enumerated.item = 1; - strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); - return 0; -} - -static int roland_load_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *value) -{ - value->value.enumerated.item[0] = kcontrol->private_value; - return 0; -} - -static int roland_load_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *value) -{ - struct snd_usb_midi* umidi = kcontrol->private_data; - int changed; - - if (value->value.enumerated.item[0] > 1) - return -EINVAL; - mutex_lock(&umidi->mutex); - changed = value->value.enumerated.item[0] != kcontrol->private_value; - if (changed) - kcontrol->private_value = value->value.enumerated.item[0]; - mutex_unlock(&umidi->mutex); - return changed; -} - -static struct snd_kcontrol_new roland_load_ctl = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "MIDI Input Mode", - .info = roland_load_info, - .get = roland_load_get, - .put = roland_load_put, - .private_value = 1, -}; - -/* - * On Roland devices, use the second alternate setting to be able to use - * the interrupt input endpoint. - */ -static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi) -{ - struct usb_interface* intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor* intfd; - - intf = umidi->iface; - if (!intf || intf->num_altsetting != 2) - return; - - hostif = &intf->altsetting[1]; - intfd = get_iface_desc(hostif); - if (intfd->bNumEndpoints != 2 || - (get_endpoint(hostif, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK || - (get_endpoint(hostif, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) - return; - - snd_printdd(KERN_INFO "switching to altsetting %d with int ep\n", - intfd->bAlternateSetting); - usb_set_interface(umidi->dev, intfd->bInterfaceNumber, - intfd->bAlternateSetting); - - umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi); - if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0) - umidi->roland_load_ctl = NULL; -} - -/* - * Try to find any usable endpoints in the interface. - */ -static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoint, - int max_endpoints) -{ - struct usb_interface* intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor* intfd; - struct usb_endpoint_descriptor* epd; - int i, out_eps = 0, in_eps = 0; - - if (USB_ID_VENDOR(umidi->usb_id) == 0x0582) - snd_usbmidi_switch_roland_altsetting(umidi); - - if (endpoint[0].out_ep || endpoint[0].in_ep) - return 0; - - intf = umidi->iface; - if (!intf || intf->num_altsetting < 1) - return -ENOENT; - hostif = intf->cur_altsetting; - intfd = get_iface_desc(hostif); - - for (i = 0; i < intfd->bNumEndpoints; ++i) { - epd = get_endpoint(hostif, i); - if (!usb_endpoint_xfer_bulk(epd) && - !usb_endpoint_xfer_int(epd)) - continue; - if (out_eps < max_endpoints && - usb_endpoint_dir_out(epd)) { - endpoint[out_eps].out_ep = usb_endpoint_num(epd); - if (usb_endpoint_xfer_int(epd)) - endpoint[out_eps].out_interval = epd->bInterval; - ++out_eps; - } - if (in_eps < max_endpoints && - usb_endpoint_dir_in(epd)) { - endpoint[in_eps].in_ep = usb_endpoint_num(epd); - if (usb_endpoint_xfer_int(epd)) - endpoint[in_eps].in_interval = epd->bInterval; - ++in_eps; - } - } - return (out_eps || in_eps) ? 0 : -ENOENT; -} - -/* - * Detects the endpoints for one-port-per-endpoint protocols. - */ -static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoints) -{ - int err, i; - - err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS); - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - if (endpoints[i].out_ep) - endpoints[i].out_cables = 0x0001; - if (endpoints[i].in_ep) - endpoints[i].in_cables = 0x0001; - } - return err; -} - -/* - * Detects the endpoints and ports of Yamaha devices. - */ -static int snd_usbmidi_detect_yamaha(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoint) -{ - struct usb_interface* intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor* intfd; - uint8_t* cs_desc; - - intf = umidi->iface; - if (!intf) - return -ENOENT; - hostif = intf->altsetting; - intfd = get_iface_desc(hostif); - if (intfd->bNumEndpoints < 1) - return -ENOENT; - - /* - * For each port there is one MIDI_IN/OUT_JACK descriptor, not - * necessarily with any useful contents. So simply count 'em. - */ - for (cs_desc = hostif->extra; - cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2; - cs_desc += cs_desc[0]) { - if (cs_desc[1] == USB_DT_CS_INTERFACE) { - if (cs_desc[2] == UAC_MIDI_IN_JACK) - endpoint->in_cables = (endpoint->in_cables << 1) | 1; - else if (cs_desc[2] == UAC_MIDI_OUT_JACK) - endpoint->out_cables = (endpoint->out_cables << 1) | 1; - } - } - if (!endpoint->in_cables && !endpoint->out_cables) - return -ENOENT; - - return snd_usbmidi_detect_endpoints(umidi, endpoint, 1); -} - -/* - * Creates the endpoints and their ports for Midiman devices. - */ -static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi* umidi, - struct snd_usb_midi_endpoint_info* endpoint) -{ - struct snd_usb_midi_endpoint_info ep_info; - struct usb_interface* intf; - struct usb_host_interface *hostif; - struct usb_interface_descriptor* intfd; - struct usb_endpoint_descriptor* epd; - int cable, err; - - intf = umidi->iface; - if (!intf) - return -ENOENT; - hostif = intf->altsetting; - intfd = get_iface_desc(hostif); - /* - * The various MidiSport devices have more or less random endpoint - * numbers, so we have to identify the endpoints by their index in - * the descriptor array, like the driver for that other OS does. - * - * There is one interrupt input endpoint for all input ports, one - * bulk output endpoint for even-numbered ports, and one for odd- - * numbered ports. Both bulk output endpoints have corresponding - * input bulk endpoints (at indices 1 and 3) which aren't used. - */ - if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) { - snd_printdd(KERN_ERR "not enough endpoints\n"); - return -ENOENT; - } - - epd = get_endpoint(hostif, 0); - if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) { - snd_printdd(KERN_ERR "endpoint[0] isn't interrupt\n"); - return -ENXIO; - } - epd = get_endpoint(hostif, 2); - if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) { - snd_printdd(KERN_ERR "endpoint[2] isn't bulk output\n"); - return -ENXIO; - } - if (endpoint->out_cables > 0x0001) { - epd = get_endpoint(hostif, 4); - if (!usb_endpoint_dir_out(epd) || - !usb_endpoint_xfer_bulk(epd)) { - snd_printdd(KERN_ERR "endpoint[4] isn't bulk output\n"); - return -ENXIO; - } - } - - ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - ep_info.out_interval = 0; - ep_info.out_cables = endpoint->out_cables & 0x5555; - err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]); - if (err < 0) - return err; - - ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - ep_info.in_interval = get_endpoint(hostif, 0)->bInterval; - ep_info.in_cables = endpoint->in_cables; - err = snd_usbmidi_in_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]); - if (err < 0) - return err; - - if (endpoint->out_cables > 0x0001) { - ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - ep_info.out_cables = endpoint->out_cables & 0xaaaa; - err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[1]); - if (err < 0) - return err; - } - - for (cable = 0; cable < 0x10; ++cable) { - if (endpoint->out_cables & (1 << cable)) - snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, cable, - &umidi->endpoints[cable & 1].out->ports[cable].substream); - if (endpoint->in_cables & (1 << cable)) - snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, cable, - &umidi->endpoints[0].in->ports[cable].substream); - } - return 0; -} - -static struct snd_rawmidi_global_ops snd_usbmidi_ops = { - .get_port_info = snd_usbmidi_get_port_info, -}; - -static int snd_usbmidi_create_rawmidi(struct snd_usb_midi* umidi, - int out_ports, int in_ports) -{ - struct snd_rawmidi *rmidi; - int err; - - err = snd_rawmidi_new(umidi->card, "USB MIDI", - umidi->next_midi_device++, - out_ports, in_ports, &rmidi); - if (err < 0) - return err; - strcpy(rmidi->name, umidi->card->shortname); - rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | - SNDRV_RAWMIDI_INFO_INPUT | - SNDRV_RAWMIDI_INFO_DUPLEX; - rmidi->ops = &snd_usbmidi_ops; - rmidi->private_data = umidi; - rmidi->private_free = snd_usbmidi_rawmidi_free; - snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_usbmidi_output_ops); - snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usbmidi_input_ops); - - umidi->rmidi = rmidi; - return 0; -} - -/* - * Temporarily stop input. - */ -void snd_usbmidi_input_stop(struct list_head* p) -{ - struct snd_usb_midi* umidi; - unsigned int i, j; - - umidi = list_entry(p, struct snd_usb_midi, list); - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i]; - if (ep->in) - for (j = 0; j < INPUT_URBS; ++j) - usb_kill_urb(ep->in->urbs[j]); - } -} - -static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint* ep) -{ - unsigned int i; - - if (!ep) - return; - for (i = 0; i < INPUT_URBS; ++i) { - struct urb* urb = ep->urbs[i]; - urb->dev = ep->umidi->dev; - snd_usbmidi_submit_urb(urb, GFP_KERNEL); - } -} - -/* - * Resume input after a call to snd_usbmidi_input_stop(). - */ -void snd_usbmidi_input_start(struct list_head* p) -{ - struct snd_usb_midi* umidi; - int i; - - umidi = list_entry(p, struct snd_usb_midi, list); - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) - snd_usbmidi_input_start_ep(umidi->endpoints[i].in); -} - -/* - * Creates and registers everything needed for a MIDI streaming interface. - */ -int snd_usbmidi_create(struct snd_card *card, - struct usb_interface* iface, - struct list_head *midi_list, - const struct snd_usb_audio_quirk* quirk) -{ - struct snd_usb_midi* umidi; - struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; - int out_ports, in_ports; - int i, err; - - umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); - if (!umidi) - return -ENOMEM; - umidi->dev = interface_to_usbdev(iface); - umidi->card = card; - umidi->iface = iface; - umidi->quirk = quirk; - umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; - init_timer(&umidi->error_timer); - spin_lock_init(&umidi->disc_lock); - mutex_init(&umidi->mutex); - umidi->usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor), - le16_to_cpu(umidi->dev->descriptor.idProduct)); - umidi->error_timer.function = snd_usbmidi_error_timer; - umidi->error_timer.data = (unsigned long)umidi; - - /* detect the endpoint(s) to use */ - memset(endpoints, 0, sizeof(endpoints)); - switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) { - case QUIRK_MIDI_STANDARD_INTERFACE: - err = snd_usbmidi_get_ms_info(umidi, endpoints); - if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */ - umidi->usb_protocol_ops = - &snd_usbmidi_maudio_broken_running_status_ops; - break; - case QUIRK_MIDI_US122L: - umidi->usb_protocol_ops = &snd_usbmidi_122l_ops; - /* fall through */ - case QUIRK_MIDI_FIXED_ENDPOINT: - memcpy(&endpoints[0], quirk->data, - sizeof(struct snd_usb_midi_endpoint_info)); - err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); - break; - case QUIRK_MIDI_YAMAHA: - err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]); - break; - case QUIRK_MIDI_MIDIMAN: - umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops; - memcpy(&endpoints[0], quirk->data, - sizeof(struct snd_usb_midi_endpoint_info)); - err = 0; - break; - case QUIRK_MIDI_NOVATION: - umidi->usb_protocol_ops = &snd_usbmidi_novation_ops; - err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); - break; - case QUIRK_MIDI_FASTLANE: - umidi->usb_protocol_ops = &snd_usbmidi_raw_ops; - /* - * Interface 1 contains isochronous endpoints, but with the same - * numbers as in interface 0. Since it is interface 1 that the - * USB core has most recently seen, these descriptors are now - * associated with the endpoint numbers. This will foul up our - * attempts to submit bulk/interrupt URBs to the endpoints in - * interface 0, so we have to make sure that the USB core looks - * again at interface 0 by calling usb_set_interface() on it. - */ - usb_set_interface(umidi->dev, 0, 0); - err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); - break; - case QUIRK_MIDI_EMAGIC: - umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops; - memcpy(&endpoints[0], quirk->data, - sizeof(struct snd_usb_midi_endpoint_info)); - err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); - break; - case QUIRK_MIDI_CME: - umidi->usb_protocol_ops = &snd_usbmidi_cme_ops; - err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); - break; - default: - snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); - err = -ENXIO; - break; - } - if (err < 0) { - kfree(umidi); - return err; - } - - /* create rawmidi device */ - out_ports = 0; - in_ports = 0; - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - out_ports += hweight16(endpoints[i].out_cables); - in_ports += hweight16(endpoints[i].in_cables); - } - err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports); - if (err < 0) { - kfree(umidi); - return err; - } - - /* create endpoint/port structures */ - if (quirk && quirk->type == QUIRK_MIDI_MIDIMAN) - err = snd_usbmidi_create_endpoints_midiman(umidi, &endpoints[0]); - else - err = snd_usbmidi_create_endpoints(umidi, endpoints); - if (err < 0) { - snd_usbmidi_free(umidi); - return err; - } - - list_add_tail(&umidi->list, midi_list); - - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) - snd_usbmidi_input_start_ep(umidi->endpoints[i].in); - return 0; -} - -EXPORT_SYMBOL(snd_usbmidi_create); -EXPORT_SYMBOL(snd_usbmidi_input_stop); -EXPORT_SYMBOL(snd_usbmidi_input_start); -EXPORT_SYMBOL(snd_usbmidi_disconnect); diff --git a/sound/usb/usbmidi.h b/sound/usb/usbmidi.h deleted file mode 100644 index 2089ec987c6..00000000000 --- a/sound/usb/usbmidi.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef __USBMIDI_H -#define __USBMIDI_H - -/* maximum number of endpoints per interface */ -#define MIDI_MAX_ENDPOINTS 2 - -/* data for QUIRK_MIDI_FIXED_ENDPOINT */ -struct snd_usb_midi_endpoint_info { - int8_t out_ep; /* ep number, 0 autodetect */ - uint8_t out_interval; /* interval for interrupt endpoints */ - int8_t in_ep; - uint8_t in_interval; - uint16_t out_cables; /* bitmask */ - uint16_t in_cables; /* bitmask */ -}; - -/* for QUIRK_MIDI_YAMAHA, data is NULL */ - -/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info - * structure (out_cables and in_cables only) */ - -/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk - * structures, terminated with .ifnum = -1 */ - -/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ - -/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ - -/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */ - -/* for QUIRK_IGNORE_INTERFACE, data is NULL */ - -/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */ - -/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info - * structure (out_cables and in_cables only) */ - -/* for QUIRK_MIDI_CME, data is NULL */ - -int snd_usbmidi_create(struct snd_card *card, - struct usb_interface *iface, - struct list_head *midi_list, - const struct snd_usb_audio_quirk *quirk); -void snd_usbmidi_input_stop(struct list_head* p); -void snd_usbmidi_input_start(struct list_head* p); -void snd_usbmidi_disconnect(struct list_head *p); - -#endif /* __USBMIDI_H */ diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c index 43d53a36249..5c056837594 100644 --- a/sound/usb/usbmixer.c +++ b/sound/usb/usbmixer.c @@ -42,6 +42,7 @@ #include "usbaudio.h" #include "usbmixer.h" +#include "helper.h" /* */ diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h deleted file mode 100644 index 2b426c1fd0e..00000000000 --- a/sound/usb/usbquirks.h +++ /dev/null @@ -1,2248 +0,0 @@ -/* - * ALSA USB Audio Driver - * - * Copyright (c) 2002 by Takashi Iwai , - * Clemens Ladisch - * - * - * 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. - * - * 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 - */ - -/* - * The contents of this file are part of the driver's id_table. - * - * In a perfect world, this file would be empty. - */ - -/* - * Use this for devices where other interfaces are standard compliant, - * to prevent the quirk being applied to those interfaces. (To work with - * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.) - */ -#define USB_DEVICE_VENDOR_SPEC(vend, prod) \ - .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ - USB_DEVICE_ID_MATCH_PRODUCT | \ - USB_DEVICE_ID_MATCH_INT_CLASS, \ - .idVendor = vend, \ - .idProduct = prod, \ - .bInterfaceClass = USB_CLASS_VENDOR_SPEC - -/* Creative/Toshiba Multimedia Center SB-0500 */ -{ - USB_DEVICE(0x041e, 0x3048), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Toshiba", - .product_name = "SB-0500", - .ifnum = QUIRK_NO_INTERFACE - } -}, - -/* Creative/E-Mu devices */ -{ - USB_DEVICE(0x041e, 0x3010), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Creative Labs", - .product_name = "Sound Blaster MP3+", - .ifnum = QUIRK_NO_INTERFACE - } -}, -{ - /* E-Mu 0202 USB */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f02, - .bInterfaceClass = USB_CLASS_AUDIO, -}, -{ - /* E-Mu 0404 USB */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f04, - .bInterfaceClass = USB_CLASS_AUDIO, -}, -{ - /* E-Mu Tracker Pre */ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f0a, - .bInterfaceClass = USB_CLASS_AUDIO, -}, - -/* - * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface - * class matches do not take effect without an explicit ID match. - */ -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x0850, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08ae, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08c6, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f5, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .idVendor = 0x046d, - .idProduct = 0x08f6, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL -}, -{ - USB_DEVICE(0x046d, 0x0990), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Logitech, Inc.", - .product_name = "QuickCam Pro 9000", - .ifnum = QUIRK_NO_INTERFACE - } -}, - -/* - * Yamaha devices - */ - -#define YAMAHA_DEVICE(id, name) { \ - USB_DEVICE(0x0499, id), \ - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ - .vendor_name = "Yamaha", \ - .product_name = name, \ - .ifnum = QUIRK_ANY_INTERFACE, \ - .type = QUIRK_MIDI_YAMAHA \ - } \ -} -#define YAMAHA_INTERFACE(id, intf, name) { \ - USB_DEVICE_VENDOR_SPEC(0x0499, id), \ - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ - .vendor_name = "Yamaha", \ - .product_name = name, \ - .ifnum = intf, \ - .type = QUIRK_MIDI_YAMAHA \ - } \ -} -YAMAHA_DEVICE(0x1000, "UX256"), -YAMAHA_DEVICE(0x1001, "MU1000"), -YAMAHA_DEVICE(0x1002, "MU2000"), -YAMAHA_DEVICE(0x1003, "MU500"), -YAMAHA_INTERFACE(0x1004, 3, "UW500"), -YAMAHA_DEVICE(0x1005, "MOTIF6"), -YAMAHA_DEVICE(0x1006, "MOTIF7"), -YAMAHA_DEVICE(0x1007, "MOTIF8"), -YAMAHA_DEVICE(0x1008, "UX96"), -YAMAHA_DEVICE(0x1009, "UX16"), -YAMAHA_INTERFACE(0x100a, 3, "EOS BX"), -YAMAHA_DEVICE(0x100c, "UC-MX"), -YAMAHA_DEVICE(0x100d, "UC-KX"), -YAMAHA_DEVICE(0x100e, "S08"), -YAMAHA_DEVICE(0x100f, "CLP-150"), -YAMAHA_DEVICE(0x1010, "CLP-170"), -YAMAHA_DEVICE(0x1011, "P-250"), -YAMAHA_DEVICE(0x1012, "TYROS"), -YAMAHA_DEVICE(0x1013, "PF-500"), -YAMAHA_DEVICE(0x1014, "S90"), -YAMAHA_DEVICE(0x1015, "MOTIF-R"), -YAMAHA_DEVICE(0x1016, "MDP-5"), -YAMAHA_DEVICE(0x1017, "CVP-204"), -YAMAHA_DEVICE(0x1018, "CVP-206"), -YAMAHA_DEVICE(0x1019, "CVP-208"), -YAMAHA_DEVICE(0x101a, "CVP-210"), -YAMAHA_DEVICE(0x101b, "PSR-1100"), -YAMAHA_DEVICE(0x101c, "PSR-2100"), -YAMAHA_DEVICE(0x101d, "CLP-175"), -YAMAHA_DEVICE(0x101e, "PSR-K1"), -YAMAHA_DEVICE(0x101f, "EZ-J24"), -YAMAHA_DEVICE(0x1020, "EZ-250i"), -YAMAHA_DEVICE(0x1021, "MOTIF ES 6"), -YAMAHA_DEVICE(0x1022, "MOTIF ES 7"), -YAMAHA_DEVICE(0x1023, "MOTIF ES 8"), -YAMAHA_DEVICE(0x1024, "CVP-301"), -YAMAHA_DEVICE(0x1025, "CVP-303"), -YAMAHA_DEVICE(0x1026, "CVP-305"), -YAMAHA_DEVICE(0x1027, "CVP-307"), -YAMAHA_DEVICE(0x1028, "CVP-309"), -YAMAHA_DEVICE(0x1029, "CVP-309GP"), -YAMAHA_DEVICE(0x102a, "PSR-1500"), -YAMAHA_DEVICE(0x102b, "PSR-3000"), -YAMAHA_DEVICE(0x102e, "ELS-01/01C"), -YAMAHA_DEVICE(0x1030, "PSR-295/293"), -YAMAHA_DEVICE(0x1031, "DGX-205/203"), -YAMAHA_DEVICE(0x1032, "DGX-305"), -YAMAHA_DEVICE(0x1033, "DGX-505"), -YAMAHA_DEVICE(0x1034, NULL), -YAMAHA_DEVICE(0x1035, NULL), -YAMAHA_DEVICE(0x1036, NULL), -YAMAHA_DEVICE(0x1037, NULL), -YAMAHA_DEVICE(0x1038, NULL), -YAMAHA_DEVICE(0x1039, NULL), -YAMAHA_DEVICE(0x103a, NULL), -YAMAHA_DEVICE(0x103b, NULL), -YAMAHA_DEVICE(0x103c, NULL), -YAMAHA_DEVICE(0x103d, NULL), -YAMAHA_DEVICE(0x103e, NULL), -YAMAHA_DEVICE(0x103f, NULL), -YAMAHA_DEVICE(0x1040, NULL), -YAMAHA_DEVICE(0x1041, NULL), -YAMAHA_DEVICE(0x1042, NULL), -YAMAHA_DEVICE(0x1043, NULL), -YAMAHA_DEVICE(0x1044, NULL), -YAMAHA_DEVICE(0x1045, NULL), -YAMAHA_INTERFACE(0x104e, 0, NULL), -YAMAHA_DEVICE(0x104f, NULL), -YAMAHA_DEVICE(0x1050, NULL), -YAMAHA_DEVICE(0x1051, NULL), -YAMAHA_DEVICE(0x1052, NULL), -YAMAHA_DEVICE(0x2000, "DGP-7"), -YAMAHA_DEVICE(0x2001, "DGP-5"), -YAMAHA_DEVICE(0x2002, NULL), -YAMAHA_DEVICE(0x5000, "CS1D"), -YAMAHA_DEVICE(0x5001, "DSP1D"), -YAMAHA_DEVICE(0x5002, "DME32"), -YAMAHA_DEVICE(0x5003, "DM2000"), -YAMAHA_DEVICE(0x5004, "02R96"), -YAMAHA_DEVICE(0x5005, "ACU16-C"), -YAMAHA_DEVICE(0x5006, "NHB32-C"), -YAMAHA_DEVICE(0x5007, "DM1000"), -YAMAHA_DEVICE(0x5008, "01V96"), -YAMAHA_DEVICE(0x5009, "SPX2000"), -YAMAHA_DEVICE(0x500a, "PM5D"), -YAMAHA_DEVICE(0x500b, "DME64N"), -YAMAHA_DEVICE(0x500c, "DME24N"), -YAMAHA_DEVICE(0x500d, NULL), -YAMAHA_DEVICE(0x500e, NULL), -YAMAHA_DEVICE(0x500f, NULL), -YAMAHA_DEVICE(0x7000, "DTX"), -YAMAHA_DEVICE(0x7010, "UB99"), -#undef YAMAHA_DEVICE -#undef YAMAHA_INTERFACE - -/* - * Roland/RolandED/Edirol/BOSS devices - */ -{ - USB_DEVICE(0x0582, 0x0000), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "UA-100", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, - .channels = 4, - .iface = 0, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .endpoint = 0x01, - .ep_attr = 0x09, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, - .channels = 2, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .attributes = UAC_EP_CS_ATTR_FILL_MAX, - .endpoint = 0x81, - .ep_attr = 0x05, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0007, - .in_cables = 0x0007 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x000f - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SC-8850", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x003f, - .in_cables = 0x003f - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "U-8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0005, - .in_cables = 0x0005 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* Has ID 0x0099 when not in "Advanced Driver" mode. - * The UM-2EX has only one input, but we cannot detect this. */ - USB_DEVICE(0x0582, 0x0005), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0003 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0007), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SC-8820", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0013, - .in_cables = 0x0013 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0008), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "PC-300", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x009d when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0009), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-1", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x000b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SK-500", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0013, - .in_cables = 0x0013 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* thanks to Emiliano Grilli - * for helping researching this data */ - USB_DEVICE(0x0582, 0x000c), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SC-D70", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .iface = 0, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .endpoint = 0x01, - .ep_attr = 0x01, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .endpoint = 0x81, - .ep_attr = 0x01, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0007, - .in_cables = 0x0007 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ /* - * This quirk is for the "Advanced Driver" mode of the Edirol UA-5. - * If the advanced mode switch at the back of the unit is off, the - * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks), - * but offers only 16-bit PCM. - * In advanced mode, the UA-5 will output S24_3LE samples (two - * channels) at the rate indicated on the front switch, including - * the 96kHz sample rate. - */ - USB_DEVICE(0x0582, 0x0010), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-5", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x0013 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0012), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "XV-5050", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x0015 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0014), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-880", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x01ff, - .in_cables = 0x01ff - } - } -}, -{ - /* has ID 0x0017 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0016), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "SD-90", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x000f - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x001c when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x001b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "MMP-2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x001e when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x001d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "V-SYNTH", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x0024 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0023), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-550", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x003f, - .in_cables = 0x003f - } - } -}, -{ - /* - * This quirk is for the "Advanced Driver" mode. If off, the UA-20 - * has ID 0x0026 and is standard compliant, but has only 16-bit PCM - * and no MIDI. - */ - USB_DEVICE(0x0582, 0x0025), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-20", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .endpoint = 0x01, - .ep_attr = 0x01, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .iface = 2, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .endpoint = 0x82, - .ep_attr = 0x01, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 44100, - .rate_max = 44100, - } - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x0028 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0027), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "SD-20", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0007 - } - } -}, -{ - /* has ID 0x002a when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0029), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "SD-80", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x000f - } - } -}, -{ /* - * This quirk is for the "Advanced" modes of the Edirol UA-700. - * If the sample format switch is not in an advanced setting, the - * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks), - * but offers only 16-bit PCM and no MIDI. - */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-700", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 3, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x002e when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x002d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "XV-2020", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x0030 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x002f), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "VariOS", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0007, - .in_cables = 0x0007 - } - } -}, -{ - /* has ID 0x0034 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0033), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "PCR", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0007 - } - } -}, - /* TODO: add Roland M-1000 support */ -{ - /* - * Has ID 0x0038 when not in "Advanced Driver" mode; - * later revisions use IDs 0x0054 and 0x00a2. - */ - USB_DEVICE(0x0582, 0x0037), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "Digital Piano", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* - * This quirk is for the "Advanced Driver" mode. If off, the GS-10 - * has ID 0x003c and is standard compliant, but has only 16-bit PCM - * and no MIDI. - */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "BOSS", - .product_name = "GS-10", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x0041 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0040), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "GI-20", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x0043 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0042), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "RS-70", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x0049 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0047), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - /* .vendor_name = "EDIROL", */ - /* .product_name = "UR-80", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - /* in the 96 kHz modes, only interface 1 is there */ - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x004a when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0048), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - /* .vendor_name = "EDIROL", */ - /* .product_name = "UR-80", */ - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0007 - } - } -}, - /* TODO: add Edirol M-100FX support */ -{ - /* has ID 0x004e when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x004c), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "PCR-A", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x004f when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x004d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "PCR-A", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0007 - } - } -}, -{ - /* - * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX - * is standard compliant, but has only 16-bit PCM. - */ - USB_DEVICE(0x0582, 0x0050), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-3FX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x0052), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-1SX", - .ifnum = 0, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - USB_DEVICE(0x0582, 0x0060), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "EXR Series", - .ifnum = 0, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - /* has ID 0x0067 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0065), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "PCR-1", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0003 - } - } -}, -{ - /* has ID 0x006b when not in "Advanced Driver" mode */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x006a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SP-606", - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x006e when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x006d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "FANTOM-X", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ /* - * This quirk is for the "Advanced" modes of the Edirol UA-25. - * If the switch is not in an advanced setting, the UA-25 has - * ID 0x0582/0x0073 and is standard compliant (no quirks), but - * offers only 16-bit PCM at 44.1 kHz and no MIDI. - */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-25", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x0076 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0075), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "BOSS", - .product_name = "DR-880", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* has ID 0x007b when not in "Advanced Driver" mode */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - /* "RD" or "RD-700SX"? */ - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0003 - } - } -}, -{ - /* has ID 0x0081 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0080), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "G-70", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, - /* TODO: add Roland V-SYNTH XT support */ - /* TODO: add BOSS GT-PRO support */ -{ - /* has ID 0x008c when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x008b), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "PC-50", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, - /* TODO: add Edirol PC-80 support */ -{ - USB_DEVICE(0x0582, 0x0096), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-1EX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0582, 0x009a), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UM-3EX", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x000f - } - } -}, -{ - /* - * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX - * is standard compliant, but has only 16-bit PCM and no MIDI. - */ - USB_DEVICE(0x0582, 0x00a3), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-4FX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } - } - } -}, - /* TODO: add Edirol MD-P1 support */ -{ - USB_DEVICE(0x582, 0x00a6), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "Juno-G", - .ifnum = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - /* Roland SH-201 */ - USB_DEVICE(0x0582, 0x00ad), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SH-201", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* Roland SonicCell */ - USB_DEVICE(0x0582, 0x00c2), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "SonicCell", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* Edirol M-16DX */ - /* FIXME: This quirk gives a good-working capture stream but the - * playback seems problematic because of lacking of sync - * with capture stream. It needs to sync with the capture - * clock. As now, you'll get frequent sound distortions - * via the playback. - */ - USB_DEVICE(0x0582, 0x00c4), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* BOSS GT-10 */ - USB_DEVICE(0x0582, 0x00da), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* Advanced modes of the Edirol UA-25EX. - * For the standard mode, UA-25EX has ID 0582:00e7, which - * offers only 16-bit PCM at 44.1 kHz and no MIDI. - */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "EDIROL", - .product_name = "UA-25EX", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UAXX - }, - { - .ifnum = -1 - } - } - } -}, -{ - /* has ID 0x00ea when not in Advanced Driver mode */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e9), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - /* .vendor_name = "Roland", */ - /* .product_name = "UA-1G", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, - -/* Guillemot devices */ -{ - /* - * This is for the "Windows Edition" where the external MIDI ports are - * the only MIDI ports; the control data is reported through HID - * interfaces. The "Macintosh Edition" has ID 0xd002 and uses standard - * compliant USB MIDI ports for external MIDI and controls. - */ - USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Hercules", - .product_name = "DJ Console (WE)", - .ifnum = 4, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, - -/* Midiman/M-Audio devices */ -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 2x2", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0003 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 1x1", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "Keystation", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 4x4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x000f - } - } -}, -{ - /* - * For hardware revision 1.05; in the later revisions (1.10 and - * 1.21), 0x1031 is the ID for the device without firmware. - * Thanks to Olaf Giesbrecht - */ - USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 8x8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x01ff, - .in_cables = 0x01ff - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 8x8", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x01ff, - .in_cables = 0x01ff - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "MidiSport 2x4", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x000f, - .in_cables = 0x0003 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "Quattro", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - /* - * Interfaces 0-2 are "Windows-compatible", 16-bit only, - * and share endpoints with the other interfaces. - * Ignore them. The other interfaces can do 24 bits, - * but captured samples are big-endian (see usbaudio.c). - */ - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 5, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 6, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 7, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 8, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 9, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "AudioPhile", - .ifnum = 6, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "Ozone", - .ifnum = 3, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "M-Audio", - .product_name = "OmniStudio", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 5, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 6, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 7, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 8, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 9, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ - USB_DEVICE(0x0763, 0x2019), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - /* .vendor_name = "M-Audio", */ - /* .product_name = "Ozone Academic", */ - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_MIDIMAN, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, - -/* Casio devices */ -{ - USB_DEVICE(0x07cf, 0x6801), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Casio", - .product_name = "PL-40R", - .ifnum = 0, - .type = QUIRK_MIDI_YAMAHA - } -}, -{ - /* this ID is used by several devices without a product ID */ - USB_DEVICE(0x07cf, 0x6802), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Casio", - .product_name = "Keyboard", - .ifnum = 0, - .type = QUIRK_MIDI_YAMAHA - } -}, - -/* Mark of the Unicorn devices */ -{ - /* thanks to Robert A. Lerche */ - .match_flags = USB_DEVICE_ID_MATCH_VENDOR | - USB_DEVICE_ID_MATCH_PRODUCT | - USB_DEVICE_ID_MATCH_DEV_SUBCLASS, - .idVendor = 0x07fd, - .idProduct = 0x0001, - .bDeviceSubClass = 2, - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "MOTU", - .product_name = "Fastlane", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_MIDI_FASTLANE - }, - { - .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, - -/* Emagic devices */ -{ - USB_DEVICE(0x086a, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Emagic", - /* .product_name = "Unitor8", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x80ff, - .in_cables = 0x80ff - } - } -}, -{ - USB_DEVICE(0x086a, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Emagic", - /* .product_name = "AMT8", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x80ff, - .in_cables = 0x80ff - } - } -}, -{ - USB_DEVICE(0x086a, 0x0003), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Emagic", - /* .product_name = "MT4", */ - .ifnum = 2, - .type = QUIRK_MIDI_EMAGIC, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x800f, - .in_cables = 0x8003 - } - } -}, - -/* TerraTec devices */ -{ - USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "TerraTec", - .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "TerraTec", - .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "TerraTec", - .product_name = "PHASE 26", - .ifnum = 3, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, -{ - USB_DEVICE(0x0ccd, 0x0028), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "TerraTec", - .product_name = "Aureon5.1MkII", - .ifnum = QUIRK_NO_INTERFACE - } -}, -{ - USB_DEVICE(0x0ccd, 0x0035), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Miditech", - .product_name = "Play'n Roll", - .ifnum = 0, - .type = QUIRK_MIDI_CME - } -}, - -/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ -{ - USB_DEVICE(0x103d, 0x0100), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Stanton", - .product_name = "ScratchAmp", - .ifnum = QUIRK_NO_INTERFACE - } -}, -{ - USB_DEVICE(0x103d, 0x0101), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Stanton", - .product_name = "ScratchAmp", - .ifnum = QUIRK_NO_INTERFACE - } -}, - -/* Novation EMS devices */ -{ - USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Novation", - .product_name = "ReMOTE Audio/XStation", - .ifnum = 4, - .type = QUIRK_MIDI_NOVATION - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Novation", - .product_name = "Speedio", - .ifnum = 3, - .type = QUIRK_MIDI_NOVATION - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Novation", - .product_name = "ReMOTE25", - .ifnum = 0, - .type = QUIRK_MIDI_NOVATION - } -}, - -/* Access Music devices */ -{ - /* VirusTI Desktop */ - USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = &(const struct snd_usb_audio_quirk[]) { - { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &(const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0003 - } - }, - { - .ifnum = 4, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -}, - -/* */ -{ - /* aka. Serato Scratch Live DJ Box */ - USB_DEVICE(0x13e5, 0x0001), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Rane", - .product_name = "SL-1", - .ifnum = QUIRK_NO_INTERFACE - } -}, - -/* Miditech devices */ -{ - USB_DEVICE(0x4752, 0x0011), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Miditech", - .product_name = "Midistart-2", - .ifnum = 0, - .type = QUIRK_MIDI_CME - } -}, - -/* Central Music devices */ -{ - /* this ID used by both Miditech MidiStudio-2 and CME UF-x */ - USB_DEVICE(0x7104, 0x2202), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = 0, - .type = QUIRK_MIDI_CME - } -}, - -/* Hauppauge HVR-950Q and HVR-850 */ -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7200), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7201), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7202), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7203), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7204), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7205), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7250), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-950Q", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, -{ - USB_DEVICE_VENDOR_SPEC(0x2040, 0x7230), - .match_flags = USB_DEVICE_ID_MATCH_DEVICE | - USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Hauppauge", - .product_name = "HVR-850", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_AUDIO_ALIGN_TRANSFER, - } -}, - -/* Digidesign Mbox */ -{ - /* Thanks to Clemens Ladisch */ - USB_DEVICE(0x0dba, 0x1000), - .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { - .vendor_name = "Digidesign", - .product_name = "MBox", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]){ - { - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE, - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_FIXED_ENDPOINT, - .data = &(const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3BE, - .channels = 2, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, - .endpoint = 0x02, - .ep_attr = 0x01, - .maxpacksize = 0x130, - .rates = SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000, - .rate_min = 44100, - .rate_max = 48000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { - 44100, 48000 - } - } - }, - { - .ifnum = -1 - } - } - - } -}, - -{ - /* - * Some USB MIDI devices don't have an audio control interface, - * so we have to grab MIDI streaming interfaces here. - */ - .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | - USB_DEVICE_ID_MATCH_INT_SUBCLASS, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_MIDI_STANDARD_INTERFACE - } -}, - -#undef USB_DEVICE_VENDOR_SPEC diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 4f6518c9b05..5f7b942ff57 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -25,7 +25,7 @@ #define MODNAME "US122L" #include "usb_stream.c" #include "../usbaudio.h" -#include "../usbmidi.h" +#include "../midi.h" #include "us122l.h" MODULE_AUTHOR("Karsten Wiese "); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index 9ab97b40a35..e43c0a86441 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -1,7 +1,7 @@ #ifndef USBUSX2Y_H #define USBUSX2Y_H #include "../usbaudio.h" -#include "../usbmidi.h" +#include "../midi.h" #include "usbus428ctldefs.h" #define NRURBS 2 -- cgit v1.2.3-18-g5258 From e11b4e0e4f5ab40ec342dc07b7201c09a45f9574 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Thu, 4 Mar 2010 19:46:14 +0100 Subject: ALSA: usb-audio: rename substream format field to altset_idx The snd_usb_substream::format field actually contains the index of the current alternate setting, so rename it to altset_idx to avoid confusion. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/card.h | 2 +- sound/usb/pcm.c | 8 ++++---- sound/usb/proc.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index 71f03c15103..856d71b7407 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -54,7 +54,7 @@ struct snd_usb_substream { struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ unsigned int cur_rate; /* current rate (for hw_params callback) */ unsigned int period_bytes; /* current period bytes (for hw_params callback) */ - unsigned int format; /* USB data format */ + unsigned int altset_idx; /* USB data format: index of alternate setting */ unsigned int datapipe; /* the data i/o pipe */ unsigned int syncpipe; /* 1 - async out or adaptive in */ unsigned int datainterval; /* log_2 of data packet interval */ diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 87863ccf906..c3d5a977cfe 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -202,11 +202,11 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) return -EIO; } subs->interface = -1; - subs->format = 0; + subs->altset_idx = 0; } /* set interface */ - if (subs->interface != fmt->iface || subs->format != fmt->altset_idx) { + if (subs->interface != fmt->iface || subs->altset_idx != fmt->altset_idx) { if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) { snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n", dev->devnum, fmt->iface, fmt->altsetting); @@ -214,7 +214,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) } snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting); subs->interface = fmt->iface; - subs->format = fmt->altset_idx; + subs->altset_idx = fmt->altset_idx; } /* create a data pipe */ @@ -771,7 +771,7 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) struct snd_usb_substream *subs = &as->substream[direction]; subs->interface = -1; - subs->format = 0; + subs->altset_idx = 0; runtime->hw = snd_usb_hardware; runtime->private_data = subs; subs->pcm_substream = substream; diff --git a/sound/usb/proc.c b/sound/usb/proc.c index be3065ea1af..78fc3ba2522 100644 --- a/sound/usb/proc.c +++ b/sound/usb/proc.c @@ -116,7 +116,7 @@ static void proc_dump_substream_status(struct snd_usb_substream *subs, struct sn unsigned int i; snd_iprintf(buffer, " Status: Running\n"); snd_iprintf(buffer, " Interface = %d\n", subs->interface); - snd_iprintf(buffer, " Altset = %d\n", subs->format); + snd_iprintf(buffer, " Altset = %d\n", subs->altset_idx); snd_iprintf(buffer, " URBs = %d [ ", subs->nurbs); for (i = 0; i < subs->nurbs; i++) snd_iprintf(buffer, "%d ", subs->dataurb[i].packets); -- cgit v1.2.3-18-g5258 From 015eb0b08150c6fef843efe22609589ead3d4fb8 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Thu, 4 Mar 2010 19:46:15 +0100 Subject: ALSA: usb-audio: use a format bitmask per alternate setting In preparation for USB audio 2.0 support, change the audioformat structure so that it uses a bitmask to specify possible formats. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/card.h | 2 +- sound/usb/endpoint.c | 4 ++-- sound/usb/format.c | 10 +++++----- sound/usb/pcm.c | 13 ++++++++++--- sound/usb/proc.c | 9 +++++++-- sound/usb/quirks-table.h | 12 ++++++------ sound/usb/quirks.c | 2 +- sound/usb/urb.c | 4 ++-- 8 files changed, 34 insertions(+), 22 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index 856d71b7407..ed92420c109 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -9,7 +9,7 @@ struct audioformat { struct list_head list; - snd_pcm_format_t format; /* format type */ + u64 formats; /* ALSA format bits */ unsigned int channels; /* # channels */ unsigned int fmt_type; /* USB audio format type (1-3) */ unsigned int frame_size; /* samples per frame for non-audio */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 3f53dee1270..d65235c0106 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -94,7 +94,7 @@ int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct au if (subs->endpoint == fp->endpoint) { list_add_tail(&fp->list, &subs->fmt_list); subs->num_formats++; - subs->formats |= 1ULL << fp->format; + subs->formats |= fp->formats; return 0; } } @@ -268,7 +268,7 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) */ if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 && fp && fp->altsetting == 1 && fp->channels == 1 && - fp->format == SNDRV_PCM_FORMAT_S16_LE && + fp->formats == SNDRV_PCM_FMTBIT_S16_LE && protocol == UAC_VERSION_1 && le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == fp->maxpacksize * 2) diff --git a/sound/usb/format.c b/sound/usb/format.c index cbfe0c23dbd..87f07f042c6 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -323,7 +323,7 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, return -1; } - fp->format = pcm_format; + fp->formats = 1uLL << pcm_format; /* gather possible sample rates */ /* audio class v1 reports possible sample rates as part of the @@ -365,16 +365,16 @@ static int parse_audio_format_ii(struct snd_usb_audio *chip, switch (format) { case UAC_FORMAT_TYPE_II_AC3: /* FIXME: there is no AC3 format defined yet */ - // fp->format = SNDRV_PCM_FORMAT_AC3; - fp->format = SNDRV_PCM_FORMAT_U8; /* temporarily hack to receive byte streams */ + // fp->formats = SNDRV_PCM_FMTBIT_AC3; + fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */ break; case UAC_FORMAT_TYPE_II_MPEG: - fp->format = SNDRV_PCM_FORMAT_MPEG; + fp->formats = SNDRV_PCM_FMTBIT_MPEG; break; default: snd_printd(KERN_INFO "%d:%u:%d : unknown format tag %#x is detected. processed as MPEG.\n", chip->dev->devnum, fp->iface, fp->altsetting, format); - fp->format = SNDRV_PCM_FORMAT_MPEG; + fp->formats = SNDRV_PCM_FMTBIT_MPEG; break; } diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index c3d5a977cfe..bd0f84f3a9d 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -58,7 +58,9 @@ static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned list_for_each(p, &subs->fmt_list) { struct audioformat *fp; fp = list_entry(p, struct audioformat, list); - if (fp->format != format || fp->channels != channels) + if (!(fp->formats & (1uLL << format))) + continue; + if (fp->channels != channels) continue; if (rate < fp->rate_min || rate > fp->rate_max) continue; @@ -428,10 +430,15 @@ static int hw_check_valid_format(struct snd_usb_substream *subs, struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); + struct snd_mask check_fmts; unsigned int ptime; /* check the format */ - if (!snd_mask_test(fmts, fp->format)) { + snd_mask_none(&check_fmts); + check_fmts.bits[0] = (u32)fp->formats; + check_fmts.bits[1] = (u32)(fp->formats >> 32); + snd_mask_intersect(&check_fmts, fmts); + if (snd_mask_empty(&check_fmts)) { hwc_debug(" > check: no supported format %d\n", fp->format); return 0; } @@ -584,7 +591,7 @@ static int hw_rule_format(struct snd_pcm_hw_params *params, fp = list_entry(p, struct audioformat, list); if (!hw_check_valid_format(subs, params, fp)) continue; - fbits |= (1ULL << fp->format); + fbits |= fp->formats; } oldbits[0] = fmt->bits[0]; diff --git a/sound/usb/proc.c b/sound/usb/proc.c index 78fc3ba2522..f5e3f356b95 100644 --- a/sound/usb/proc.c +++ b/sound/usb/proc.c @@ -79,11 +79,16 @@ static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct s list_for_each(p, &subs->fmt_list) { struct audioformat *fp; + snd_pcm_format_t fmt; fp = list_entry(p, struct audioformat, list); snd_iprintf(buffer, " Interface %d\n", fp->iface); snd_iprintf(buffer, " Altset %d\n", fp->altsetting); - snd_iprintf(buffer, " Format: %s\n", - snd_pcm_format_name(fp->format)); + snd_iprintf(buffer, " Format:"); + for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt) + if (fp->formats & (1uLL << fmt)) + snd_iprintf(buffer, " %s", + snd_pcm_format_name(fmt)); + snd_iprintf(buffer, "\n"); snd_iprintf(buffer, " Channels: %d\n", fp->channels); snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", fp->endpoint & USB_ENDPOINT_NUMBER_MASK, diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 2b426c1fd0e..6e8651d2ee9 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -279,7 +279,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 0, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 4, .iface = 0, .altsetting = 1, @@ -296,7 +296,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 1, .altsetting = 1, @@ -580,7 +580,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 0, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 0, .altsetting = 1, @@ -597,7 +597,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, .altsetting = 1, @@ -793,7 +793,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, .altsetting = 1, @@ -810,7 +810,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 2, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 2, .altsetting = 1, diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4c16920844e..99a19ba4320 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -174,7 +174,7 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, const struct snd_usb_audio_quirk *quirk) { static const struct audioformat ua_format = { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .fmt_type = UAC_FORMAT_TYPE_I, .altsetting = 1, diff --git a/sound/usb/urb.c b/sound/usb/urb.c index e9c339f7586..ad50d439892 100644 --- a/sound/usb/urb.c +++ b/sound/usb/urb.c @@ -662,7 +662,7 @@ static int prepare_nodata_playback_urb(struct snd_usb_substream *subs, urb->number_of_packets = ctx->packets; urb->transfer_buffer_length = offs * stride; memset(urb->transfer_buffer, - subs->cur_audiofmt->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0, + runtime->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0, offs * stride); return 0; } @@ -924,7 +924,7 @@ void snd_usb_init_substream(struct snd_usb_stream *as, snd_usb_set_pcm_ops(as->pcm, stream); list_add_tail(&fp->list, &subs->fmt_list); - subs->formats |= 1ULL << fp->format; + subs->formats |= fp->formats; subs->endpoint = fp->endpoint; subs->num_formats++; subs->fmt_type = fp->fmt_type; -- cgit v1.2.3-18-g5258 From 29088fef3e3f62147c1dd53d764da4f04bf3188d Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Thu, 4 Mar 2010 19:46:16 +0100 Subject: ALSA: usb-audio: support multiple formats with audio class v2 devices Change the parser to correctly handle v2 descriptors with multiple format bits set. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/format.c | 93 ++++++++++++++++++++---------------------------- sound/usb/quirks-table.h | 2 +- 2 files changed, 39 insertions(+), 56 deletions(-) diff --git a/sound/usb/format.c b/sound/usb/format.c index 87f07f042c6..b613e0aaeb6 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -37,19 +37,20 @@ * @format: the format tag (wFormatTag) * @fmt: the format type descriptor */ -static int parse_audio_format_i_type(struct snd_usb_audio *chip, +static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, struct audioformat *fp, int format, void *_fmt, int protocol) { - int pcm_format, i; int sample_width, sample_bytes; + u64 pcm_formats; switch (protocol) { case UAC_VERSION_1: { struct uac_format_type_i_discrete_descriptor *fmt = _fmt; sample_width = fmt->bBitResolution; sample_bytes = fmt->bSubframeSize; + format = 1 << format; break; } @@ -57,24 +58,7 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct uac_format_type_i_ext_descriptor *fmt = _fmt; sample_width = fmt->bBitResolution; sample_bytes = fmt->bSubslotSize; - - /* - * FIXME - * USB audio class v2 devices specify a bitmap of possible - * audio formats rather than one fix value. For now, we just - * pick one of them and report that as the only possible - * value for this setting. - * The bit allocation map is in fact compatible to the - * wFormatTag of the v1 AS streaming descriptors, which is why - * we can simply map the matrix. - */ - - for (i = 0; i < 5; i++) - if (format & (1UL << i)) { - format = i + 1; - break; - } - + format <<= 1; break; } @@ -82,15 +66,15 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, return -EINVAL; } - /* FIXME: correct endianess and sign? */ - pcm_format = -1; + pcm_formats = 0; - switch (format) { - case UAC_FORMAT_TYPE_I_UNDEFINED: /* some devices don't define this correctly... */ + if (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED)) { + /* some devices don't define this correctly... */ snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n", chip->dev->devnum, fp->iface, fp->altsetting); - /* fall-through */ - case UAC_FORMAT_TYPE_I_PCM: + format = 1 << UAC_FORMAT_TYPE_I_PCM; + } + if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) { if (sample_width > sample_bytes * 8) { snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n", chip->dev->devnum, fp->iface, fp->altsetting, @@ -99,22 +83,22 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, /* check the format byte size */ switch (sample_bytes) { case 1: - pcm_format = SNDRV_PCM_FORMAT_S8; + pcm_formats |= SNDRV_PCM_FMTBIT_S8; break; case 2: if (snd_usb_is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S16_BE; /* grrr, big endian!! */ + pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */ else - pcm_format = SNDRV_PCM_FORMAT_S16_LE; + pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE; break; case 3: if (snd_usb_is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S24_3BE; /* grrr, big endian!! */ + pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */ else - pcm_format = SNDRV_PCM_FORMAT_S24_3LE; + pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE; break; case 4: - pcm_format = SNDRV_PCM_FORMAT_S32_LE; + pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE; break; default: snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n", @@ -122,30 +106,29 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, sample_width, sample_bytes); break; } - break; - case UAC_FORMAT_TYPE_I_PCM8: - pcm_format = SNDRV_PCM_FORMAT_U8; - + } + if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) { /* Dallas DS4201 workaround: it advertises U8 format, but really supports S8. */ if (chip->usb_id == USB_ID(0x04fa, 0x4201)) - pcm_format = SNDRV_PCM_FORMAT_S8; - break; - case UAC_FORMAT_TYPE_I_IEEE_FLOAT: - pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE; - break; - case UAC_FORMAT_TYPE_I_ALAW: - pcm_format = SNDRV_PCM_FORMAT_A_LAW; - break; - case UAC_FORMAT_TYPE_I_MULAW: - pcm_format = SNDRV_PCM_FORMAT_MU_LAW; - break; - default: - snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n", + pcm_formats |= SNDRV_PCM_FMTBIT_S8; + else + pcm_formats |= SNDRV_PCM_FMTBIT_U8; + } + if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) { + pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE; + } + if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) { + pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW; + } + if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) { + pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW; + } + if (format & ~0x3f) { + snd_printk(KERN_INFO "%d:%u:%d : unsupported format bits %#x\n", chip->dev->devnum, fp->iface, fp->altsetting, format); - break; } - return pcm_format; + return pcm_formats; } @@ -317,14 +300,14 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, default: pcm_format = SNDRV_PCM_FORMAT_S16_LE; } + fp->formats = 1uLL << pcm_format; } else { - pcm_format = parse_audio_format_i_type(chip, fp, format, fmt, protocol); - if (pcm_format < 0) + fp->formats = parse_audio_format_i_type(chip, fp, format, + fmt, protocol); + if (!fp->formats) return -1; } - fp->formats = 1uLL << pcm_format; - /* gather possible sample rates */ /* audio class v1 reports possible sample rates as part of the * proprietary class specific descriptor. diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 6e8651d2ee9..81c5f8a312c 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2203,7 +2203,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = &(const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3BE, + .formats = SNDRV_PCM_FMTBIT_S24_3BE, .channels = 2, .iface = 1, .altsetting = 1, -- cgit v1.2.3-18-g5258 From 767d75ad1c08c31646498a13837a5c59db90ccad Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 4 Mar 2010 19:46:17 +0100 Subject: ALSA: usb-audio: add support for samplerate setting on v2 devices Sample rate setting is done with a 4-byte long class request that addresses the interface. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 4 +- sound/usb/pcm.c | 173 +++++++++++++++++++++++++++++++++++++-------------- sound/usb/pcm.h | 4 +- sound/usb/quirks.c | 4 +- 4 files changed, 133 insertions(+), 52 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index d65235c0106..91850f84cad 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -350,8 +350,8 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) } /* try to set the interface... */ usb_set_interface(chip->dev, iface_no, altno); - snd_usb_init_pitch(chip->dev, iface_no, alts, fp); - snd_usb_init_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max); + snd_usb_init_pitch(chip, iface_no, alts, fp); + snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max); } return 0; } diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index bd0f84f3a9d..e0f3f87f99a 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -107,69 +107,150 @@ static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned return found; } +static int init_pitch_v1(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt) +{ + struct usb_device *dev = chip->dev; + unsigned int ep; + unsigned char data[1]; + int err; + + ep = get_endpoint(alts, 0)->bEndpointAddress; + + /* if endpoint doesn't have pitch control, bail out */ + if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL)) + return 0; + + data[0] = 1; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", + dev->devnum, iface, ep); + return err; + } + + return 0; +} /* * initialize the picth control and sample rate */ -int snd_usb_init_pitch(struct usb_device *dev, int iface, +int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt) { + struct usb_interface_descriptor *altsd = get_iface_desc(alts); + + switch (altsd->bInterfaceProtocol) { + case UAC_VERSION_1: + return init_pitch_v1(chip, iface, alts, fmt); + + case UAC_VERSION_2: + /* not implemented yet */ + return 0; + } + + return -EINVAL; +} + +static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; unsigned int ep; - unsigned char data[1]; - int err; + unsigned char data[3]; + int err, crate; ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has pitch control, enable it */ - if (fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL) { - data[0] = 1; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", - dev->devnum, iface, ep); - return err; - } + /* if endpoint doesn't have sampling rate control, bail out */ + if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE)) { + snd_printk(KERN_WARNING "%d:%d:%d: endpoint lacks sample rate attribute bit, cannot set.\n", + dev->devnum, iface, fmt->altsetting); + return 0; + } + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", + dev->devnum, iface, fmt->altsetting, rate, ep); + return err; } + if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", + dev->devnum, iface, fmt->altsetting, ep); + return 0; /* some devices don't support reading */ + } + crate = data[0] | (data[1] << 8) | (data[2] << 16); + if (crate != rate) { + snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); + // runtime->rate = crate; + } + return 0; } -int snd_usb_init_sample_rate(struct usb_device *dev, int iface, +static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; + unsigned char data[4]; + int err, crate; + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + data[3] = rate >> 24; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0100, chip->clock_id << 8, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d (v2)\n", + dev->devnum, iface, fmt->altsetting, rate); + return err; + } + if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 0x0100, chip->clock_id << 8, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq (v2)\n", + dev->devnum, iface, fmt->altsetting); + return err; + } + crate = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + if (crate != rate) + snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); + + return 0; +} + +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt, int rate) { - unsigned int ep; - unsigned char data[3]; - int err; + struct usb_interface_descriptor *altsd = get_iface_desc(alts); - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has sampling rate control, set it */ - if (fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE) { - int crate; - data[0] = rate; - data[1] = rate >> 8; - data[2] = rate >> 16; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", - dev->devnum, iface, fmt->altsetting, rate, ep); - return err; - } - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", - dev->devnum, iface, fmt->altsetting, ep); - return 0; /* some devices don't support reading */ - } - crate = data[0] | (data[1] << 8) | (data[2] << 16); - if (crate != rate) { - snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); - // runtime->rate = crate; - } + switch (altsd->bInterfaceProtocol) { + case UAC_VERSION_1: + return set_sample_rate_v1(chip, iface, alts, fmt, rate); + + case UAC_VERSION_2: + return set_sample_rate_v2(chip, iface, alts, fmt, rate); } - return 0; + + return -EINVAL; } /* @@ -280,7 +361,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) if (fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX) subs->fill_max = 1; - if ((err = snd_usb_init_pitch(dev, subs->interface, alts, fmt)) < 0) + if ((err = snd_usb_init_pitch(subs->stream->chip, subs->interface, alts, fmt)) < 0) return err; subs->cur_audiofmt = fmt; @@ -343,7 +424,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream, struct usb_interface *iface; iface = usb_ifnum_to_if(subs->dev, fmt->iface); alts = &iface->altsetting[fmt->altset_idx]; - ret = snd_usb_init_sample_rate(subs->dev, subs->interface, alts, fmt, rate); + ret = snd_usb_init_sample_rate(subs->stream->chip, subs->interface, alts, fmt, rate); if (ret < 0) return ret; subs->cur_rate = rate; diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h index 85856016e05..1c931b68f3b 100644 --- a/sound/usb/pcm.h +++ b/sound/usb/pcm.h @@ -3,11 +3,11 @@ void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); -int snd_usb_init_pitch(struct usb_device *dev, int iface, +int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt); -int snd_usb_init_sample_rate(struct usb_device *dev, int iface, +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, struct usb_host_interface *alts, struct audioformat *fmt, int rate); diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 99a19ba4320..0c0b23b6379 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -159,8 +159,8 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip, fp->datainterval = snd_usb_parse_datainterval(chip, alts); fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); usb_set_interface(chip->dev, fp->iface, 0); - snd_usb_init_pitch(chip->dev, fp->iface, alts, fp); - snd_usb_init_sample_rate(chip->dev, fp->iface, alts, fp, fp->rate_max); + snd_usb_init_pitch(chip, fp->iface, alts, fp); + snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max); return 0; } -- cgit v1.2.3-18-g5258 From bc6552f4717e07e7737b5dc17883c2ff99f2a315 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 5 Mar 2010 16:27:15 +0000 Subject: ASoC: Add 16/16 registers to soc-cache I2C only at the minute. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-cache.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 5869dc3be78..bf593a834f5 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -366,6 +366,84 @@ static int snd_soc_16_8_spi_write(void *control_data, const char *data, #define snd_soc_16_8_spi_write NULL #endif +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec, + unsigned int r) +{ + struct i2c_msg xfer[2]; + u16 reg = cpu_to_be16(r); + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 2; + xfer[0].buf = (u8 *)® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return be16_to_cpu(data); +} +#else +#define snd_soc_16_16_read_i2c NULL +#endif + +static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size || + snd_soc_codec_volatile_register(codec, reg)) { + if (codec->cache_only) + return -EINVAL; + + return codec->hw_read(codec, reg); + } + + return cache[reg]; +} + +static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[4]; + int ret; + + data[0] = (reg >> 8) & 0xff; + data[1] = reg & 0xff; + data[2] = (value >> 8) & 0xff; + data[3] = value & 0xff; + + if (reg < codec->reg_cache_size) + cache[reg] = value; + + if (codec->cache_only) { + codec->cache_sync = 1; + return 0; + } + + ret = codec->hw_write(codec->control_data, data, 4); + if (ret == 4) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} static struct { int addr_bits; @@ -400,6 +478,11 @@ static struct { .i2c_read = snd_soc_16_8_read_i2c, .spi_write = snd_soc_16_8_spi_write, }, + { + .addr_bits = 16, .data_bits = 16, + .write = snd_soc_16_16_write, .read = snd_soc_16_16_read, + .i2c_read = snd_soc_16_16_read_i2c, + }, }; /** -- cgit v1.2.3-18-g5258 From 1d24452b5536bd5c0c7721887a38d736b885014f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 4 Mar 2010 11:03:26 +0000 Subject: ASoC: Remove unused pmdown_time flag The flag is no longer used in the code so it just wastes a bit. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/soc-dapm.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index c0922a03422..2c8eb0a331c 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -427,7 +427,6 @@ struct snd_soc_dapm_widget { unsigned char ext:1; /* has external widgets */ unsigned char muted:1; /* muted for pop reduction */ unsigned char suspend:1; /* was active before suspend */ - unsigned char pmdown:1; /* waiting for timeout */ int (*power_check)(struct snd_soc_dapm_widget *w); -- cgit v1.2.3-18-g5258 From 692247196dd845eef02f26bf2959719b82e935c6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 3 Mar 2010 14:57:09 +0000 Subject: ASoC: Improve DAPM pop_wait delays Currently during pop/click debug we're inserting a delay both after every log message we generate and at explicit points in the sequence, slowing things down even further than they need to be especially when many writes get coalesced by the sequence generation code. Remove the per-printk delay and ensure that we have explicit delays where we say we want them. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-dapm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 6c335109578..86ded22e36a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -97,7 +97,6 @@ static void pop_dbg(u32 pop_time, const char *fmt, ...) if (pop_time) { vprintk(fmt, args); - pop_wait(pop_time); } va_end(args); @@ -314,8 +313,8 @@ static int dapm_update_bits(struct snd_soc_dapm_widget *widget) pop_dbg(codec->pop_time, "pop test %s : %s in %d ms\n", widget->name, widget->power ? "on" : "off", codec->pop_time); - snd_soc_write(codec, widget->reg, new); pop_wait(codec->pop_time); + snd_soc_write(codec, widget->reg, new); } pr_debug("reg %x old %x new %x change %d\n", widget->reg, old, new, change); @@ -1075,6 +1074,7 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n", codec->pop_time); + pop_wait(codec->pop_time); return 0; } -- cgit v1.2.3-18-g5258 From 3a9d620278981f367a77a8888c0d59a70473b056 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 1 Mar 2010 10:10:43 +0900 Subject: ASoC: da7210: Add 8/12/16/24/32/48/96 kHz rate support Signed-off-by: Phil Edworthy Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/da7210.c | 67 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index cf2975a7294..3bd867de597 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -74,15 +74,14 @@ /* INMIX_R bit fields */ #define DA7210_IN_R_EN (1 << 7) -/* ADC_HPF bit fields */ -#define DA7210_ADC_VOICE_EN (1 << 7) - /* ADC bit fields */ #define DA7210_ADC_L_EN (1 << 3) #define DA7210_ADC_R_EN (1 << 7) -/* DAC_HPF fields */ -#define DA7210_DAC_VOICE_EN (1 << 7) +/* DAC/ADC HPF fields */ +#define DA7210_VOICE_F0_MASK (0x7 << 4) +#define DA7210_VOICE_F0_25 (1 << 4) +#define DA7210_VOICE_EN (1 << 7) /* DAC_SEL bit fields */ #define DA7210_DAC_L_SRC_DAI_L (4 << 0) @@ -123,7 +122,15 @@ #define DA7210_PLL_BYP (1 << 6) /* PLL bit fields */ -#define DA7210_PLL_FS_48000 (11 << 0) +#define DA7210_PLL_FS_MASK (0xF << 0) +#define DA7210_PLL_FS_8000 (0x1 << 0) +#define DA7210_PLL_FS_12000 (0x3 << 0) +#define DA7210_PLL_FS_16000 (0x5 << 0) +#define DA7210_PLL_FS_24000 (0x7 << 0) +#define DA7210_PLL_FS_32000 (0x9 << 0) +#define DA7210_PLL_FS_48000 (0xB << 0) +#define DA7210_PLL_FS_96000 (0xF << 0) + #define DA7210_VERSION "0.0.1" @@ -241,7 +248,8 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; u32 dai_cfg1; - u32 reg, mask; + u32 hpf_reg, hpf_mask, hpf_value; + u32 fs; /* set DAI source to Left and Right ADC */ da7210_write(codec, DA7210_DAI_SRC_SEL, @@ -265,25 +273,46 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); - /* FIXME - * - * It support 48K only now - */ + hpf_reg = (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) ? + DA7210_DAC_HPF : DA7210_ADC_HPF; + switch (params_rate(params)) { + case 8000: + fs = DA7210_PLL_FS_8000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + break; + case 12000: + fs = DA7210_PLL_FS_12000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + break; + case 16000: + fs = DA7210_PLL_FS_16000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + break; + case 32000: + fs = DA7210_PLL_FS_32000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + break; case 48000: - if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { - reg = DA7210_DAC_HPF; - mask = DA7210_DAC_VOICE_EN; - } else { - reg = DA7210_ADC_HPF; - mask = DA7210_ADC_VOICE_EN; - } + fs = DA7210_PLL_FS_48000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + break; + case 96000: + fs = DA7210_PLL_FS_96000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; break; default: return -EINVAL; } - snd_soc_update_bits(codec, reg, mask, 0); + snd_soc_update_bits(codec, hpf_reg, hpf_mask, hpf_value); + snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs); return 0; } -- cgit v1.2.3-18-g5258 From 55c63bd256d41d79adbe1c3fc2325fb328419a18 Mon Sep 17 00:00:00 2001 From: Daniel Glöckner Date: Tue, 9 Mar 2010 12:57:52 -0500 Subject: ALSA: provide a more useful get_unmapped_area handler for pcm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared memory mappings on nommu machines require a get_unmapped_area file operation that suggests an address for the mapping. The current implementation returns 0 and thus forces the driver to implement an mmap handler that fixes up the start and end address of the vma. This patch returns the address of the dma buffer, so it should work out of the box for all drivers that use the snd_pcm_runtime->dma_area pointer. Addresses for mapping the status and control pages are returned as well, but to make those work the conditional compilation of snd_pcm_mmap_{status,control} would need to be revised. URL: http://thread.gmane.org/gmane.linux.alsa.devel/61230 Signed-off-by: Daniel Glöckner Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 87288762403..9681518aa39 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3434,14 +3434,28 @@ out: #endif /* CONFIG_SND_SUPPORT_OLD_API */ #ifndef CONFIG_MMU -unsigned long dummy_get_unmapped_area(struct file *file, unsigned long addr, - unsigned long len, unsigned long pgoff, - unsigned long flags) -{ - return 0; +static unsigned long snd_pcm_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct snd_pcm_file *pcm_file = file->private_data; + struct snd_pcm_substream *substream = pcm_file->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset = pgoff << PAGE_SHIFT; + + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS: + return (unsigned long)runtime->status; + case SNDRV_PCM_MMAP_OFFSET_CONTROL: + return (unsigned long)runtime->control; + default: + return (unsigned long)runtime->dma_area + offset; + } } #else -# define dummy_get_unmapped_area NULL +# define snd_pcm_get_unmapped_area NULL #endif /* @@ -3460,7 +3474,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, - .get_unmapped_area = dummy_get_unmapped_area, + .get_unmapped_area = snd_pcm_get_unmapped_area, }, { .owner = THIS_MODULE, @@ -3473,6 +3487,6 @@ const struct file_operations snd_pcm_f_ops[2] = { .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, - .get_unmapped_area = dummy_get_unmapped_area, + .get_unmapped_area = snd_pcm_get_unmapped_area, } }; -- cgit v1.2.3-18-g5258 From 8d43d1bc81190ee8e4e694d805afa586339b0afc Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Wed, 10 Mar 2010 14:48:33 +0530 Subject: ASoC: DaVinci: Add hw_param callback for S/PDIF DIT link On TI DM6467 EVM, S/PDIF DIT codec fails to open as it is unable to install hardware params. This dummy codec has no set_fmt and set_sysclk implementations and calls from the application to these functions cause errors. This patch adds a new hardware params callback function for S/PDIF transciever codec. Signed-off-by: Chaithrika U S Tested-by: Anuj Aggarwal Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-evm.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 7ccbe6684fc..dba6651547c 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -81,10 +81,24 @@ static int evm_hw_params(struct snd_pcm_substream *substream, return 0; } +static int evm_spdif_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->dai->cpu_dai; + + /* set cpu DAI configuration */ + return snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT); +} + static struct snd_soc_ops evm_ops = { .hw_params = evm_hw_params, }; +static struct snd_soc_ops evm_spdif_ops = { + .hw_params = evm_spdif_hw_params, +}; + /* davinci-evm machine dapm widgets */ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), @@ -165,7 +179,7 @@ static struct snd_soc_dai_link dm6467_evm_dai[] = { .stream_name = "spdif", .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_DIT_DAI], .codec_dai = &dit_stub_dai, - .ops = &evm_ops, + .ops = &evm_spdif_ops, }, }; static struct snd_soc_dai_link da8xx_evm_dai = { -- cgit v1.2.3-18-g5258 From 4b527e2900531befd3d4df2e70c85029ea571f1a Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 9 Mar 2010 12:58:09 -0500 Subject: ASoC: SSM2602: add SND control for mic boost2 and default it to off Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index d2ff1cde688..942f5dc3080 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -139,6 +139,7 @@ SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 7, 1, 0), SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), @@ -604,8 +605,7 @@ static int ssm2602_init(struct snd_soc_device *socdev) reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); /*select Line in as default input*/ - ssm2602_write(codec, SSM2602_APANA, - APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC | + ssm2602_write(codec, SSM2602_APANA, APANA_SELECT_DAC | APANA_ENABLE_MIC_BOOST); ssm2602_write(codec, SSM2602_PWR, 0); -- cgit v1.2.3-18-g5258 From f0d10f5aa3955fcd4a9a7f950c66b64666589d4d Mon Sep 17 00:00:00 2001 From: Barry Song Date: Tue, 9 Mar 2010 12:58:14 -0500 Subject: ASoC: bf5xx-sport: use common SPORT code for MMR info No point in duplicating this structure layout in each driver. Signed-off-by: Barry Song Signed-off-by: Mike Frysinger Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-sport.h | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h index 2e63dea73e9..a86e8cc0b2d 100644 --- a/sound/soc/blackfin/bf5xx-sport.h +++ b/sound/soc/blackfin/bf5xx-sport.h @@ -34,33 +34,7 @@ #include #include #include - -struct sport_register { - u16 tcr1; u16 reserved0; - u16 tcr2; u16 reserved1; - u16 tclkdiv; u16 reserved2; - u16 tfsdiv; u16 reserved3; - u32 tx; - u32 reserved_l0; - u32 rx; - u32 reserved_l1; - u16 rcr1; u16 reserved4; - u16 rcr2; u16 reserved5; - u16 rclkdiv; u16 reserved6; - u16 rfsdiv; u16 reserved7; - u16 stat; u16 reserved8; - u16 chnl; u16 reserved9; - u16 mcmc1; u16 reserved10; - u16 mcmc2; u16 reserved11; - u32 mtcs0; - u32 mtcs1; - u32 mtcs2; - u32 mtcs3; - u32 mrcs0; - u32 mrcs1; - u32 mrcs2; - u32 mrcs3; -}; +#include #define DESC_ELEMENT_COUNT 9 -- cgit v1.2.3-18-g5258 From 08226614789496dc395e3714115a3bcf2ca3baa2 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:50 +0900 Subject: ASoC: S3C: I2Sv2: Add missing semicolon Add missing semicolon after s3c2412_i2s_delay Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index aa84f4cb4d8..8f085087aa8 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -753,7 +753,7 @@ int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) /* Allow overriding by (for example) IISv4 */ if (!ops->delay) - ops->delay = s3c2412_i2s_delay, + ops->delay = s3c2412_i2s_delay; dai->suspend = s3c2412_i2s_suspend; dai->resume = s3c2412_i2s_resume; -- cgit v1.2.3-18-g5258 From 4793d6afbd7d66d64799a9dd1881888c28c4e1d0 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:54 +0900 Subject: ASoC: S3C: I2Sv2: Unify clock source IDs Rather than having the multiple definitions of the same clocks, define them in one common place and refer by SoC specific names. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.h | 4 ++++ sound/soc/s3c24xx/s3c2412-i2s.h | 4 ++-- sound/soc/s3c24xx/s3c64xx-i2s.h | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h index ecf8eaaed1d..b094d3c23cb 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -25,6 +25,10 @@ #define S3C_I2SV2_DIV_RCLK (2) #define S3C_I2SV2_DIV_PRESCALER (3) +#define S3C_I2SV2_CLKSRC_PCLK 0 +#define S3C_I2SV2_CLKSRC_AUDIOBUS 1 +#define S3C_I2SV2_CLKSRC_CDCLK 2 + /** * struct s3c_i2sv2_info - S3C I2S-V2 information * @dev: The parent device passed to use from the probe. diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h index 92848e54be1..60cac002a83 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -21,8 +21,8 @@ #define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK #define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER -#define S3C2412_CLKSRC_PCLK (0) -#define S3C2412_CLKSRC_I2SCLK (1) +#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS extern struct clk *s3c2412_get_iisclk(void); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index abe7253b55f..e350d280cd7 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -23,9 +23,9 @@ struct clk; #define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK #define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER -#define S3C64XX_CLKSRC_PCLK (0) -#define S3C64XX_CLKSRC_MUX (1) -#define S3C64XX_CLKSRC_CDCLK (2) +#define S3C64XX_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C64XX_CLKSRC_MUX S3C_I2SV2_CLKSRC_AUDIOBUS +#define S3C64XX_CLKSRC_CDCLK S3C_I2SV2_CLKSRC_CDCLK extern struct snd_soc_dai s3c64xx_i2s_dai[]; -- cgit v1.2.3-18-g5258 From b568f84b30f2b690e5ef704835effd09a212cd72 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:55 +0900 Subject: ASoC: s3c-i2s-v2 remove unnecessary headers s3c-i2s-v2 remove unnecessary headers Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 8f085087aa8..65d1cddc4b3 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -16,18 +16,12 @@ * option) any later version. */ -#include -#include -#include #include #include -#include #include -#include #include #include -#include #include #include -- cgit v1.2.3-18-g5258 From 87b7eb266c0bcdb5a0f1e670a73f2de742da459f Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:56 +0900 Subject: ASoC: s3c64xx-i2s remove unncessary headers s3c64xx-i2s remove unncessary headers Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 93ed3aad163..39563edabe2 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -12,9 +12,6 @@ * published by the Free Software Foundation. */ -#include -#include -#include #include #include #include -- cgit v1.2.3-18-g5258 From fa6231e1738617a864869600f51c3feb9e1081c8 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:57 +0900 Subject: ASoC: S3C64XX: I2S: Move RATE and FMT defines to header In order for the RATE and FMT defines to be reuseable in future by the i2sv4 driver, move the MACROs out to the header file. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 9 --------- sound/soc/s3c24xx/s3c64xx-i2s.h | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 39563edabe2..65528943579 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -127,15 +127,6 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, } -#define S3C64XX_I2S_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) - -#define S3C64XX_I2S_FMTS \ - (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S24_LE) - static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { .set_sysclk = s3c64xx_i2s_set_sysclk, }; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index e350d280cd7..53d2a0a0df3 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -27,6 +27,15 @@ struct clk; #define S3C64XX_CLKSRC_MUX S3C_I2SV2_CLKSRC_AUDIOBUS #define S3C64XX_CLKSRC_CDCLK S3C_I2SV2_CLKSRC_CDCLK +#define S3C64XX_I2S_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) + +#define S3C64XX_I2S_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + extern struct snd_soc_dai s3c64xx_i2s_dai[]; extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai); -- cgit v1.2.3-18-g5258 From 51c6ab130642ed975681df843c772dda48a1d2ed Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:51 +0900 Subject: ASoC: S3C: I2Sv2: Reject immidiate register value Towards generalizing CPU driver interface, do not accept direct field values for the BCLK and RCLK. The machine driver should simply request the FS-multiple and not provide the value to be set in divide field of IISMOD. [Confirmed by Jassi that no existing machine drivers are affected -- broonie] Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 68 +++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 65d1cddc4b3..b846f563cb5 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -463,29 +463,25 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, switch (div_id) { case S3C_I2SV2_DIV_BCLK: - if (div > 3) { - /* convert value to bit field */ - - switch (div) { - case 16: - div = S3C2412_IISMOD_BCLK_16FS; - break; + switch (div) { + case 16: + div = S3C2412_IISMOD_BCLK_16FS; + break; - case 32: - div = S3C2412_IISMOD_BCLK_32FS; - break; + case 32: + div = S3C2412_IISMOD_BCLK_32FS; + break; - case 24: - div = S3C2412_IISMOD_BCLK_24FS; - break; + case 24: + div = S3C2412_IISMOD_BCLK_24FS; + break; - case 48: - div = S3C2412_IISMOD_BCLK_48FS; - break; + case 48: + div = S3C2412_IISMOD_BCLK_48FS; + break; - default: - return -EINVAL; - } + default: + return -EINVAL; } reg = readl(i2s->regs + S3C2412_IISMOD); @@ -496,29 +492,25 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, break; case S3C_I2SV2_DIV_RCLK: - if (div > 3) { - /* convert value to bit field */ - - switch (div) { - case 256: - div = S3C2412_IISMOD_RCLK_256FS; - break; + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; - case 384: - div = S3C2412_IISMOD_RCLK_384FS; - break; + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; - case 512: - div = S3C2412_IISMOD_RCLK_512FS; - break; + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; - case 768: - div = S3C2412_IISMOD_RCLK_768FS; - break; + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; - default: - return -EINVAL; - } + default: + return -EINVAL; } reg = readl(i2s->regs + S3C2412_IISMOD); -- cgit v1.2.3-18-g5258 From bf3288260288160e22f85bbf00b281523c14b587 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:52 +0900 Subject: ASoC: S3C64XX: I2S: Make BCLK independent of sample size For some CPU-CODEC and source clock combination we might need to set BCLK to N*Sample_size*LRCLK, where N may be even 3 or 4, not just 2. We can simply remove the dependency of BCLK on sample size as there is already a callback(S3C_I2SV2_DIV_BCLK) available to set required BCLK. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index b846f563cb5..667431c824d 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -358,19 +358,16 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, #endif #ifdef CONFIG_PLAT_S3C64XX - iismod &= ~(S3C64XX_IISMOD_BLC_MASK | S3C2412_IISMOD_BCLK_MASK); + iismod &= ~S3C64XX_IISMOD_BLC_MASK; /* Sample size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: - /* 8 bit sample, 16fs BCLK */ - iismod |= (S3C64XX_IISMOD_BLC_8BIT | S3C2412_IISMOD_BCLK_16FS); + iismod |= S3C64XX_IISMOD_BLC_8BIT; break; case SNDRV_PCM_FORMAT_S16_LE: - /* 16 bit sample, 32fs BCLK */ break; case SNDRV_PCM_FORMAT_S24_LE: - /* 24 bit sample, 48fs BCLK */ - iismod |= (S3C64XX_IISMOD_BLC_24BIT | S3C2412_IISMOD_BCLK_48FS); + iismod |= S3C64XX_IISMOD_BLC_24BIT; break; } #endif -- cgit v1.2.3-18-g5258 From 9c9b125736db934b98aa7e16b675767ed4fcf5ce Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Wed, 10 Mar 2010 16:48:58 +0900 Subject: ASoC: S3C: I2Sv2: Segregate hw_params callback Towards having build for multiple SoCs segregate hw_params callback for s3c2412 and s3c64xx. Since, all new SoCs have s3c64xx like register map, we keep that as default implementation if no SoC specific callback is already defined. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 18 +++--------------- sound/soc/s3c24xx/s3c2412-i2s.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 667431c824d..c3fcb63cbf2 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -326,7 +326,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, return 0; } -static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, +static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *socdai) { @@ -346,18 +346,6 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); -#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S8: - iismod |= S3C2412_IISMOD_8BIT; - break; - case SNDRV_PCM_FORMAT_S16_LE: - iismod &= ~S3C2412_IISMOD_8BIT; - break; - } -#endif - -#ifdef CONFIG_PLAT_S3C64XX iismod &= ~S3C64XX_IISMOD_BLC_MASK; /* Sample size */ switch (params_format(params)) { @@ -370,7 +358,6 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod |= S3C64XX_IISMOD_BLC_24BIT; break; } -#endif writel(iismod, i2s->regs + S3C2412_IISMOD); pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); @@ -730,7 +717,8 @@ int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) struct snd_soc_dai_ops *ops = dai->ops; ops->trigger = s3c2412_i2s_trigger; - ops->hw_params = s3c2412_i2s_hw_params; + if (!ops->hw_params) + ops->hw_params = s3c_i2sv2_hw_params; ops->set_fmt = s3c2412_i2s_set_fmt; ops->set_clkdiv = s3c2412_i2s_set_clkdiv; diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 359e59346ba..a5b21f63261 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -103,6 +103,10 @@ struct clk *s3c2412_get_iisclk(void) } EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} static int s3c2412_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) @@ -142,6 +146,38 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, return 0; } +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_dai->dma_data = i2s->dma_playback; + else + cpu_dai->dma_data = i2s->dma_capture; + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + #define S3C2412_I2S_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 | \ @@ -149,6 +185,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { .set_sysclk = s3c2412_i2s_set_sysclk, + .hw_params = s3c2412_i2s_hw_params, }; struct snd_soc_dai s3c2412_i2s_dai = { -- cgit v1.2.3-18-g5258 From 960b3b4b4cb6d22d55ad0ebb280dac9876937fa0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 11 Mar 2010 11:37:44 +0900 Subject: ASoC: da7210: Add 11025/22050/44100/88200 rate support This driver USE PLL for 11025/22050/44100/88200 rate. To enable switching to bypass mode, PLL is always turned on. Special thanks to Phil Signed-off-by: Phil Edworthy Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/da7210.c | 90 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 3bd867de597..35b88163865 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -55,8 +55,14 @@ #define DA7210_DAI_SRC_SEL 0x25 #define DA7210_DAI_CFG1 0x26 #define DA7210_DAI_CFG3 0x28 +#define DA7210_PLL_DIV1 0x29 +#define DA7210_PLL_DIV2 0x2A #define DA7210_PLL_DIV3 0x2B #define DA7210_PLL 0x2C +#define DA7210_A_HID_UNLOCK 0x8A +#define DA7210_A_TEST_UNLOCK 0x8B +#define DA7210_A_PLL1 0x90 +#define DA7210_A_CP_MODE 0xA7 /* STARTUP1 bit fields */ #define DA7210_SC_MST_EN (1 << 0) @@ -124,13 +130,17 @@ /* PLL bit fields */ #define DA7210_PLL_FS_MASK (0xF << 0) #define DA7210_PLL_FS_8000 (0x1 << 0) +#define DA7210_PLL_FS_11025 (0x2 << 0) #define DA7210_PLL_FS_12000 (0x3 << 0) #define DA7210_PLL_FS_16000 (0x5 << 0) +#define DA7210_PLL_FS_22050 (0x6 << 0) #define DA7210_PLL_FS_24000 (0x7 << 0) #define DA7210_PLL_FS_32000 (0x9 << 0) +#define DA7210_PLL_FS_44100 (0xA << 0) #define DA7210_PLL_FS_48000 (0xB << 0) +#define DA7210_PLL_FS_88200 (0xE << 0) #define DA7210_PLL_FS_96000 (0xF << 0) - +#define DA7210_PLL_EN (0x1 << 7) #define DA7210_VERSION "0.0.1" @@ -249,7 +259,7 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; u32 dai_cfg1; u32 hpf_reg, hpf_mask, hpf_value; - u32 fs; + u32 fs, bypass; /* set DAI source to Left and Right ADC */ da7210_write(codec, DA7210_DAI_SRC_SEL, @@ -281,38 +291,76 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, fs = DA7210_PLL_FS_8000; hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; + break; + case 11025: + fs = DA7210_PLL_FS_11025; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = 0; break; case 12000: fs = DA7210_PLL_FS_12000; hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; break; case 16000: fs = DA7210_PLL_FS_16000; hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; + break; + case 22050: + fs = DA7210_PLL_FS_22050; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; break; case 32000: fs = DA7210_PLL_FS_32000; hpf_mask = DA7210_VOICE_EN; hpf_value = 0; + bypass = DA7210_PLL_BYP; + break; + case 44100: + fs = DA7210_PLL_FS_44100; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; break; case 48000: fs = DA7210_PLL_FS_48000; hpf_mask = DA7210_VOICE_EN; hpf_value = 0; + bypass = DA7210_PLL_BYP; + break; + case 88200: + fs = DA7210_PLL_FS_88200; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; break; case 96000: fs = DA7210_PLL_FS_96000; hpf_mask = DA7210_VOICE_EN; hpf_value = 0; + bypass = DA7210_PLL_BYP; break; default: return -EINVAL; } + /* Disable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); + snd_soc_update_bits(codec, hpf_reg, hpf_mask, hpf_value); snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs); + snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, bypass); + + /* Enable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, + DA7210_SC_MST_EN, DA7210_SC_MST_EN); return 0; } @@ -390,6 +438,7 @@ struct snd_soc_dai da7210_dai = { .formats = DA7210_FORMATS, }, .ops = &da7210_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(da7210_dai); @@ -444,7 +493,21 @@ static int da7210_init(struct da7210_priv *da7210) /* FIXME * * This driver use fixed value here + * And below settings expects MCLK = 12.288MHz + * + * When you select different MCLK, please check... + * DA7210_PLL_DIV1 val + * DA7210_PLL_DIV2 val + * DA7210_PLL_DIV3 val + * DA7210_PLL_DIV3 :: DA7210_MCLK_RANGExxx + */ + + /* + * make sure that DA7210 use bypass mode before start up */ + da7210_write(codec, DA7210_STARTUP1, 0); + da7210_write(codec, DA7210_PLL_DIV3, + DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); /* * ADC settings @@ -482,9 +545,28 @@ static int da7210_init(struct da7210_priv *da7210) /* Diable PLL and bypass it */ da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000); - /* Bypass PLL and set MCLK freq rang to 10-20MHz */ - da7210_write(codec, DA7210_PLL_DIV3, + /* + * If 48kHz sound came, it use bypass mode, + * and when it is 44.1kHz, it use PLL. + * + * This time, this driver sets PLL always ON + * and controls bypass/PLL mode by switching + * DA7210_PLL_DIV3 :: DA7210_PLL_BYP bit. + * see da7210_hw_params + */ + da7210_write(codec, DA7210_PLL_DIV1, 0xE5); /* MCLK = 12.288MHz */ + da7210_write(codec, DA7210_PLL_DIV2, 0x99); + da7210_write(codec, DA7210_PLL_DIV3, 0x0A | DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN); + + /* As suggested by Dialog */ + da7210_write(codec, DA7210_A_HID_UNLOCK, 0x8B); /* unlock */ + da7210_write(codec, DA7210_A_TEST_UNLOCK, 0xB4); + da7210_write(codec, DA7210_A_PLL1, 0x01); + da7210_write(codec, DA7210_A_CP_MODE, 0x7C); + da7210_write(codec, DA7210_A_HID_UNLOCK, 0x00); /* re-lock */ + da7210_write(codec, DA7210_A_TEST_UNLOCK, 0x00); /* Activate all enabled subsystem */ da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); -- cgit v1.2.3-18-g5258 From d9ad6296ec3b4a55ba25f2c5e4824be487242e1f Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Fri, 12 Mar 2010 13:38:52 +0900 Subject: ASoC: PCM_RATE: Check for KNOT and CONTINUOUS flags For ASoC, if either CPU or CODEC driver has set the flag, the MACHINE driver should be given a chance to figure out if the dai, that set the flag, can accomodate a rate that it does not explicitly specify but is specified by the dai at the other end of the link. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 06c38d1502b..eb73aab78b9 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -404,6 +404,12 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) codec_dai->playback.formats & cpu_dai->playback.formats; runtime->hw.rates = codec_dai->playback.rates & cpu_dai->playback.rates; + if (codec_dai->playback.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= cpu_dai->playback.rates; + if (cpu_dai->playback.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= codec_dai->playback.rates; } else { runtime->hw.rate_min = max(codec_dai->capture.rate_min, @@ -421,6 +427,12 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) codec_dai->capture.formats & cpu_dai->capture.formats; runtime->hw.rates = codec_dai->capture.rates & cpu_dai->capture.rates; + if (codec_dai->capture.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= cpu_dai->capture.rates; + if (cpu_dai->capture.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= codec_dai->capture.rates; } snd_pcm_limit_hw_rates(runtime); -- cgit v1.2.3-18-g5258 From ca26308c2223cb424c297a70fb7b6c7530a563ba Mon Sep 17 00:00:00 2001 From: Miguel Aguilar Date: Thu, 11 Mar 2010 09:32:21 -0600 Subject: MFD: DaVinci Voice Codec This is the MFD driver for the DaVinci Voice codec, it has two clients: * Voice codec interface * Voice codec CQ93VC Signed-off-by: Miguel Aguilar Acked-by: Samuel Ortiz Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- drivers/mfd/Kconfig | 4 + drivers/mfd/Makefile | 1 + drivers/mfd/davinci_voicecodec.c | 189 +++++++++++++++++++++++++++++++++ include/linux/mfd/davinci_voicecodec.h | 126 ++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 drivers/mfd/davinci_voicecodec.c create mode 100644 include/linux/mfd/davinci_voicecodec.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 951fa9b93fb..20e322912a5 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -53,6 +53,10 @@ config MFD_SH_MOBILE_SDHI This driver supports the SDHI hardware block found in many SuperH Mobile SoCs. +config MFD_DAVINCI_VOICECODEC + tristate + select MFD_CORE + config MFD_DM355EVM_MSP bool "DaVinci DM355 EVM microcontroller" depends on I2C && MACH_DAVINCI_DM355_EVM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 22715add99a..4fbf8f89a49 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o +obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c new file mode 100644 index 00000000000..9886aa8de25 --- /dev/null +++ b/drivers/mfd/davinci_voicecodec.c @@ -0,0 +1,189 @@ +/* + * DaVinci Voice Codec Core Interface for TI platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +u32 davinci_vc_read(struct davinci_vc *davinci_vc, int reg) +{ + return __raw_readl(davinci_vc->base + reg); +} + +void davinci_vc_write(struct davinci_vc *davinci_vc, + int reg, u32 val) +{ + __raw_writel(val, davinci_vc->base + reg); +} + +static int __init davinci_vc_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc; + struct resource *res, *mem; + struct mfd_cell *cell = NULL; + int ret; + + davinci_vc = kzalloc(sizeof(struct davinci_vc), GFP_KERNEL); + if (!davinci_vc) { + dev_dbg(&pdev->dev, + "could not allocate memory for private data\n"); + return -ENOMEM; + } + + davinci_vc->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(davinci_vc->clk)) { + dev_dbg(&pdev->dev, + "could not get the clock for voice codec\n"); + ret = -ENODEV; + goto fail1; + } + clk_enable(davinci_vc->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource\n"); + ret = -ENODEV; + goto fail2; + } + + davinci_vc->pbase = res->start; + davinci_vc->base_size = resource_size(res); + + mem = request_mem_region(davinci_vc->pbase, davinci_vc->base_size, + pdev->name); + if (!mem) { + dev_err(&pdev->dev, "VCIF region already claimed\n"); + ret = -EBUSY; + goto fail2; + } + + davinci_vc->base = ioremap(davinci_vc->pbase, davinci_vc->base_size); + if (!davinci_vc->base) { + dev_err(&pdev->dev, "can't ioremap mem resource.\n"); + ret = -ENOMEM; + goto fail3; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + return -ENXIO; + } + + davinci_vc->davinci_vcif.dma_tx_channel = res->start; + davinci_vc->davinci_vcif.dma_tx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_WFIFO); + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + return -ENXIO; + } + + davinci_vc->davinci_vcif.dma_rx_channel = res->start; + davinci_vc->davinci_vcif.dma_rx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_RFIFO); + + davinci_vc->dev = &pdev->dev; + davinci_vc->pdev = pdev; + + /* Voice codec interface client */ + cell = &davinci_vc->cells[DAVINCI_VC_VCIF_CELL]; + cell->name = "davinci_vcif"; + cell->driver_data = davinci_vc; + + /* Voice codec CQ93VC client */ + cell = &davinci_vc->cells[DAVINCI_VC_CQ93VC_CELL]; + cell->name = "cq93vc"; + cell->driver_data = davinci_vc; + + ret = mfd_add_devices(&pdev->dev, pdev->id, davinci_vc->cells, + DAVINCI_VC_CELLS, NULL, 0); + if (ret != 0) { + dev_err(&pdev->dev, "fail to register client devices\n"); + goto fail4; + } + + return 0; + +fail4: + iounmap(davinci_vc->base); +fail3: + release_mem_region(davinci_vc->pbase, davinci_vc->base_size); +fail2: + clk_disable(davinci_vc->clk); + clk_put(davinci_vc->clk); + davinci_vc->clk = NULL; +fail1: + kfree(davinci_vc); + + return ret; +} + +static int __devexit davinci_vc_remove(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + + iounmap(davinci_vc->base); + release_mem_region(davinci_vc->pbase, davinci_vc->base_size); + + clk_disable(davinci_vc->clk); + clk_put(davinci_vc->clk); + davinci_vc->clk = NULL; + + kfree(davinci_vc); + + return 0; +} + +static struct platform_driver davinci_vc_driver = { + .driver = { + .name = "davinci_voicecodec", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(davinci_vc_remove), +}; + +static int __init davinci_vc_init(void) +{ + return platform_driver_probe(&davinci_vc_driver, davinci_vc_probe); +} +module_init(davinci_vc_init); + +static void __exit davinci_vc_exit(void) +{ + platform_driver_unregister(&davinci_vc_driver); +} +module_exit(davinci_vc_exit); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci Voice Codec Core Interface"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/davinci_voicecodec.h b/include/linux/mfd/davinci_voicecodec.h new file mode 100644 index 00000000000..0ab61320ffa --- /dev/null +++ b/include/linux/mfd/davinci_voicecodec.h @@ -0,0 +1,126 @@ +/* + * DaVinci Voice Codec Core Interface for TI platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ + +#ifndef __LINUX_MFD_DAVINCI_VOICECODEC_H_ +#define __LINUX_MFD_DAVINIC_VOICECODEC_H_ + +#include +#include +#include + +#include + +/* + * Register values. + */ +#define DAVINCI_VC_PID 0x00 +#define DAVINCI_VC_CTRL 0x04 +#define DAVINCI_VC_INTEN 0x08 +#define DAVINCI_VC_INTSTATUS 0x0c +#define DAVINCI_VC_INTCLR 0x10 +#define DAVINCI_VC_EMUL_CTRL 0x14 +#define DAVINCI_VC_RFIFO 0x20 +#define DAVINCI_VC_WFIFO 0x24 +#define DAVINCI_VC_FIFOSTAT 0x28 +#define DAVINCI_VC_TST_CTRL 0x2C +#define DAVINCI_VC_REG05 0x94 +#define DAVINCI_VC_REG09 0xA4 +#define DAVINCI_VC_REG12 0xB0 + +/* DAVINCI_VC_CTRL bit fields */ +#define DAVINCI_VC_CTRL_MASK 0x5500 +#define DAVINCI_VC_CTRL_RSTADC BIT(0) +#define DAVINCI_VC_CTRL_RSTDAC BIT(1) +#define DAVINCI_VC_CTRL_RD_BITS_8 BIT(4) +#define DAVINCI_VC_CTRL_RD_UNSIGNED BIT(5) +#define DAVINCI_VC_CTRL_WD_BITS_8 BIT(6) +#define DAVINCI_VC_CTRL_WD_UNSIGNED BIT(7) +#define DAVINCI_VC_CTRL_RFIFOEN BIT(8) +#define DAVINCI_VC_CTRL_RFIFOCL BIT(9) +#define DAVINCI_VC_CTRL_RFIFOMD_WORD_1 BIT(10) +#define DAVINCI_VC_CTRL_WFIFOEN BIT(12) +#define DAVINCI_VC_CTRL_WFIFOCL BIT(13) +#define DAVINCI_VC_CTRL_WFIFOMD_WORD_1 BIT(14) + +/* DAVINCI_VC_INT bit fields */ +#define DAVINCI_VC_INT_MASK 0x3F +#define DAVINCI_VC_INT_RDRDY_MASK BIT(0) +#define DAVINCI_VC_INT_RERROVF_MASK BIT(1) +#define DAVINCI_VC_INT_RERRUDR_MASK BIT(2) +#define DAVINCI_VC_INT_WDREQ_MASK BIT(3) +#define DAVINCI_VC_INT_WERROVF_MASKBIT BIT(4) +#define DAVINCI_VC_INT_WERRUDR_MASK BIT(5) + +/* DAVINCI_VC_REG05 bit fields */ +#define DAVINCI_VC_REG05_PGA_GAIN 0x07 + +/* DAVINCI_VC_REG09 bit fields */ +#define DAVINCI_VC_REG09_MUTE 0x40 +#define DAVINCI_VC_REG09_DIG_ATTEN 0x3F + +/* DAVINCI_VC_REG12 bit fields */ +#define DAVINCI_VC_REG12_POWER_ALL_ON 0xFD +#define DAVINCI_VC_REG12_POWER_ALL_OFF 0x00 + +#define DAVINCI_VC_CELLS 2 + +enum davinci_vc_cells { + DAVINCI_VC_VCIF_CELL, + DAVINCI_VC_CQ93VC_CELL, +}; + +struct davinci_vcif { + struct platform_device *pdev; + u32 dma_tx_channel; + u32 dma_rx_channel; + dma_addr_t dma_tx_addr; + dma_addr_t dma_rx_addr; +}; + +struct cq93vc { + struct platform_device *pdev; + struct snd_soc_codec *codec; + u32 sysclk; +}; + +struct davinci_vc; + +struct davinci_vc { + /* Device data */ + struct device *dev; + struct platform_device *pdev; + struct clk *clk; + + /* Memory resources */ + void __iomem *base; + resource_size_t pbase; + size_t base_size; + + /* MFD cells */ + struct mfd_cell cells[DAVINCI_VC_CELLS]; + + /* Client devices */ + struct davinci_vcif davinci_vcif; + struct cq93vc cq93vc; +}; + +#endif -- cgit v1.2.3-18-g5258 From e155fcc23c918f5d78a35d1eb6f3f0d523f51991 Mon Sep 17 00:00:00 2001 From: Miguel Aguilar Date: Thu, 11 Mar 2010 09:32:42 -0600 Subject: ASoC: DaVinci: Voice Codec Interface This patch adds the support for the interface needed by the DaVinci Voice Codec CQ93VC. Signed-off-by: Miguel Aguilar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/davinci/Kconfig | 3 + sound/soc/davinci/Makefile | 2 + sound/soc/davinci/davinci-vcif.c | 272 +++++++++++++++++++++++++++++++++++++++ sound/soc/davinci/davinci-vcif.h | 28 ++++ 4 files changed, 305 insertions(+) create mode 100644 sound/soc/davinci/davinci-vcif.c create mode 100644 sound/soc/davinci/davinci-vcif.h diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 047ee39418c..47e7ccecc57 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -12,6 +12,9 @@ config SND_DAVINCI_SOC_I2S config SND_DAVINCI_SOC_MCASP tristate +config SND_DAVINCI_SOC_VCIF + tristate + config SND_DAVINCI_SOC_EVM tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM" depends on SND_DAVINCI_SOC diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index a6939d71b98..a93679d618c 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -2,10 +2,12 @@ snd-soc-davinci-objs := davinci-pcm.o snd-soc-davinci-i2s-objs := davinci-i2s.o snd-soc-davinci-mcasp-objs:= davinci-mcasp.o +snd-soc-davinci-vcif-objs:= davinci-vcif.o obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o +obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o # DAVINCI Machine Support snd-soc-evm-objs := davinci-evm.o diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c new file mode 100644 index 00000000000..03f3feb0fe2 --- /dev/null +++ b/sound/soc/davinci/davinci-vcif.c @@ -0,0 +1,272 @@ +/* + * ALSA SoC Voice Codec Interface for TI DAVINCI processor + * + * Copyright (C) 2010 Texas Instruments. + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "davinci-pcm.h" +#include "davinci-i2s.h" +#include "davinci-vcif.h" + +#define MOD_REG_BIT(val, mask, set) do { \ + if (set) { \ + val |= mask; \ + } else { \ + val &= ~mask; \ + } \ +} while (0) + +struct davinci_vcif_dev { + struct davinci_vc *davinci_vc; + struct davinci_pcm_dma_params dma_params[2]; +}; + +static void davinci_vcif_start(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + rtd->dai->cpu_dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Start the sample generator and enable transmitter/receiver */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 1); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 1); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static void davinci_vcif_stop(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + rtd->dai->cpu_dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Reset transmitter/receiver and sample rate/frame sync generators */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 0); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 0); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static int davinci_vcif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct davinci_vcif_dev *davinci_vcif_dev = dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + struct davinci_pcm_dma_params *dma_params = + &davinci_vcif_dev->dma_params[substream->stream]; + u32 w; + + /* Restart the codec before setup */ + davinci_vcif_stop(substream); + davinci_vcif_start(substream); + + /* General line settings */ + writel(DAVINCI_VC_CTRL_MASK, davinci_vc->base + DAVINCI_VC_CTRL); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTCLR); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTEN); + + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + /* Determine xfer data type */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + dma_params->data_type = 0; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 1); + break; + case SNDRV_PCM_FORMAT_S8: + dma_params->data_type = 1; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_WD_BITS_8, 1); + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + case SNDRV_PCM_FORMAT_S16_LE: + dma_params->data_type = 2; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + default: + printk(KERN_WARNING "davinci-vcif: unsupported PCM format"); + return -EINVAL; + } + + dma_params->acnt = dma_params->data_type; + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); + + return 0; +} + +static int davinci_vcif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_vcif_start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_vcif_stop(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +#define DAVINCI_VCIF_RATES SNDRV_PCM_RATE_8000_48000 + +static struct snd_soc_dai_ops davinci_vcif_dai_ops = { + .trigger = davinci_vcif_trigger, + .hw_params = davinci_vcif_hw_params, +}; + +struct snd_soc_dai davinci_vcif_dai = { + .name = "davinci-vcif", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &davinci_vcif_dai_ops, + +}; +EXPORT_SYMBOL_GPL(davinci_vcif_dai); + +static int davinci_vcif_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + struct davinci_vcif_dev *davinci_vcif_dev; + int ret; + + davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL); + if (!davinci_vc) { + dev_dbg(&pdev->dev, + "could not allocate memory for private data\n"); + return -ENOMEM; + } + + /* DMA tx params */ + davinci_vcif_dev->davinci_vc = davinci_vc; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = + davinci_vc->davinci_vcif.dma_tx_channel; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr = + davinci_vc->davinci_vcif.dma_tx_addr; + + /* DMA rx params */ + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = + davinci_vc->davinci_vcif.dma_rx_channel; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr = + davinci_vc->davinci_vcif.dma_rx_addr; + + davinci_vcif_dai.dev = &pdev->dev; + davinci_vcif_dai.dma_data = davinci_vcif_dev->dma_params; + davinci_vcif_dai.private_data = davinci_vcif_dev; + + ret = snd_soc_register_dai(&davinci_vcif_dai); + if (ret != 0) { + dev_err(&pdev->dev, "could not register dai\n"); + goto fail; + } + + return 0; + +fail: + kfree(davinci_vcif_dev); + + return ret; +} + +static int davinci_vcif_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&davinci_vcif_dai); + + return 0; +} + +static struct platform_driver davinci_vcif_driver = { + .probe = davinci_vcif_probe, + .remove = davinci_vcif_remove, + .driver = { + .name = "davinci_vcif", + .owner = THIS_MODULE, + }, +}; + +static int __init davinci_vcif_init(void) +{ + return platform_driver_probe(&davinci_vcif_driver, davinci_vcif_probe); +} +module_init(davinci_vcif_init); + +static void __exit davinci_vcif_exit(void) +{ + platform_driver_unregister(&davinci_vcif_driver); +} +module_exit(davinci_vcif_exit); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC Voice Codec Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-vcif.h b/sound/soc/davinci/davinci-vcif.h new file mode 100644 index 00000000000..571c9948724 --- /dev/null +++ b/sound/soc/davinci/davinci-vcif.h @@ -0,0 +1,28 @@ +/* + * ALSA SoC Voice Codec Interface for TI DAVINCI processor + * + * Copyright (C) 2010 Texas Instruments. + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ + +#ifndef _DAVINCI_VCIF_H +#define _DAVINCI_VCIF_H + +extern struct snd_soc_dai davinci_vcif_dai; + +#endif -- cgit v1.2.3-18-g5258 From b56e972b75a945e97818437720551859c7ef2d53 Mon Sep 17 00:00:00 2001 From: Miguel Aguilar Date: Thu, 11 Mar 2010 09:32:59 -0600 Subject: ASoC: DaVinci: CQ93VC Voice Codec Currently the DM365 is the only SoC that includes this Voice Codec. Signed-off-by: Miguel Aguilar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cq93vc.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cq93vc.h | 29 +++++ 4 files changed, 333 insertions(+) create mode 100644 sound/soc/codecs/cq93vc.c create mode 100644 sound/soc/codecs/cq93vc.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1743d565e99..16c47edb826 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -21,6 +21,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4535 if I2C select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C + select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C @@ -114,6 +115,9 @@ config SND_SOC_AK4642 config SND_SOC_AK4671 tristate +config SND_SOC_CQ0093VC + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index dd5ce6df629..69817778b2b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -8,6 +8,7 @@ snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o +snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o @@ -70,6 +71,7 @@ obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c new file mode 100644 index 00000000000..51329744136 --- /dev/null +++ b/sound/soc/codecs/cq93vc.c @@ -0,0 +1,298 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cq93vc.h" + +static inline unsigned int cq93vc_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct davinci_vc *davinci_vc = codec->control_data; + + return readl(davinci_vc->base + reg); +} + +static inline int cq93vc_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct davinci_vc *davinci_vc = codec->control_data; + + writel(value, davinci_vc->base + reg); + + return 0; +} + +static const struct snd_kcontrol_new cq93vc_snd_controls[] = { + SOC_SINGLE("PGA Capture Volume", DAVINCI_VC_REG05, 0, 0x03, 0), + SOC_SINGLE("Mono DAC Playback Volume", DAVINCI_VC_REG09, 0, 0x3f, 0), +}; + +static int cq93vc_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 reg = cq93vc_read(codec, DAVINCI_VC_REG09) & ~DAVINCI_VC_REG09_MUTE; + + if (mute) + cq93vc_write(codec, DAVINCI_VC_REG09, + reg | DAVINCI_VC_REG09_MUTE); + else + cq93vc_write(codec, DAVINCI_VC_REG09, reg); + + return 0; +} + +static int cq93vc_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 davinci_vc *davinci_vc = codec->control_data; + + switch (freq) { + case 22579200: + case 27000000: + case 33868800: + davinci_vc->cq93vc.sysclk = freq; + return 0; + } + + return -EINVAL; +} + +static int cq93vc_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + case SND_SOC_BIAS_OFF: + /* force all power off */ + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + } + codec->bias_level = level; + + return 0; +} + +#define CQ93VC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) +#define CQ93VC_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_ops cq93vc_dai_ops = { + .digital_mute = cq93vc_mute, + .set_sysclk = cq93vc_set_dai_sysclk, +}; + +struct snd_soc_dai cq93vc_dai = { + .name = "CQ93VC", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .ops = &cq93vc_dai_ops, +}; +EXPORT_SYMBOL_GPL(cq93vc_dai); + +static int cq93vc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + cq93vc_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +static struct snd_soc_codec *cq93vc_codec; + +static int cq93vc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct snd_soc_codec *codec; + int ret; + + socdev->card->codec = cq93vc_codec; + codec = socdev->card->codec; + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(dev, "%s: failed to create pcms\n", pdev->name); + return ret; + } + + /* Set controls */ + snd_soc_add_controls(codec, cq93vc_snd_controls, + ARRAY_SIZE(cq93vc_snd_controls)); + + /* Off, with power on */ + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static int cq93vc_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_cq93vc = { + .probe = cq93vc_probe, + .remove = cq93vc_remove, + .resume = cq93vc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_cq93vc); + +static __init int cq93vc_codec_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) { + dev_dbg(davinci_vc->dev, + "could not allocate memory for codec data\n"); + return -ENOMEM; + } + + davinci_vc->cq93vc.codec = codec; + + cq93vc_dai.dev = &pdev->dev; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->dev = &pdev->dev; + codec->name = "CQ93VC"; + codec->owner = THIS_MODULE; + codec->read = cq93vc_read; + codec->write = cq93vc_write; + codec->set_bias_level = cq93vc_set_bias_level; + codec->dai = &cq93vc_dai; + codec->num_dai = 1; + codec->control_data = davinci_vc; + + cq93vc_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(davinci_vc->dev, "failed to register codec\n"); + goto fail1; + } + + ret = snd_soc_register_dai(&cq93vc_dai); + if (ret) { + dev_err(davinci_vc->dev, "could register dai\n"); + goto fail2; + } + return 0; + +fail2: + snd_soc_unregister_codec(codec); + +fail1: + kfree(codec); + cq93vc_codec = NULL; + + return ret; +} + +static int __devexit cq93vc_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_unregister_dai(&cq93vc_dai); + snd_soc_unregister_codec(&codec); + + kfree(codec); + cq93vc_codec = NULL; + + return 0; +} + +static struct platform_driver cq93vc_codec_driver = { + .driver = { + .name = "cq93vc", + .owner = THIS_MODULE, + }, + .probe = cq93vc_codec_probe, + .remove = __devexit_p(cq93vc_codec_remove), +}; + +static __init int cq93vc_init(void) +{ + return platform_driver_probe(&cq93vc_codec_driver, cq93vc_codec_probe); +} +module_init(cq93vc_init); + +static __exit void cq93vc_exit(void) +{ + platform_driver_unregister(&cq93vc_codec_driver); +} +module_exit(cq93vc_exit); + +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver"); +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cq93vc.h b/sound/soc/codecs/cq93vc.h new file mode 100644 index 00000000000..845b1968ef9 --- /dev/null +++ b/sound/soc/codecs/cq93vc.h @@ -0,0 +1,29 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar + * + * 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. + * + * 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 + */ + +#ifndef _CQ93VC_H +#define _CQ93VC_H + +extern struct snd_soc_dai cq93vc_dai; +extern struct snd_soc_codec_device soc_codec_dev_cq93vc; + +#endif -- cgit v1.2.3-18-g5258 From aa9b88ee80d3985ebae9567a8f4e0d07ce15b5a7 Mon Sep 17 00:00:00 2001 From: Miguel Aguilar Date: Thu, 11 Mar 2010 09:33:40 -0600 Subject: DaVinci: DM365: Voice Codec support for the DM365 EVM The DM365 EVM has two codecs: the Audio Codec (AIC3x) and the Voice Codec, the idea is to have both enabled in the same kernel simultaneously. However, the current soc-core doesn't support simultaneous codecs, once that support will have added, a patch will be posted to enable both codecs in the DM365 EVM. Signed-off-by: Miguel Aguilar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- arch/arm/mach-davinci/board-dm365-evm.c | 4 +++ sound/soc/davinci/Kconfig | 24 ++++++++++++++++-- sound/soc/davinci/davinci-evm.c | 45 +++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-davinci/board-dm365-evm.c b/arch/arm/mach-davinci/board-dm365-evm.c index d15beceb632..512174a874a 100644 --- a/arch/arm/mach-davinci/board-dm365-evm.c +++ b/arch/arm/mach-davinci/board-dm365-evm.c @@ -604,7 +604,11 @@ static __init void dm365_evm_init(void) /* maybe setup mmc1/etc ... _after_ mmc0 */ evm_init_cpld(); +#ifdef CONFIG_SND_DM365_AIC3X_CODEC dm365_init_asp(&dm365_evm_snd_data); +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + dm365_init_vc(&dm365_evm_snd_data); +#endif dm365_init_rtc(); dm365_init_ks(&dm365evm_ks_data); diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 47e7ccecc57..6bbf001f659 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -18,12 +18,32 @@ config SND_DAVINCI_SOC_VCIF config SND_DAVINCI_SOC_EVM tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM" depends on SND_DAVINCI_SOC - depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM + depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM select SND_DAVINCI_SOC_I2S select SND_SOC_TLV320AIC3X help Say Y if you want to add support for SoC audio on TI - DaVinci DM6446 or DM355 EVM platforms. + DaVinci DM6446, DM355 or DM365 EVM platforms. + +choice + prompt "DM365 codec select" + depends on SND_DAVINCI_SOC_EVM + depends on MACH_DAVINCI_DM365_EVM + default SND_DM365_EXTERNAL_CODEC + +config SND_DM365_AIC3X_CODEC + bool "Audio Codec - AIC3101" + help + Say Y if you want to add support for AIC3101 audio codec + +config SND_DM365_VOICE_CODEC + bool "Voice Codec - CQ93VC" + select MFD_DAVINCI_VOICECODEC + select SND_DAVINCI_SOC_VCIF + select SND_SOC_CQ0093VC + help + Say Y if you want to add support for SoC On-chip voice codec +endchoice config SND_DM6467_SOC_EVM tristate "SoC Audio support for DaVinci DM6467 EVM" diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index dba6651547c..97f74d6a33e 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -28,10 +28,12 @@ #include #include "../codecs/tlv320aic3x.h" +#include "../codecs/cq93vc.h" #include "../codecs/spdif_transciever.h" #include "davinci-pcm.h" #include "davinci-i2s.h" #include "davinci-mcasp.h" +#include "davinci-vcif.h" #define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \ SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF) @@ -165,6 +167,22 @@ static struct snd_soc_dai_link evm_dai = { .ops = &evm_ops, }; +static struct snd_soc_dai_link dm365_evm_dai = { +#ifdef CONFIG_SND_DM365_AIC3X_CODEC + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai = &davinci_i2s_dai, + .codec_dai = &aic3x_dai, + .init = evm_aic3x_init, + .ops = &evm_ops, +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + .name = "Voice Codec - CQ93VC", + .stream_name = "CQ93", + .cpu_dai = &davinci_vcif_dai, + .codec_dai = &cq93vc_dai, +#endif +}; + static struct snd_soc_dai_link dm6467_evm_dai[] = { { .name = "TLV320AIC3X", @@ -191,7 +209,7 @@ static struct snd_soc_dai_link da8xx_evm_dai = { .ops = &evm_ops, }; -/* davinci dm6446, dm355 or dm365 evm audio machine driver */ +/* davinci dm6446, dm355 evm audio machine driver */ static struct snd_soc_card snd_soc_card_evm = { .name = "DaVinci EVM", .platform = &davinci_soc_platform, @@ -199,6 +217,15 @@ static struct snd_soc_card snd_soc_card_evm = { .num_links = 1, }; +/* davinci dm365 evm audio machine driver */ +static struct snd_soc_card dm365_snd_soc_card_evm = { + .name = "DaVinci DM365 EVM", + .platform = &davinci_soc_platform, + .dai_link = &dm365_evm_dai, + .num_links = 1, +}; + + /* davinci dm6467 evm audio machine driver */ static struct snd_soc_card dm6467_snd_soc_card_evm = { .name = "DaVinci DM6467 EVM", @@ -230,6 +257,17 @@ static struct snd_soc_device evm_snd_devdata = { .codec_data = &aic3x_setup, }; +/* evm audio subsystem */ +static struct snd_soc_device dm365_evm_snd_devdata = { + .card = &dm365_snd_soc_card_evm, +#ifdef CONFIG_SND_DM365_AIC3X_CODEC + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &aic3x_setup, +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + .codec_dev = &soc_codec_dev_cq93vc, +#endif +}; + /* evm audio subsystem */ static struct snd_soc_device dm6467_evm_snd_devdata = { .card = &dm6467_snd_soc_card_evm, @@ -258,12 +296,15 @@ static int __init evm_init(void) int index; int ret; - if (machine_is_davinci_evm() || machine_is_davinci_dm365_evm()) { + if (machine_is_davinci_evm()) { evm_snd_dev_data = &evm_snd_devdata; index = 0; } else if (machine_is_davinci_dm355_evm()) { evm_snd_dev_data = &evm_snd_devdata; index = 1; + } else if (machine_is_davinci_dm365_evm()) { + evm_snd_dev_data = &dm365_evm_snd_devdata; + index = 0; } else if (machine_is_davinci_dm6467_evm()) { evm_snd_dev_data = &dm6467_evm_snd_devdata; index = 0; -- cgit v1.2.3-18-g5258 From c3746a07f13f8711885fd3909c03477a1b47ab82 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 11 Mar 2010 16:26:21 +0200 Subject: ASoC: tlv320dac33: Start/stop sequence change To avoid race condition especially in FIFO modes the sequence for enabling and disabling the codec need to be changed. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320dac33.c | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index f9f367d29a9..e845c4b9066 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -310,7 +310,8 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) if (power) reg |= DAC33_PDNALLB; else - reg &= ~DAC33_PDNALLB; + reg &= ~(DAC33_PDNALLB | DAC33_OSCPDNB | + DAC33_DACRPDNB | DAC33_DACLPDNB); dac33_write(codec, DAC33_PWR_CTRL, reg); } @@ -634,26 +635,6 @@ static irqreturn_t dac33_interrupt_handler(int irq, void *dev) return IRQ_HANDLED; } -static void dac33_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 tlv320dac33_priv *dac33 = codec->private_data; - unsigned int pwr_ctrl; - - /* Stop pending workqueue */ - if (dac33->fifo_mode) - cancel_work_sync(&dac33->work); - - mutex_lock(&dac33->mutex); - pwr_ctrl = dac33_read_reg_cache(codec, DAC33_PWR_CTRL); - pwr_ctrl &= ~(DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB); - dac33_write(codec, DAC33_PWR_CTRL, pwr_ctrl); - mutex_unlock(&dac33->mutex); -} - static void dac33_oscwait(struct snd_soc_codec *codec) { int timeout = 20; @@ -751,6 +732,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) } mutex_lock(&dac33->mutex); + dac33_soft_power(codec, 0); dac33_soft_power(codec, 1); reg_tmp = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL); @@ -1185,7 +1167,6 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320dac33); #define DAC33_FORMATS SNDRV_PCM_FMTBIT_S16_LE static struct snd_soc_dai_ops dac33_dai_ops = { - .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, -- cgit v1.2.3-18-g5258 From eeb309a8a6ff86ce462e072bc95ecbe85618505e Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 11 Mar 2010 16:26:22 +0200 Subject: ASoC: tlv320dac33: Add option for keeping the BCLK running Platform data option for the codec to keep the BCLK clock continuously running in FIFO modes (codec master). OMAP3 McBSP when in slave mode needs continuous BCLK running on the serial bus in order to operate correctly. Since in FIFO mode the DAC33 can also shut down the BCLK clock and enable it only when it is needed, let the platforms decide if the CPU side needs the BCLK running or not. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/tlv320dac33-plat.h | 1 + sound/soc/codecs/tlv320dac33.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/sound/tlv320dac33-plat.h b/include/sound/tlv320dac33-plat.h index ac0665264bd..3f428d53195 100644 --- a/include/sound/tlv320dac33-plat.h +++ b/include/sound/tlv320dac33-plat.h @@ -15,6 +15,7 @@ struct tlv320dac33_platform_data { int power_gpio; + int keep_bclk; /* Keep the BCLK running in FIFO modes */ u8 burst_bclkdiv; }; diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index e845c4b9066..a6f19271aae 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -93,6 +93,8 @@ struct tlv320dac33_priv { unsigned int nsample; /* burst read amount from host */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ + int keep_bclk; /* Keep the BCLK continuously running + * in FIFO modes */ enum dac33_state state; }; @@ -803,7 +805,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) */ fifoctrl_a &= ~DAC33_FBYPAS; fifoctrl_a &= ~DAC33_FAUTO; - aictrl_b &= ~DAC33_BCLKON; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; break; case DAC33_FIFO_MODE7: /* @@ -814,7 +819,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) */ fifoctrl_a &= ~DAC33_FBYPAS; fifoctrl_a |= DAC33_FAUTO; - aictrl_b &= ~DAC33_BCLKON; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; break; default: /* @@ -1234,6 +1242,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, dac33->power_gpio = pdata->power_gpio; dac33->burst_bclkdiv = pdata->burst_bclkdiv; + dac33->keep_bclk = pdata->keep_bclk; dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; /* Disable FIFO use by default */ -- cgit v1.2.3-18-g5258 From 7dc976ed38a927fc9880bd3dc498a6a903d1ba74 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 3 Mar 2010 15:08:08 +0200 Subject: OMAP3: McBSP: Add interface for FIFO caused delay query New functions for querying the FIFO caused delay on both TX and RX path. On TX path the return value shows the number of used locations in the FIFO. On RX papth it returns the number of locations to be filled to reach the threshold value (DMA will be triggered to read the data out from the FIFO). Signed-off-by: Peter Ujfalusi Acked-by: Tony Lindgren Acked-by: Jarkko Nikula Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- arch/arm/plat-omap/include/plat/mcbsp.h | 6 ++++ arch/arm/plat-omap/mcbsp.c | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/arch/arm/plat-omap/include/plat/mcbsp.h b/arch/arm/plat-omap/include/plat/mcbsp.h index 39748354ce4..1bd7021336c 100644 --- a/arch/arm/plat-omap/include/plat/mcbsp.h +++ b/arch/arm/plat-omap/include/plat/mcbsp.h @@ -149,6 +149,8 @@ #define OMAP_MCBSP_REG_WAKEUPEN 0xA8 #define OMAP_MCBSP_REG_XCCR 0xAC #define OMAP_MCBSP_REG_RCCR 0xB0 +#define OMAP_MCBSP_REG_XBUFFSTAT 0xB4 +#define OMAP_MCBSP_REG_RBUFFSTAT 0xB8 #define OMAP_MCBSP_REG_SSELCR 0xBC #define OMAP_ST_REG_REV 0x00 @@ -471,6 +473,8 @@ void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold); void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold); u16 omap_mcbsp_get_max_tx_threshold(unsigned int id); u16 omap_mcbsp_get_max_rx_threshold(unsigned int id); +u16 omap_mcbsp_get_tx_delay(unsigned int id); +u16 omap_mcbsp_get_rx_delay(unsigned int id); int omap_mcbsp_get_dma_op_mode(unsigned int id); #else static inline void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) @@ -479,6 +483,8 @@ static inline void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold) { } static inline u16 omap_mcbsp_get_max_tx_threshold(unsigned int id) { return 0; } static inline u16 omap_mcbsp_get_max_rx_threshold(unsigned int id) { return 0; } +static inline u16 omap_mcbsp_get_tx_delay(unsigned int id) { return 0; } +static inline u16 omap_mcbsp_get_rx_delay(unsigned int id) { return 0; } static inline int omap_mcbsp_get_dma_op_mode(unsigned int id) { return 0; } #endif int omap_mcbsp_request(unsigned int id); diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index e47686e0a63..5e6d3096c72 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -561,6 +561,61 @@ u16 omap_mcbsp_get_max_rx_threshold(unsigned int id) } EXPORT_SYMBOL(omap_mcbsp_get_max_rx_threshold); +#define MCBSP2_FIFO_SIZE 0x500 /* 1024 + 256 locations */ +#define MCBSP1345_FIFO_SIZE 0x80 /* 128 locations */ +/* + * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO + */ +u16 omap_mcbsp_get_tx_delay(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + u16 buffstat; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return -ENODEV; + } + mcbsp = id_to_mcbsp_ptr(id); + + /* Returns the number of free locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, XBUFFSTAT); + + /* Number of slots are different in McBSP ports */ + if (mcbsp->id == 2) + return MCBSP2_FIFO_SIZE - buffstat; + else + return MCBSP1345_FIFO_SIZE - buffstat; +} +EXPORT_SYMBOL(omap_mcbsp_get_tx_delay); + +/* + * omap_mcbsp_get_rx_delay returns the number of free slots in the McBSP FIFO + * to reach the threshold value (when the DMA will be triggered to read it) + */ +u16 omap_mcbsp_get_rx_delay(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + u16 buffstat, threshold; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return -ENODEV; + } + mcbsp = id_to_mcbsp_ptr(id); + + /* Returns the number of used locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, RBUFFSTAT); + /* RX threshold */ + threshold = MCBSP_READ(mcbsp, THRSH1); + + /* Return the number of location till we reach the threshold limit */ + if (threshold <= buffstat) + return 0; + else + return threshold - buffstat; +} +EXPORT_SYMBOL(omap_mcbsp_get_rx_delay); + /* * omap_mcbsp_get_dma_op_mode just return the current configured * operating mode for the mcbsp channel -- cgit v1.2.3-18-g5258 From 75581d2459d3e6e5e796a42b362e4abb612bc958 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 3 Mar 2010 15:08:09 +0200 Subject: ASoC: OMAP3: Report delay caused by the internal FIFO Use the new delay calback function to report the delay through ALSA for application caused by the internal FIFO. Signed-off-by: Peter Ujfalusi Acked-by: Jarkko Nikula Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index e814a9591f7..2952fb046ed 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -256,6 +256,31 @@ static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, return err; } +static snd_pcm_sframes_t omap_mcbsp_dai_delay( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); + u16 fifo_use; + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + fifo_use = omap_mcbsp_get_tx_delay(mcbsp_data->bus_id); + else + fifo_use = omap_mcbsp_get_rx_delay(mcbsp_data->bus_id); + + /* + * Divide the used locations with the channel count to get the + * FIFO usage in samples (don't care about partial samples in the + * buffer). + */ + delay = fifo_use / substream->runtime->channels; + + return delay; +} + static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -607,6 +632,7 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .startup = omap_mcbsp_dai_startup, .shutdown = omap_mcbsp_dai_shutdown, .trigger = omap_mcbsp_dai_trigger, + .delay = omap_mcbsp_dai_delay, .hw_params = omap_mcbsp_dai_hw_params, .set_fmt = omap_mcbsp_dai_set_dai_fmt, .set_clkdiv = omap_mcbsp_dai_set_clkdiv, -- cgit v1.2.3-18-g5258 From 7e847894039d7590321de306fca2b1ae58662f29 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:20 +0100 Subject: linux/usb/audio.h: split header - Split the audio.h file in two to clearly denote the differences between the standards. - Add many more defines to audio-v2.h. Most of them are not currently used. - Replaced a magic value with a proper define Signed-off-by: Daniel Mack Acked-by: Greg Kroah-Hartman Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v2.h | 319 +++++++++++++++++++++++++++++++++++++++++++ include/linux/usb/audio.h | 50 +------ sound/usb/card.c | 3 +- sound/usb/endpoint.c | 1 + sound/usb/format.c | 1 + sound/usb/pcm.c | 5 +- 6 files changed, 333 insertions(+), 46 deletions(-) create mode 100644 include/linux/usb/audio-v2.h diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h new file mode 100644 index 00000000000..3b8560d233b --- /dev/null +++ b/include/linux/usb/audio-v2.h @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2010 Daniel Mack + * + * This software is distributed under the terms of the GNU General Public + * License ("GPL") version 2, as published by the Free Software Foundation. + * + * This file holds USB constants and structures defined + * by the USB Device Class Definition for Audio Devices in version 2.0. + * Comments below reference relevant sections of the documents contained + * in http://www.usb.org/developers/devclass_docs/Audio2.0_final.zip + */ + +#ifndef __LINUX_USB_AUDIO_V2_H +#define __LINUX_USB_AUDIO_V2_H + +#include + +/* v1.0 and v2.0 of this standard have many things in common. For the rest + * of the definitions, please refer to audio.h */ + +/* 4.7.2.1 Clock Source Descriptor */ + +struct uac_clock_source_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bClockID; + __u8 bmAttributes; + __u8 bmControls; + __u8 bAssocTerminal; + __u8 iClockSource; +} __attribute__((packed)); + +/* 4.7.2.2 Clock Source Descriptor */ + +struct uac_clock_selector_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bClockID; + __u8 bNrInPins; + __u8 bmControls; + __u8 baCSourceID[]; +} __attribute__((packed)); + +/* 4.9.2 Class-Specific AS Interface Descriptor */ + +struct uac_as_header_descriptor_v2 { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bTerminalLink; + __u8 bmControls; + __u8 bFormatType; + __u32 bmFormats; + __u8 bNrChannels; + __u32 bmChannelConfig; + __u8 iChannelNames; +} __attribute__((packed)); + + +/* A.7 Audio Function Category Codes */ +#define UAC2_FUNCTION_SUBCLASS_UNDEFINED 0x00 +#define UAC2_FUNCTION_DESKTOP_SPEAKER 0x01 +#define UAC2_FUNCTION_HOME_THEATER 0x02 +#define UAC2_FUNCTION_MICROPHONE 0x03 +#define UAC2_FUNCTION_HEADSET 0x04 +#define UAC2_FUNCTION_TELEPHONE 0x05 +#define UAC2_FUNCTION_CONVERTER 0x06 +#define UAC2_FUNCTION_SOUND_RECORDER 0x07 +#define UAC2_FUNCTION_IO_BOX 0x08 +#define UAC2_FUNCTION_MUSICAL_INSTRUMENT 0x09 +#define UAC2_FUNCTION_PRO_AUDIO 0x0a +#define UAC2_FUNCTION_AUDIO_VIDEO 0x0b +#define UAC2_FUNCTION_CONTROL_PANEL 0x0c +#define UAC2_FUNCTION_OTHER 0xff + +/* A.9 Audio Class-Specific AC Interface Descriptor Subtypes */ +/* see audio.h for the rest, which is identical to v1 */ +#define UAC2_EFFECT_UNIT 0x07 +#define UAC2_PROCESSING_UNIT_V2 0x08 +#define UAC2_EXTENSION_UNIT_V2 0x09 +#define UAC2_CLOCK_SOURCE 0x0a +#define UAC2_CLOCK_SELECTOR 0x0b +#define UAC2_CLOCK_MULTIPLIER 0x0c +#define UAC2_SAMPLE_RATE_CONVERTER 0x0d + +/* A.10 Audio Class-Specific AS Interface Descriptor Subtypes */ +/* see audio.h for the rest, which is identical to v1 */ +#define UAC2_ENCODER 0x03 +#define UAC2_DECODER 0x04 + +/* A.11 Effect Unit Effect Types */ +#define UAC2_EFFECT_UNDEFINED 0x00 +#define UAC2_EFFECT_PARAM_EQ 0x01 +#define UAC2_EFFECT_REVERB 0x02 +#define UAC2_EFFECT_MOD_DELAY 0x03 +#define UAC2_EFFECT_DYN_RANGE_COMP 0x04 + +/* A.12 Processing Unit Process Types */ +#define UAC2_PROCESS_UNDEFINED 0x00 +#define UAC2_PROCESS_UP_DOWNMIX 0x01 +#define UAC2_PROCESS_DOLBY_PROLOCIC 0x02 +#define UAC2_PROCESS_STEREO_EXTENDER 0x03 + +/* A.14 Audio Class-Specific Request Codes */ +#define UAC2_CS_CUR 0x01 +#define UAC2_CS_RANGE 0x02 + +/* A.15 Encoder Type Codes */ +#define UAC2_ENCODER_UNDEFINED 0x00 +#define UAC2_ENCODER_OTHER 0x01 +#define UAC2_ENCODER_MPEG 0x02 +#define UAC2_ENCODER_AC3 0x03 +#define UAC2_ENCODER_WMA 0x04 +#define UAC2_ENCODER_DTS 0x05 + +/* A.16 Decoder Type Codes */ +#define UAC2_DECODER_UNDEFINED 0x00 +#define UAC2_DECODER_OTHER 0x01 +#define UAC2_DECODER_MPEG 0x02 +#define UAC2_DECODER_AC3 0x03 +#define UAC2_DECODER_WMA 0x04 +#define UAC2_DECODER_DTS 0x05 + +/* A.17.1 Clock Source Control Selectors */ +#define UAC2_CS_UNDEFINED 0x00 +#define UAC2_CS_CONTROL_SAM_FREQ 0x01 +#define UAC2_CS_CONTROL_CLOCK_VALID 0x02 + +/* A.17.2 Clock Selector Control Selectors */ +#define UAC2_CX_UNDEFINED 0x00 +#define UAC2_CX_CLOCK_SELECTOR 0x01 + +/* A.17.3 Clock Multiplier Control Selectors */ +#define UAC2_CM_UNDEFINED 0x00 +#define UAC2_CM_NUMERATOR 0x01 +#define UAC2_CM_DENOMINTATOR 0x02 + +/* A.17.4 Terminal Control Selectors */ +#define UAC2_TE_UNDEFINED 0x00 +#define UAC2_TE_COPY_PROTECT 0x01 +#define UAC2_TE_CONNECTOR 0x02 +#define UAC2_TE_OVERLOAD 0x03 +#define UAC2_TE_CLUSTER 0x04 +#define UAC2_TE_UNDERFLOW 0x05 +#define UAC2_TE_OVERFLOW 0x06 +#define UAC2_TE_LATENCY 0x07 + +/* A.17.5 Mixer Control Selectors */ +#define UAC2_MU_UNDEFINED 0x00 +#define UAC2_MU_MIXER 0x01 +#define UAC2_MU_CLUSTER 0x02 +#define UAC2_MU_UNDERFLOW 0x03 +#define UAC2_MU_OVERFLOW 0x04 +#define UAC2_MU_LATENCY 0x05 + +/* A.17.6 Selector Control Selectors */ +#define UAC2_SU_UNDEFINED 0x00 +#define UAC2_SU_SELECTOR 0x01 +#define UAC2_SU_LATENCY 0x02 + +/* A.17.7 Feature Unit Control Selectors */ +/* see audio.h for the rest, which is identical to v1 */ +#define UAC2_FU_INPUT_GAIN 0x0b +#define UAC2_FU_INPUT_GAIN_PAD 0x0c +#define UAC2_FU_PHASE_INVERTER 0x0d +#define UAC2_FU_UNDERFLOW 0x0e +#define UAC2_FU_OVERFLOW 0x0f +#define UAC2_FU_LATENCY 0x10 + +/* A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors */ +#define UAC2_PE_UNDEFINED 0x00 +#define UAC2_PE_ENABLE 0x01 +#define UAC2_PE_CENTERFREQ 0x02 +#define UAC2_PE_QFACTOR 0x03 +#define UAC2_PE_GAIN 0x04 +#define UAC2_PE_UNDERFLOW 0x05 +#define UAC2_PE_OVERFLOW 0x06 +#define UAC2_PE_LATENCY 0x07 + +/* A.17.8.2 Reverberation Effect Unit Control Selectors */ +#define UAC2_RV_UNDEFINED 0x00 +#define UAC2_RV_ENABLE 0x01 +#define UAC2_RV_TYPE 0x02 +#define UAC2_RV_LEVEL 0x03 +#define UAC2_RV_TIME 0x04 +#define UAC2_RV_FEEDBACK 0x05 +#define UAC2_RV_PREDELAY 0x06 +#define UAC2_RV_DENSITY 0x07 +#define UAC2_RV_HIFREQ_ROLLOFF 0x08 +#define UAC2_RV_UNDERFLOW 0x09 +#define UAC2_RV_OVERFLOW 0x0a +#define UAC2_RV_LATENCY 0x0b + +/* A.17.8.3 Modulation Delay Effect Control Selectors */ +#define UAC2_MD_UNDEFINED 0x00 +#define UAC2_MD_ENABLE 0x01 +#define UAC2_MD_BALANCE 0x02 +#define UAC2_MD_RATE 0x03 +#define UAC2_MD_DEPTH 0x04 +#define UAC2_MD_TIME 0x05 +#define UAC2_MD_FEEDBACK 0x06 +#define UAC2_MD_UNDERFLOW 0x07 +#define UAC2_MD_OVERFLOW 0x08 +#define UAC2_MD_LATENCY 0x09 + +/* A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors */ +#define UAC2_DR_UNDEFINED 0x00 +#define UAC2_DR_ENABLE 0x01 +#define UAC2_DR_COMPRESSION_RATE 0x02 +#define UAC2_DR_MAXAMPL 0x03 +#define UAC2_DR_THRESHOLD 0x04 +#define UAC2_DR_ATTACK_TIME 0x05 +#define UAC2_DR_RELEASE_TIME 0x06 +#define UAC2_DR_UNDEFLOW 0x07 +#define UAC2_DR_OVERFLOW 0x08 +#define UAC2_DR_LATENCY 0x09 + +/* A.17.9.1 Up/Down-mix Processing Unit Control Selectors */ +#define UAC2_UD_UNDEFINED 0x00 +#define UAC2_UD_ENABLE 0x01 +#define UAC2_UD_MODE_SELECT 0x02 +#define UAC2_UD_CLUSTER 0x03 +#define UAC2_UD_UNDERFLOW 0x04 +#define UAC2_UD_OVERFLOW 0x05 +#define UAC2_UD_LATENCY 0x06 + +/* A.17.9.2 Dolby Prologic[tm] Processing Unit Control Selectors */ +#define UAC2_DP_UNDEFINED 0x00 +#define UAC2_DP_ENABLE 0x01 +#define UAC2_DP_MODE_SELECT 0x02 +#define UAC2_DP_CLUSTER 0x03 +#define UAC2_DP_UNDERFFLOW 0x04 +#define UAC2_DP_OVERFLOW 0x05 +#define UAC2_DP_LATENCY 0x06 + +/* A.17.9.3 Stereo Expander Processing Unit Control Selectors */ +#define UAC2_ST_EXT_UNDEFINED 0x00 +#define UAC2_ST_EXT_ENABLE 0x01 +#define UAC2_ST_EXT_WIDTH 0x02 +#define UAC2_ST_EXT_UNDEFLOW 0x03 +#define UAC2_ST_EXT_OVERFLOW 0x04 +#define UAC2_ST_EXT_LATENCY 0x05 + +/* A.17.10 Extension Unit Control Selectors */ +#define UAC2_XU_UNDEFINED 0x00 +#define UAC2_XU_ENABLE 0x01 +#define UAC2_XU_CLUSTER 0x02 +#define UAC2_XU_UNDERFLOW 0x03 +#define UAC2_XU_OVERFLOW 0x04 +#define UAC2_XU_LATENCY 0x05 + +/* A.17.11 AudioStreaming Interface Control Selectors */ +#define UAC2_AS_UNDEFINED 0x00 +#define UAC2_AS_ACT_ALT_SETTING 0x01 +#define UAC2_AS_VAL_ALT_SETTINGS 0x02 +#define UAC2_AS_AUDIO_DATA_FORMAT 0x03 + +/* A.17.12 Encoder Control Selectors */ +#define UAC2_EN_UNDEFINED 0x00 +#define UAC2_EN_BIT_RATE 0x01 +#define UAC2_EN_QUALITY 0x02 +#define UAC2_EN_VBR 0x03 +#define UAC2_EN_TYPE 0x04 +#define UAC2_EN_UNDERFLOW 0x05 +#define UAC2_EN_OVERFLOW 0x06 +#define UAC2_EN_ENCODER_ERROR 0x07 +#define UAC2_EN_PARAM1 0x08 +#define UAC2_EN_PARAM2 0x09 +#define UAC2_EN_PARAM3 0x0a +#define UAC2_EN_PARAM4 0x0b +#define UAC2_EN_PARAM5 0x0c +#define UAC2_EN_PARAM6 0x0d +#define UAC2_EN_PARAM7 0x0e +#define UAC2_EN_PARAM8 0x0f + +/* A.17.13.1 MPEG Decoder Control Selectors */ +#define UAC2_MPEG_UNDEFINED 0x00 +#define UAC2_MPEG_DUAL_CHANNEL 0x01 +#define UAC2_MPEG_SECOND_STEREO 0x02 +#define UAC2_MPEG_MULTILINGUAL 0x03 +#define UAC2_MPEG_DYN_RANGE 0x04 +#define UAC2_MPEG_SCALING 0x05 +#define UAC2_MPEG_HILO_SCALING 0x06 +#define UAC2_MPEG_UNDERFLOW 0x07 +#define UAC2_MPEG_OVERFLOW 0x08 +#define UAC2_MPEG_DECODER_ERROR 0x09 + +/* A17.13.2 AC3 Decoder Control Selectors */ +#define UAC2_AC3_UNDEFINED 0x00 +#define UAC2_AC3_MODE 0x01 +#define UAC2_AC3_DYN_RANGE 0x02 +#define UAC2_AC3_SCALING 0x03 +#define UAC2_AC3_HILO_SCALING 0x04 +#define UAC2_AC3_UNDERFLOW 0x05 +#define UAC2_AC3_OVERFLOW 0x06 +#define UAC2_AC3_DECODER_ERROR 0x07 + +/* A17.13.3 WMA Decoder Control Selectors */ +#define UAC2_WMA_UNDEFINED 0x00 +#define UAC2_WMA_UNDERFLOW 0x01 +#define UAC2_WMA_OVERFLOW 0x02 +#define UAC2_WMA_DECODER_ERROR 0x03 + +/* A17.13.4 DTS Decoder Control Selectors */ +#define UAC2_DTS_UNDEFINED 0x00 +#define UAC2_DTS_UNDERFLOW 0x01 +#define UAC2_DTS_OVERFLOW 0x02 +#define UAC2_DTS_DECODER_ERROR 0x03 + +/* A17.14 Endpoint Control Selectors */ +#define UAC2_EP_CS_UNDEFINED 0x00 +#define UAC2_EP_CS_PITCH 0x01 +#define UAC2_EP_CS_DATA_OVERRUN 0x02 +#define UAC2_EP_CS_DATA_UNDERRUN 0x03 + +#endif /* __LINUX_USB_AUDIO_V2_H */ + diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index 4d3e450e2b0..cdad728543a 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -13,6 +13,9 @@ * Comments below reference relevant sections of that document: * * http://www.usb.org/developers/devclass_docs/audio10.pdf + * + * Types and defines in this file are either specific to version 1.0 of + * this standard or common for newer versions. */ #ifndef __LINUX_USB_AUDIO_H @@ -20,14 +23,15 @@ #include +/* bInterfaceProtocol values to denote the version of the standard used */ +#define UAC_VERSION_1 0x00 +#define UAC_VERSION_2 0x20 + /* A.2 Audio Interface Subclass Codes */ #define USB_SUBCLASS_AUDIOCONTROL 0x01 #define USB_SUBCLASS_AUDIOSTREAMING 0x02 #define USB_SUBCLASS_MIDISTREAMING 0x03 -#define UAC_VERSION_1 0x00 -#define UAC_VERSION_2 0x20 - /* A.5 Audio Class-Specific AC Interface Descriptor Subtypes */ #define UAC_HEADER 0x01 #define UAC_INPUT_TERMINAL 0x02 @@ -38,15 +42,6 @@ #define UAC_PROCESSING_UNIT_V1 0x07 #define UAC_EXTENSION_UNIT_V1 0x08 -/* UAC v2.0 types */ -#define UAC_EFFECT_UNIT 0x07 -#define UAC_PROCESSING_UNIT_V2 0x08 -#define UAC_EXTENSION_UNIT_V2 0x09 -#define UAC_CLOCK_SOURCE 0x0a -#define UAC_CLOCK_SELECTOR 0x0b -#define UAC_CLOCK_MULTIPLIER 0x0c -#define UAC_SAMPLE_RATE_CONVERTER 0x0d - /* A.6 Audio Class-Specific AS Interface Descriptor Subtypes */ #define UAC_AS_GENERAL 0x01 #define UAC_FORMAT_TYPE 0x02 @@ -78,10 +73,6 @@ #define UAC_GET_STAT 0xff -/* Audio class v2.0 handles all the parameter calls differently */ -#define UAC2_CS_CUR 0x01 -#define UAC2_CS_RANGE 0x02 - /* MIDI - A.1 MS Class-Specific Interface Descriptor Subtypes */ #define UAC_MS_HEADER 0x01 #define UAC_MIDI_IN_JACK 0x02 @@ -200,19 +191,6 @@ struct uac_as_header_descriptor_v1 { __le16 wFormatTag; /* The Audio Data Format */ } __attribute__ ((packed)); -struct uac_as_header_descriptor_v2 { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bTerminalLink; - __u8 bmControls; - __u8 bFormatType; - __u32 bmFormats; - __u8 bNrChannels; - __u32 bmChannelConfig; - __u8 iChannelNames; -} __attribute__((packed)); - #define UAC_DT_AS_HEADER_SIZE 7 /* Formats - A.1.1 Audio Data Format Type I Codes */ @@ -277,7 +255,6 @@ struct uac_format_type_i_ext_descriptor { __u8 bSideBandProtocol; } __attribute__((packed)); - /* Formats - Audio Data Format Type I Codes */ #define UAC_FORMAT_TYPE_II_MPEG 0x1001 @@ -336,19 +313,6 @@ struct uac_iso_endpoint_descriptor { #define UAC_EP_CS_ATTR_PITCH_CONTROL 0x02 #define UAC_EP_CS_ATTR_FILL_MAX 0x80 -/* Audio class v2.0: CLOCK_SOURCE descriptor */ - -struct uac_clock_source_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bClockID; - __u8 bmAttributes; - __u8 bmControls; - __u8 bAssocTerminal; - __u8 iClockSource; -} __attribute__((packed)); - /* A.10.2 Feature Unit Control Selectors */ struct uac_feature_unit_descriptor { diff --git a/sound/usb/card.c b/sound/usb/card.c index 426aabc729d..78d12ff00e8 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -250,7 +251,7 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) * clock selectors and sample rate conversion units. */ cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, - NULL, UAC_CLOCK_SOURCE); + NULL, UAC2_CLOCK_SOURCE); if (!cs) { snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n"); diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 91850f84cad..b1309cdc1ac 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/sound/usb/format.c b/sound/usb/format.c index b613e0aaeb6..0e04efe9551 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index e0f3f87f99a..630e2203b34 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -215,7 +216,7 @@ static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface, data[3] = rate >> 24; if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - 0x0100, chip->clock_id << 8, + UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, data, sizeof(data), 1000)) < 0) { snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d (v2)\n", dev->devnum, iface, fmt->altsetting, rate); @@ -223,7 +224,7 @@ static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface, } if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - 0x0100, chip->clock_id << 8, + UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, data, sizeof(data), 1000)) < 0) { snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq (v2)\n", dev->devnum, iface, fmt->altsetting); -- cgit v1.2.3-18-g5258 From 45d760567a7d773237b8996584a4ae0440d5e369 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:21 +0100 Subject: ALSA: usb-mixer: use defines from audio.h No need for the private enum. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/format.c | 6 ++++-- sound/usb/usbmixer.c | 27 ++++++--------------------- sound/usb/usbmixer_maps.c | 4 ++-- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/sound/usb/format.c b/sound/usb/format.c index 0e04efe9551..fcadedd9454 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -218,7 +218,8 @@ static int parse_audio_format_rates_v2(struct snd_usb_audio *chip, /* get the number of sample rates first by only fetching 2 bytes */ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - 0x0100, chip->clock_id << 8, tmp, sizeof(tmp), 1000); + UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, + tmp, sizeof(tmp), 1000); if (ret < 0) { snd_printk(KERN_ERR "unable to retrieve number of sample rates\n"); @@ -236,7 +237,8 @@ static int parse_audio_format_rates_v2(struct snd_usb_audio *chip, /* now get the full information */ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - 0x0100, chip->clock_id << 8, data, data_size, 1000); + UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, + data, data_size, 1000); if (ret < 0) { snd_printk(KERN_ERR "unable to retrieve sample rate range\n"); diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c index 5c056837594..ab8f0f0b65b 100644 --- a/sound/usb/usbmixer.c +++ b/sound/usb/usbmixer.c @@ -136,21 +136,6 @@ struct usb_mixer_elem_info { u8 initialized; }; - -enum { - USB_FEATURE_NONE = 0, - USB_FEATURE_MUTE = 1, - USB_FEATURE_VOLUME, - USB_FEATURE_BASS, - USB_FEATURE_MID, - USB_FEATURE_TREBLE, - USB_FEATURE_GEQ, - USB_FEATURE_AGC, - USB_FEATURE_DELAY, - USB_FEATURE_BASSBOOST, - USB_FEATURE_LOUDNESS -}; - enum { USB_MIXER_BOOLEAN, USB_MIXER_INV_BOOLEAN, @@ -954,7 +939,7 @@ static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, control++; /* change from zero-based to 1-based value */ - if (control == USB_FEATURE_GEQ) { + if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) { /* FIXME: not supported yet */ return; } @@ -1001,8 +986,8 @@ static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, kctl->id.name, sizeof(kctl->id.name)); switch (control) { - case USB_FEATURE_MUTE: - case USB_FEATURE_VOLUME: + case UAC_MUTE_CONTROL: + case UAC_VOLUME_CONTROL: /* determine the control name. the rule is: * - if a name id is given in descriptor, use it. * - if the connected input can be determined, then use the name @@ -1029,9 +1014,9 @@ static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, len = append_ctl_name(kctl, " Playback"); } } - append_ctl_name(kctl, control == USB_FEATURE_MUTE ? + append_ctl_name(kctl, control == UAC_MUTE_CONTROL ? " Switch" : " Volume"); - if (control == USB_FEATURE_VOLUME) { + if (control == UAC_VOLUME_CONTROL) { kctl->tlv.c = mixer_vol_tlv; kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ | @@ -1120,7 +1105,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void snd_printk(KERN_INFO "usbmixer: master volume quirk for PCM2702 chip\n"); /* disable non-functional volume control */ - master_bits &= ~(1 << (USB_FEATURE_VOLUME - 1)); + master_bits &= ~UAC_FU_VOLUME; break; } if (channels > 0) diff --git a/sound/usb/usbmixer_maps.c b/sound/usb/usbmixer_maps.c index 79e903a6086..d93fc89beba 100644 --- a/sound/usb/usbmixer_maps.c +++ b/sound/usb/usbmixer_maps.c @@ -85,8 +85,8 @@ static struct usbmix_name_map extigy_map[] = { /* 16: MU (w/o controls) */ { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ { 17, "Channel Routing", 2 }, /* PU: mode select */ - { 18, "Tone Control - Bass", USB_FEATURE_BASS }, /* FU */ - { 18, "Tone Control - Treble", USB_FEATURE_TREBLE }, /* FU */ + { 18, "Tone Control - Bass", UAC_BASS_CONTROL }, /* FU */ + { 18, "Tone Control - Treble", UAC_TREBLE_CONTROL }, /* FU */ { 18, "Master Playback" }, /* FU; others */ /* 19: OT speaker */ /* 20: OT headphone */ -- cgit v1.2.3-18-g5258 From 7b1eda223debcba706ab989a09c4eecb327aebdf Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:22 +0100 Subject: ALSA: usb-mixer: factor out quirks Move all non-standard mixer controls and vendor-specific extensions to a separate file. Some structs need to be exported now. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 411 +++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_quirks.h | 13 ++ sound/usb/quirks.c | 1 + sound/usb/usbmixer.c | 425 ++--------------------------------------------- sound/usb/usbmixer.h | 45 ++++- 6 files changed, 480 insertions(+), 416 deletions(-) create mode 100644 sound/usb/mixer_quirks.c create mode 100644 sound/usb/mixer_quirks.h diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 0758d8dc8cd..744024a0a9f 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -4,6 +4,7 @@ snd-usb-audio-objs := card.o \ usbmixer.o \ + mixer_quirks.o \ proc.o \ quirks.o \ format.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c new file mode 100644 index 00000000000..d2f4dcdf59e --- /dev/null +++ b/sound/usb/mixer_quirks.c @@ -0,0 +1,411 @@ +/* + * USB Audio Driver for ALSA + * + * Quirks and vendor-specific extensions for mixer interfaces + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * + * 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. + * + * 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 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "usbaudio.h" +#include "usbmixer.h" +#include "mixer_quirks.h" +#include "helper.h" + +/* + * Sound Blaster remote control configuration + * + * format of remote control data: + * Extigy: xx 00 + * Audigy 2 NX: 06 80 xx 00 00 00 + * Live! 24-bit: 06 80 xx yy 22 83 + */ +static const struct rc_config { + u32 usb_id; + u8 offset; + u8 length; + u8 packet_length; + u8 min_packet_length; /* minimum accepted length of the URB result */ + u8 mute_mixer_id; + u32 mute_code; +} rc_configs[] = { + { USB_ID(0x041e, 0x3000), 0, 1, 2, 1, 18, 0x0013 }, /* Extigy */ + { USB_ID(0x041e, 0x3020), 2, 1, 6, 6, 18, 0x0013 }, /* Audigy 2 NX */ + { USB_ID(0x041e, 0x3040), 2, 2, 6, 6, 2, 0x6e91 }, /* Live! 24-bit */ + { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ +}; + +static void snd_usb_soundblaster_remote_complete(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + const struct rc_config *rc = mixer->rc_cfg; + u32 code; + + if (urb->status < 0 || urb->actual_length < rc->min_packet_length) + return; + + code = mixer->rc_buffer[rc->offset]; + if (rc->length == 2) + code |= mixer->rc_buffer[rc->offset + 1] << 8; + + /* the Mute button actually changes the mixer control */ + if (code == rc->mute_code) + snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); + mixer->rc_code = code; + wmb(); + wake_up(&mixer->rc_waitq); +} + +static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + int err; + u32 rc_code; + + if (count != 1 && count != 4) + return -EINVAL; + err = wait_event_interruptible(mixer->rc_waitq, + (rc_code = xchg(&mixer->rc_code, 0)) != 0); + if (err == 0) { + if (count == 1) + err = put_user(rc_code, buf); + else + err = put_user(rc_code, (u32 __user *)buf); + } + return err < 0 ? err : count; +} + +static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, + poll_table *wait) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + poll_wait(file, &mixer->rc_waitq, wait); + return mixer->rc_code ? POLLIN | POLLRDNORM : 0; +} + +static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hwdep; + int err, len, i; + + for (i = 0; i < ARRAY_SIZE(rc_configs); ++i) + if (rc_configs[i].usb_id == mixer->chip->usb_id) + break; + if (i >= ARRAY_SIZE(rc_configs)) + return 0; + mixer->rc_cfg = &rc_configs[i]; + + len = mixer->rc_cfg->packet_length; + + init_waitqueue_head(&mixer->rc_waitq); + err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep); + if (err < 0) + return err; + snprintf(hwdep->name, sizeof(hwdep->name), + "%s remote control", mixer->chip->card->shortname); + hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC; + hwdep->private_data = mixer; + hwdep->ops.read = snd_usb_sbrc_hwdep_read; + hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; + hwdep->exclusive = 1; + + mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->rc_urb) + return -ENOMEM; + mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL); + if (!mixer->rc_setup_packet) { + usb_free_urb(mixer->rc_urb); + mixer->rc_urb = NULL; + return -ENOMEM; + } + mixer->rc_setup_packet->bRequestType = + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + mixer->rc_setup_packet->bRequest = UAC_GET_MEM; + mixer->rc_setup_packet->wValue = cpu_to_le16(0); + mixer->rc_setup_packet->wIndex = cpu_to_le16(0); + mixer->rc_setup_packet->wLength = cpu_to_le16(len); + usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, + usb_rcvctrlpipe(mixer->chip->dev, 0), + (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, + snd_usb_soundblaster_remote_complete, mixer); + return 0; +} + +#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info + +static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + + ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index]; + return 0; +} + +static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int value = ucontrol->value.integer.value[0]; + int err, changed; + + if (value > 1) + return -EINVAL; + changed = value != mixer->audigy2nx_leds[index]; + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, index + 2, NULL, 0, 100); + if (err < 0) + return err; + mixer->audigy2nx_leds[index] = value; + return changed; +} + +static struct snd_kcontrol_new snd_audigy2nx_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CMSS LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Power LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Dolby Digital LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 2, + }, +}; + +static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) { + if (i > 1 && /* Live24ext has 2 LEDs only */ + (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048))) + break; + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_audigy2nx_controls[i], mixer)); + if (err < 0) + return err; + } + mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */ + return 0; +} + +static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const struct sb_jack { + int unitid; + const char *name; + } jacks_audigy2nx[] = { + {4, "dig in "}, + {7, "line in"}, + {19, "spk out"}, + {20, "hph out"}, + {-1, NULL} + }, jacks_live24ext[] = { + {4, "line in"}, /* &1=Line, &2=Mic*/ + {3, "hph out"}, /* headphones */ + {0, "RC "}, /* last command, 6 bytes see rc_config above */ + {-1, NULL} + }; + const struct sb_jack *jacks; + struct usb_mixer_interface *mixer = entry->private_data; + int i, err; + u8 buf[3]; + + snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) + jacks = jacks_audigy2nx; + else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) + jacks = jacks_live24ext; + else + return; + + for (i = 0; jacks[i].name; ++i) { + snd_iprintf(buffer, "%s: ", jacks[i].name); + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_rcvctrlpipe(mixer->chip->dev, 0), + UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0, + jacks[i].unitid << 8, buf, 3, 100); + if (err == 3 && (buf[0] == 3 || buf[0] == 6)) + snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); + else + snd_iprintf(buffer, "?\n"); + } +} + +static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = !!(mixer->xonar_u1_status & 0x02); + return 0; +} + +static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + u8 old_status, new_status; + int err, changed; + + old_status = mixer->xonar_u1_status; + if (ucontrol->value.integer.value[0]) + new_status = old_status | 0x02; + else + new_status = old_status & ~0x02; + changed = new_status != old_status; + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x08, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 50, 0, &new_status, 1, 100); + if (err < 0) + return err; + mixer->xonar_u1_status = new_status; + return changed; +} + +static struct snd_kcontrol_new snd_xonar_u1_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_xonar_u1_switch_get, + .put = snd_xonar_u1_switch_put, +}; + +static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_xonar_u1_output_switch, mixer)); + if (err < 0) + return err; + mixer->xonar_u1_status = 0x05; + return 0; +} + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id) +{ + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *cval; + int unitid = 12; /* SamleRate ExtensionUnit ID */ + + list_for_each_entry(mixer, &chip->mixer_list, list) { + cval = mixer->id_elems[unitid]; + if (cval) { + snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, + cval->control << 8, + samplerate_id); + snd_usb_mixer_notify_id(mixer, unitid); + } + break; + } +} + +int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) +{ + int err; + struct snd_info_entry *entry; + + if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) + return err; + + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) { + if ((err = snd_audigy2nx_controls_create(mixer)) < 0) + return err; + if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry)) + snd_info_set_text_ops(entry, mixer, + snd_audigy2nx_proc_read); + } + + if (mixer->chip->usb_id == USB_ID(0x0b05, 0x1739) || + mixer->chip->usb_id == USB_ID(0x0b05, 0x1743)) { + err = snd_xonar_u1_controls_create(mixer); + if (err < 0) + return err; + } + + return 0; +} + +void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, + int unitid) +{ + if (!mixer->rc_cfg) + return; + /* unit ids specific to Extigy/Audigy 2 NX: */ + switch (unitid) { + case 0: /* remote control */ + mixer->rc_urb->dev = mixer->chip->dev; + usb_submit_urb(mixer->rc_urb, GFP_ATOMIC); + break; + case 4: /* digital in jack */ + case 7: /* line in jacks */ + case 19: /* speaker out jacks */ + case 20: /* headphones out jack */ + break; + /* live24ext: 4 = line-in jack */ + case 3: /* hp-out jack (may actuate Mute) */ + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) + snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id); + break; + default: + snd_printd(KERN_DEBUG "memory change in unknown unit %d\n", unitid); + break; + } +} + diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h new file mode 100644 index 00000000000..bdbfab09381 --- /dev/null +++ b/sound/usb/mixer_quirks.h @@ -0,0 +1,13 @@ +#ifndef SND_USB_MIXER_QUIRKS_H +#define SND_USB_MIXER_QUIRKS_H + +int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer); + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id); + +void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, + int unitid); + +#endif /* SND_USB_MIXER_QUIRKS_H */ + diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 0c0b23b6379..a82cfeda21f 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -25,6 +25,7 @@ #include "usbaudio.h" #include "card.h" #include "usbmixer.h" +#include "mixer_quirks.h" #include "midi.h" #include "quirks.h" #include "helper.h" diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c index ab8f0f0b65b..ec2436e9532 100644 --- a/sound/usb/usbmixer.c +++ b/sound/usb/usbmixer.c @@ -43,60 +43,10 @@ #include "usbaudio.h" #include "usbmixer.h" #include "helper.h" - -/* - */ - -/* ignore error from controls - for debugging */ -/* #define IGNORE_CTL_ERROR */ - -/* - * Sound Blaster remote control configuration - * - * format of remote control data: - * Extigy: xx 00 - * Audigy 2 NX: 06 80 xx 00 00 00 - * Live! 24-bit: 06 80 xx yy 22 83 - */ -static const struct rc_config { - u32 usb_id; - u8 offset; - u8 length; - u8 packet_length; - u8 min_packet_length; /* minimum accepted length of the URB result */ - u8 mute_mixer_id; - u32 mute_code; -} rc_configs[] = { - { USB_ID(0x041e, 0x3000), 0, 1, 2, 1, 18, 0x0013 }, /* Extigy */ - { USB_ID(0x041e, 0x3020), 2, 1, 6, 6, 18, 0x0013 }, /* Audigy 2 NX */ - { USB_ID(0x041e, 0x3040), 2, 2, 6, 6, 2, 0x6e91 }, /* Live! 24-bit */ - { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ -}; +#include "mixer_quirks.h" #define MAX_ID_ELEMS 256 -struct usb_mixer_interface { - struct snd_usb_audio *chip; - unsigned int ctrlif; - struct list_head list; - unsigned int ignore_ctl_error; - struct urb *urb; - /* array[MAX_ID_ELEMS], indexed by unit id */ - struct usb_mixer_elem_info **id_elems; - - /* Sound Blaster remote control stuff */ - const struct rc_config *rc_cfg; - u32 rc_code; - wait_queue_head_t rc_waitq; - struct urb *rc_urb; - struct usb_ctrlrequest *rc_setup_packet; - u8 rc_buffer[6]; - - u8 audigy2nx_leds[3]; - u8 xonar_u1_status; -}; - - struct usb_audio_term { int id; int type; @@ -118,24 +68,6 @@ struct mixer_build { const struct usbmix_selector_map *selector_map; }; -#define MAX_CHANNELS 10 /* max logical channels */ - -struct usb_mixer_elem_info { - struct usb_mixer_interface *mixer; - struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ - struct snd_ctl_elem_id *elem_id; - unsigned int id; - unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ - int channels; - int val_type; - int min, max, res; - int dBmin, dBmax; - int cached; - int cache_val[MAX_CHANNELS]; - u8 initialized; -}; - enum { USB_MIXER_BOOLEAN, USB_MIXER_INV_BOOLEAN, @@ -431,7 +363,8 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval, * set a mixer value */ -static int set_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int value_set) +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set) { unsigned char buf[2]; int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; @@ -455,14 +388,14 @@ static int set_ctl_value(struct usb_mixer_elem_info *cval, int request, int vali static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value) { - return set_ctl_value(cval, UAC_SET_CUR, validx, value); + return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); } static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; - err = set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, + err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, value); if (err < 0) return err; @@ -751,7 +684,8 @@ static int get_min_max(struct usb_mixer_elem_info *cval, int default_min) int last_valid_res = cval->res; while (cval->res > 1) { - if (set_ctl_value(cval, UAC_SET_RES, (cval->control << 8) | minchn, cval->res / 2) < 0) + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + (cval->control << 8) | minchn, cval->res / 2) < 0) break; cval->res /= 2; } @@ -1808,8 +1742,7 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) return 0; } -static void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, - int unitid) +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) { struct usb_mixer_elem_info *info; @@ -1858,34 +1791,6 @@ static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, } } -static void snd_usb_mixer_memory_change(struct usb_mixer_interface *mixer, - int unitid) -{ - if (!mixer->rc_cfg) - return; - /* unit ids specific to Extigy/Audigy 2 NX: */ - switch (unitid) { - case 0: /* remote control */ - mixer->rc_urb->dev = mixer->chip->dev; - usb_submit_urb(mixer->rc_urb, GFP_ATOMIC); - break; - case 4: /* digital in jack */ - case 7: /* line in jacks */ - case 19: /* speaker out jacks */ - case 20: /* headphones out jack */ - break; - /* live24ext: 4 = line-in jack */ - case 3: /* hp-out jack (may actuate Mute) */ - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) - snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id); - break; - default: - snd_printd(KERN_DEBUG "memory change in unknown unit %d\n", unitid); - break; - } -} - static void snd_usb_mixer_status_complete(struct urb *urb) { struct usb_mixer_interface *mixer = urb->context; @@ -1903,7 +1808,7 @@ static void snd_usb_mixer_status_complete(struct urb *urb) if (!(buf[0] & 0x40)) snd_usb_mixer_notify_id(mixer, buf[1]); else - snd_usb_mixer_memory_change(mixer, buf[1]); + snd_usb_mixer_rc_memory_change(mixer, buf[1]); } } if (urb->status != -ENOENT && urb->status != -ECONNRESET) { @@ -1947,296 +1852,6 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) return 0; } -static void snd_usb_soundblaster_remote_complete(struct urb *urb) -{ - struct usb_mixer_interface *mixer = urb->context; - const struct rc_config *rc = mixer->rc_cfg; - u32 code; - - if (urb->status < 0 || urb->actual_length < rc->min_packet_length) - return; - - code = mixer->rc_buffer[rc->offset]; - if (rc->length == 2) - code |= mixer->rc_buffer[rc->offset + 1] << 8; - - /* the Mute button actually changes the mixer control */ - if (code == rc->mute_code) - snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); - mixer->rc_code = code; - wmb(); - wake_up(&mixer->rc_waitq); -} - -static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, - long count, loff_t *offset) -{ - struct usb_mixer_interface *mixer = hw->private_data; - int err; - u32 rc_code; - - if (count != 1 && count != 4) - return -EINVAL; - err = wait_event_interruptible(mixer->rc_waitq, - (rc_code = xchg(&mixer->rc_code, 0)) != 0); - if (err == 0) { - if (count == 1) - err = put_user(rc_code, buf); - else - err = put_user(rc_code, (u32 __user *)buf); - } - return err < 0 ? err : count; -} - -static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, - poll_table *wait) -{ - struct usb_mixer_interface *mixer = hw->private_data; - - poll_wait(file, &mixer->rc_waitq, wait); - return mixer->rc_code ? POLLIN | POLLRDNORM : 0; -} - -static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) -{ - struct snd_hwdep *hwdep; - int err, len, i; - - for (i = 0; i < ARRAY_SIZE(rc_configs); ++i) - if (rc_configs[i].usb_id == mixer->chip->usb_id) - break; - if (i >= ARRAY_SIZE(rc_configs)) - return 0; - mixer->rc_cfg = &rc_configs[i]; - - len = mixer->rc_cfg->packet_length; - - init_waitqueue_head(&mixer->rc_waitq); - err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep); - if (err < 0) - return err; - snprintf(hwdep->name, sizeof(hwdep->name), - "%s remote control", mixer->chip->card->shortname); - hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC; - hwdep->private_data = mixer; - hwdep->ops.read = snd_usb_sbrc_hwdep_read; - hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; - hwdep->exclusive = 1; - - mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!mixer->rc_urb) - return -ENOMEM; - mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL); - if (!mixer->rc_setup_packet) { - usb_free_urb(mixer->rc_urb); - mixer->rc_urb = NULL; - return -ENOMEM; - } - mixer->rc_setup_packet->bRequestType = - USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; - mixer->rc_setup_packet->bRequest = UAC_GET_MEM; - mixer->rc_setup_packet->wValue = cpu_to_le16(0); - mixer->rc_setup_packet->wIndex = cpu_to_le16(0); - mixer->rc_setup_packet->wLength = cpu_to_le16(len); - usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, - usb_rcvctrlpipe(mixer->chip->dev, 0), - (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, - snd_usb_soundblaster_remote_complete, mixer); - return 0; -} - -#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info - -static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - int index = kcontrol->private_value; - - ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index]; - return 0; -} - -static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - int index = kcontrol->private_value; - int value = ucontrol->value.integer.value[0]; - int err, changed; - - if (value > 1) - return -EINVAL; - changed = value != mixer->audigy2nx_leds[index]; - err = snd_usb_ctl_msg(mixer->chip->dev, - usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - value, index + 2, NULL, 0, 100); - if (err < 0) - return err; - mixer->audigy2nx_leds[index] = value; - return changed; -} - -static struct snd_kcontrol_new snd_audigy2nx_controls[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "CMSS LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 0, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Power LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 1, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Dolby Digital LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 2, - }, -}; - -static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) -{ - int i, err; - - for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) { - if (i > 1 && /* Live24ext has 2 LEDs only */ - (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3048))) - break; - err = snd_ctl_add(mixer->chip->card, - snd_ctl_new1(&snd_audigy2nx_controls[i], mixer)); - if (err < 0) - return err; - } - mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */ - return 0; -} - -static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, - struct snd_info_buffer *buffer) -{ - static const struct sb_jack { - int unitid; - const char *name; - } jacks_audigy2nx[] = { - {4, "dig in "}, - {7, "line in"}, - {19, "spk out"}, - {20, "hph out"}, - {-1, NULL} - }, jacks_live24ext[] = { - {4, "line in"}, /* &1=Line, &2=Mic*/ - {3, "hph out"}, /* headphones */ - {0, "RC "}, /* last command, 6 bytes see rc_config above */ - {-1, NULL} - }; - const struct sb_jack *jacks; - struct usb_mixer_interface *mixer = entry->private_data; - int i, err; - u8 buf[3]; - - snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) - jacks = jacks_audigy2nx; - else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) - jacks = jacks_live24ext; - else - return; - - for (i = 0; jacks[i].name; ++i) { - snd_iprintf(buffer, "%s: ", jacks[i].name); - err = snd_usb_ctl_msg(mixer->chip->dev, - usb_rcvctrlpipe(mixer->chip->dev, 0), - UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | - USB_RECIP_INTERFACE, 0, - jacks[i].unitid << 8, buf, 3, 100); - if (err == 3 && (buf[0] == 3 || buf[0] == 6)) - snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); - else - snd_iprintf(buffer, "?\n"); - } -} - -static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - - ucontrol->value.integer.value[0] = !!(mixer->xonar_u1_status & 0x02); - return 0; -} - -static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - u8 old_status, new_status; - int err, changed; - - old_status = mixer->xonar_u1_status; - if (ucontrol->value.integer.value[0]) - new_status = old_status | 0x02; - else - new_status = old_status & ~0x02; - changed = new_status != old_status; - err = snd_usb_ctl_msg(mixer->chip->dev, - usb_sndctrlpipe(mixer->chip->dev, 0), 0x08, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - 50, 0, &new_status, 1, 100); - if (err < 0) - return err; - mixer->xonar_u1_status = new_status; - return changed; -} - -static struct snd_kcontrol_new snd_xonar_u1_output_switch = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Digital Playback Switch", - .info = snd_ctl_boolean_mono_info, - .get = snd_xonar_u1_switch_get, - .put = snd_xonar_u1_switch_put, -}; - -static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) -{ - int err; - - err = snd_ctl_add(mixer->chip->card, - snd_ctl_new1(&snd_xonar_u1_output_switch, mixer)); - if (err < 0) - return err; - mixer->xonar_u1_status = 0x05; - return 0; -} - -void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, - unsigned char samplerate_id) -{ - struct usb_mixer_interface *mixer; - struct usb_mixer_elem_info *cval; - int unitid = 12; /* SamleRate ExtensionUnit ID */ - - list_for_each_entry(mixer, &chip->mixer_list, list) { - cval = mixer->id_elems[unitid]; - if (cval) { - set_cur_ctl_value(cval, cval->control << 8, - samplerate_id); - snd_usb_mixer_notify_id(mixer, unitid); - } - break; - } -} - int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, int ignore_error) { @@ -2277,25 +1892,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, (err = snd_usb_mixer_status_create(mixer)) < 0) goto _error; - if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) - goto _error; - - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) { - if ((err = snd_audigy2nx_controls_create(mixer)) < 0) - goto _error; - if (!snd_card_proc_new(chip->card, "audigy2nx", &entry)) - snd_info_set_text_ops(entry, mixer, - snd_audigy2nx_proc_read); - } - - if (mixer->chip->usb_id == USB_ID(0x0b05, 0x1739) || - mixer->chip->usb_id == USB_ID(0x0b05, 0x1743)) { - err = snd_xonar_u1_controls_create(mixer); - if (err < 0) - goto _error; - } + snd_usb_mixer_apply_create_quirk(mixer); err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops); if (err < 0) @@ -2316,7 +1913,7 @@ _error: void snd_usb_mixer_disconnect(struct list_head *p) { struct usb_mixer_interface *mixer; - + mixer = list_entry(p, struct usb_mixer_interface, list); usb_kill_urb(mixer->urb); usb_kill_urb(mixer->rc_urb); diff --git a/sound/usb/usbmixer.h b/sound/usb/usbmixer.h index e199e4bb02f..63101ae201c 100644 --- a/sound/usb/usbmixer.h +++ b/sound/usb/usbmixer.h @@ -1,11 +1,52 @@ #ifndef __USBMIXER_H #define __USBMIXER_H +struct usb_mixer_interface { + struct snd_usb_audio *chip; + unsigned int ctrlif; + struct list_head list; + unsigned int ignore_ctl_error; + struct urb *urb; + /* array[MAX_ID_ELEMS], indexed by unit id */ + struct usb_mixer_elem_info **id_elems; + + /* Sound Blaster remote control stuff */ + const struct rc_config *rc_cfg; + u32 rc_code; + wait_queue_head_t rc_waitq; + struct urb *rc_urb; + struct usb_ctrlrequest *rc_setup_packet; + u8 rc_buffer[6]; + + u8 audigy2nx_leds[3]; + u8 xonar_u1_status; +}; + +#define MAX_CHANNELS 10 /* max logical channels */ + +struct usb_mixer_elem_info { + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ + struct snd_ctl_elem_id *elem_id; + unsigned int id; + unsigned int control; /* CS or ICN (high byte) */ + unsigned int cmask; /* channel mask bitmap: 0 = master */ + int channels; + int val_type; + int min, max, res; + int dBmin, dBmax; + int cached; + int cache_val[MAX_CHANNELS]; + u8 initialized; +}; + int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, int ignore_error); void snd_usb_mixer_disconnect(struct list_head *p); -void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, - unsigned char samplerate_id); +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); + +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set); #endif /* __USBMIXER_H */ -- cgit v1.2.3-18-g5258 From f0b5e634ff25e02a64676022ee13284a9c810879 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:23 +0100 Subject: ALSA: usbmixer: rename usbmixer.[ch] -> mixer.[ch] For clearer namespace, also rename usbmixer_maps.c -> mixer_maps.c Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/usb/Makefile | 2 +- sound/usb/card.c | 2 +- sound/usb/mixer.c | 1920 +++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer.h | 52 ++ sound/usb/mixer_maps.c | 376 +++++++++ sound/usb/mixer_quirks.c | 2 +- sound/usb/quirks.c | 2 +- sound/usb/usbmixer.c | 1920 --------------------------------------------- sound/usb/usbmixer.h | 52 -- sound/usb/usbmixer_maps.c | 376 --------- 10 files changed, 2352 insertions(+), 2352 deletions(-) create mode 100644 sound/usb/mixer.c create mode 100644 sound/usb/mixer.h create mode 100644 sound/usb/mixer_maps.c delete mode 100644 sound/usb/usbmixer.c delete mode 100644 sound/usb/usbmixer.h delete mode 100644 sound/usb/usbmixer_maps.c diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 744024a0a9f..e7ac7f493a8 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -3,7 +3,7 @@ # snd-usb-audio-objs := card.o \ - usbmixer.o \ + mixer.o \ mixer_quirks.o \ proc.o \ quirks.o \ diff --git a/sound/usb/card.c b/sound/usb/card.c index 78d12ff00e8..0bd62a1aa4f 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -56,7 +56,7 @@ #include "usbaudio.h" #include "card.h" #include "midi.h" -#include "usbmixer.h" +#include "mixer.h" #include "proc.h" #include "quirks.h" #include "endpoint.h" diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c new file mode 100644 index 00000000000..4e7c2fd9e3b --- /dev/null +++ b/sound/usb/mixer.c @@ -0,0 +1,1920 @@ +/* + * (Tentative) USB Audio Driver for ALSA + * + * Mixer control part + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * + * 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. + * + * 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "mixer_quirks.h" + +#define MAX_ID_ELEMS 256 + +struct usb_audio_term { + int id; + int type; + int channels; + unsigned int chconfig; + int name; +}; + +struct usbmix_name_map; + +struct mixer_build { + struct snd_usb_audio *chip; + struct usb_mixer_interface *mixer; + unsigned char *buffer; + unsigned int buflen; + DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS); + struct usb_audio_term oterm; + const struct usbmix_name_map *map; + const struct usbmix_selector_map *selector_map; +}; + +enum { + USB_MIXER_BOOLEAN, + USB_MIXER_INV_BOOLEAN, + USB_MIXER_S8, + USB_MIXER_U8, + USB_MIXER_S16, + USB_MIXER_U16, +}; + +enum { + USB_PROC_UPDOWN = 1, + USB_PROC_UPDOWN_SWITCH = 1, + USB_PROC_UPDOWN_MODE_SEL = 2, + + USB_PROC_PROLOGIC = 2, + USB_PROC_PROLOGIC_SWITCH = 1, + USB_PROC_PROLOGIC_MODE_SEL = 2, + + USB_PROC_3DENH = 3, + USB_PROC_3DENH_SWITCH = 1, + USB_PROC_3DENH_SPACE = 2, + + USB_PROC_REVERB = 4, + USB_PROC_REVERB_SWITCH = 1, + USB_PROC_REVERB_LEVEL = 2, + USB_PROC_REVERB_TIME = 3, + USB_PROC_REVERB_DELAY = 4, + + USB_PROC_CHORUS = 5, + USB_PROC_CHORUS_SWITCH = 1, + USB_PROC_CHORUS_LEVEL = 2, + USB_PROC_CHORUS_RATE = 3, + USB_PROC_CHORUS_DEPTH = 4, + + USB_PROC_DCR = 6, + USB_PROC_DCR_SWITCH = 1, + USB_PROC_DCR_RATIO = 2, + USB_PROC_DCR_MAX_AMP = 3, + USB_PROC_DCR_THRESHOLD = 4, + USB_PROC_DCR_ATTACK = 5, + USB_PROC_DCR_RELEASE = 6, +}; + +/*E-mu 0202(0404) eXtension Unit(XU) control*/ +enum { + USB_XU_CLOCK_RATE = 0xe301, + USB_XU_CLOCK_SOURCE = 0xe302, + USB_XU_DIGITAL_IO_STATUS = 0xe303, + USB_XU_DEVICE_OPTIONS = 0xe304, + USB_XU_DIRECT_MONITORING = 0xe305, + USB_XU_METERING = 0xe306 +}; +enum { + USB_XU_CLOCK_SOURCE_SELECTOR = 0x02, /* clock source*/ + USB_XU_CLOCK_RATE_SELECTOR = 0x03, /* clock rate */ + USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01, /* the spdif format */ + USB_XU_SOFT_LIMIT_SELECTOR = 0x03 /* soft limiter */ +}; + +/* + * manual mapping of mixer names + * if the mixer topology is too complicated and the parsed names are + * ambiguous, add the entries in usbmixer_maps.c. + */ +#include "mixer_maps.c" + +static const struct usbmix_name_map * +find_map(struct mixer_build *state, int unitid, int control) +{ + const struct usbmix_name_map *p = state->map; + + if (!p) + return NULL; + + for (p = state->map; p->id; p++) { + if (p->id == unitid && + (!control || !p->control || control == p->control)) + return p; + } + return NULL; +} + +/* get the mapped name if the unit matches */ +static int +check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen) +{ + if (!p || !p->name) + return 0; + + buflen--; + return strlcpy(buf, p->name, buflen); +} + +/* check whether the control should be ignored */ +static inline int +check_ignored_ctl(const struct usbmix_name_map *p) +{ + if (!p || p->name || p->dB) + return 0; + return 1; +} + +/* dB mapping */ +static inline void check_mapped_dB(const struct usbmix_name_map *p, + struct usb_mixer_elem_info *cval) +{ + if (p && p->dB) { + cval->dBmin = p->dB->min; + cval->dBmax = p->dB->max; + } +} + +/* get the mapped selector source name */ +static int check_mapped_selector_name(struct mixer_build *state, int unitid, + int index, char *buf, int buflen) +{ + const struct usbmix_selector_map *p; + + if (! state->selector_map) + return 0; + for (p = state->selector_map; p->id; p++) { + if (p->id == unitid && index < p->count) + return strlcpy(buf, p->names[index], buflen); + } + return 0; +} + +/* + * find an audio control unit with the given unit id + */ +static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit) +{ + unsigned char *p; + + p = NULL; + while ((p = snd_usb_find_desc(state->buffer, state->buflen, p, + USB_DT_CS_INTERFACE)) != NULL) { + if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC_EXTENSION_UNIT_V1 && p[3] == unit) + return p; + } + return NULL; +} + + +/* + * copy a string with the given id + */ +static int snd_usb_copy_string_desc(struct mixer_build *state, int index, char *buf, int maxlen) +{ + int len = usb_string(state->chip->dev, index, buf, maxlen - 1); + buf[len] = 0; + return len; +} + +/* + * convert from the byte/word on usb descriptor to the zero-based integer + */ +static int convert_signed_value(struct usb_mixer_elem_info *cval, int val) +{ + switch (cval->val_type) { + case USB_MIXER_BOOLEAN: + return !!val; + case USB_MIXER_INV_BOOLEAN: + return !val; + case USB_MIXER_U8: + val &= 0xff; + break; + case USB_MIXER_S8: + val &= 0xff; + if (val >= 0x80) + val -= 0x100; + break; + case USB_MIXER_U16: + val &= 0xffff; + break; + case USB_MIXER_S16: + val &= 0xffff; + if (val >= 0x8000) + val -= 0x10000; + break; + } + return val; +} + +/* + * convert from the zero-based int to the byte/word for usb descriptor + */ +static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val) +{ + switch (cval->val_type) { + case USB_MIXER_BOOLEAN: + return !!val; + case USB_MIXER_INV_BOOLEAN: + return !val; + case USB_MIXER_S8: + case USB_MIXER_U8: + return val & 0xff; + case USB_MIXER_S16: + case USB_MIXER_U16: + return val & 0xffff; + } + return 0; /* not reached */ +} + +static int get_relative_value(struct usb_mixer_elem_info *cval, int val) +{ + if (! cval->res) + cval->res = 1; + if (val < cval->min) + return 0; + else if (val >= cval->max) + return (cval->max - cval->min + cval->res - 1) / cval->res; + else + return (val - cval->min) / cval->res; +} + +static int get_abs_value(struct usb_mixer_elem_info *cval, int val) +{ + if (val < 0) + return cval->min; + if (! cval->res) + cval->res = 1; + val *= cval->res; + val += cval->min; + if (val > cval->max) + return cval->max; + return val; +} + + +/* + * retrieve a mixer value + */ + +static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +{ + unsigned char buf[2]; + int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; + int timeout = 10; + + while (timeout-- > 0) { + if (snd_usb_ctl_msg(cval->mixer->chip->dev, + usb_rcvctrlpipe(cval->mixer->chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, cval->mixer->ctrlif | (cval->id << 8), + buf, val_len, 100) >= val_len) { + *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); + return 0; + } + } + snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); + return -EINVAL; +} + +static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value) +{ + return get_ctl_value(cval, UAC_GET_CUR, validx, value); +} + +/* channel = 0: master, 1 = first channel */ +static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, + int channel, int *value) +{ + return get_ctl_value(cval, UAC_GET_CUR, (cval->control << 8) | channel, value); +} + +static int get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value) +{ + int err; + + if (cval->cached & (1 << channel)) { + *value = cval->cache_val[index]; + return 0; + } + err = get_cur_mix_raw(cval, channel, value); + if (err < 0) { + if (!cval->mixer->ignore_ctl_error) + snd_printd(KERN_ERR "cannot get current value for " + "control %d ch %d: err = %d\n", + cval->control, channel, err); + return err; + } + cval->cached |= 1 << channel; + cval->cache_val[index] = *value; + return 0; +} + + +/* + * set a mixer value + */ + +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set) +{ + unsigned char buf[2]; + int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; + int timeout = 10; + + value_set = convert_bytes_value(cval, value_set); + buf[0] = value_set & 0xff; + buf[1] = (value_set >> 8) & 0xff; + while (timeout-- > 0) + if (snd_usb_ctl_msg(cval->mixer->chip->dev, + usb_sndctrlpipe(cval->mixer->chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + validx, cval->mixer->ctrlif | (cval->id << 8), + buf, val_len, 100) >= 0) + return 0; + snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", + request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type, buf[0], buf[1]); + return -EINVAL; +} + +static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value) +{ + return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); +} + +static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value) +{ + int err; + err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, + value); + if (err < 0) + return err; + cval->cached |= 1 << channel; + cval->cache_val[index] = value; + return 0; +} + +/* + * TLV callback for mixer volume controls + */ +static int mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + DECLARE_TLV_DB_MINMAX(scale, 0, 0); + + if (size < sizeof(scale)) + return -ENOMEM; + scale[2] = cval->dBmin; + scale[3] = cval->dBmax; + if (copy_to_user(_tlv, scale, sizeof(scale))) + return -EFAULT; + return 0; +} + +/* + * parser routines begin here... + */ + +static int parse_audio_unit(struct mixer_build *state, int unitid); + + +/* + * check if the input/output channel routing is enabled on the given bitmap. + * used for mixer unit parser + */ +static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs) +{ + int idx = ich * num_outs + och; + return bmap[idx >> 3] & (0x80 >> (idx & 7)); +} + + +/* + * add an alsa control element + * search and increment the index until an empty slot is found. + * + * if failed, give up and free the control instance. + */ + +static int add_control_to_empty(struct mixer_build *state, struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *cval = kctl->private_data; + int err; + + while (snd_ctl_find_id(state->chip->card, &kctl->id)) + kctl->id.index++; + if ((err = snd_ctl_add(state->chip->card, kctl)) < 0) { + snd_printd(KERN_ERR "cannot add control (err = %d)\n", err); + return err; + } + cval->elem_id = &kctl->id; + cval->next_id_elem = state->mixer->id_elems[cval->id]; + state->mixer->id_elems[cval->id] = cval; + return 0; +} + + +/* + * get a terminal name string + */ + +static struct iterm_name_combo { + int type; + char *name; +} iterm_names[] = { + { 0x0300, "Output" }, + { 0x0301, "Speaker" }, + { 0x0302, "Headphone" }, + { 0x0303, "HMD Audio" }, + { 0x0304, "Desktop Speaker" }, + { 0x0305, "Room Speaker" }, + { 0x0306, "Com Speaker" }, + { 0x0307, "LFE" }, + { 0x0600, "External In" }, + { 0x0601, "Analog In" }, + { 0x0602, "Digital In" }, + { 0x0603, "Line" }, + { 0x0604, "Legacy In" }, + { 0x0605, "IEC958 In" }, + { 0x0606, "1394 DA Stream" }, + { 0x0607, "1394 DV Stream" }, + { 0x0700, "Embedded" }, + { 0x0701, "Noise Source" }, + { 0x0702, "Equalization Noise" }, + { 0x0703, "CD" }, + { 0x0704, "DAT" }, + { 0x0705, "DCC" }, + { 0x0706, "MiniDisk" }, + { 0x0707, "Analog Tape" }, + { 0x0708, "Phonograph" }, + { 0x0709, "VCR Audio" }, + { 0x070a, "Video Disk Audio" }, + { 0x070b, "DVD Audio" }, + { 0x070c, "TV Tuner Audio" }, + { 0x070d, "Satellite Rec Audio" }, + { 0x070e, "Cable Tuner Audio" }, + { 0x070f, "DSS Audio" }, + { 0x0710, "Radio Receiver" }, + { 0x0711, "Radio Transmitter" }, + { 0x0712, "Multi-Track Recorder" }, + { 0x0713, "Synthesizer" }, + { 0 }, +}; + +static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm, + unsigned char *name, int maxlen, int term_only) +{ + struct iterm_name_combo *names; + + if (iterm->name) + return snd_usb_copy_string_desc(state, iterm->name, name, maxlen); + + /* virtual type - not a real terminal */ + if (iterm->type >> 16) { + if (term_only) + return 0; + switch (iterm->type >> 16) { + case UAC_SELECTOR_UNIT: + strcpy(name, "Selector"); return 8; + case UAC_PROCESSING_UNIT_V1: + strcpy(name, "Process Unit"); return 12; + case UAC_EXTENSION_UNIT_V1: + strcpy(name, "Ext Unit"); return 8; + case UAC_MIXER_UNIT: + strcpy(name, "Mixer"); return 5; + default: + return sprintf(name, "Unit %d", iterm->id); + } + } + + switch (iterm->type & 0xff00) { + case 0x0100: + strcpy(name, "PCM"); return 3; + case 0x0200: + strcpy(name, "Mic"); return 3; + case 0x0400: + strcpy(name, "Headset"); return 7; + case 0x0500: + strcpy(name, "Phone"); return 5; + } + + for (names = iterm_names; names->type; names++) + if (names->type == iterm->type) { + strcpy(name, names->name); + return strlen(names->name); + } + return 0; +} + + +/* + * parse the source unit recursively until it reaches to a terminal + * or a branched unit. + */ +static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) +{ + unsigned char *p1; + + memset(term, 0, sizeof(*term)); + while ((p1 = find_audio_control_unit(state, id)) != NULL) { + term->id = id; + switch (p1[2]) { + case UAC_INPUT_TERMINAL: + term->type = combine_word(p1 + 4); + term->channels = p1[7]; + term->chconfig = combine_word(p1 + 8); + term->name = p1[11]; + return 0; + case UAC_FEATURE_UNIT: + id = p1[4]; + break; /* continue to parse */ + case UAC_MIXER_UNIT: + term->type = p1[2] << 16; /* virtual type */ + term->channels = p1[5 + p1[4]]; + term->chconfig = combine_word(p1 + 6 + p1[4]); + term->name = p1[p1[0] - 1]; + return 0; + case UAC_SELECTOR_UNIT: + /* call recursively to retrieve the channel info */ + if (check_input_term(state, p1[5], term) < 0) + return -ENODEV; + term->type = p1[2] << 16; /* virtual type */ + term->id = id; + term->name = p1[9 + p1[0] - 1]; + return 0; + case UAC_PROCESSING_UNIT_V1: + case UAC_EXTENSION_UNIT_V1: + if (p1[6] == 1) { + id = p1[7]; + break; /* continue to parse */ + } + term->type = p1[2] << 16; /* virtual type */ + term->channels = p1[7 + p1[6]]; + term->chconfig = combine_word(p1 + 8 + p1[6]); + term->name = p1[12 + p1[6] + p1[11 + p1[6]]]; + return 0; + default: + return -ENODEV; + } + } + return -ENODEV; +} + + +/* + * Feature Unit + */ + +/* feature unit control information */ +struct usb_feature_control_info { + const char *name; + unsigned int type; /* control type (mute, volume, etc.) */ +}; + +static struct usb_feature_control_info audio_feature_info[] = { + { "Mute", USB_MIXER_INV_BOOLEAN }, + { "Volume", USB_MIXER_S16 }, + { "Tone Control - Bass", USB_MIXER_S8 }, + { "Tone Control - Mid", USB_MIXER_S8 }, + { "Tone Control - Treble", USB_MIXER_S8 }, + { "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */ + { "Auto Gain Control", USB_MIXER_BOOLEAN }, + { "Delay Control", USB_MIXER_U16 }, + { "Bass Boost", USB_MIXER_BOOLEAN }, + { "Loudness", USB_MIXER_BOOLEAN }, +}; + + +/* private_free callback */ +static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +{ + kfree(kctl->private_data); + kctl->private_data = NULL; +} + + +/* + * interface to ALSA control for feature/mixer units + */ + +/* + * retrieve the minimum and maximum values for the specified control + */ +static int get_min_max(struct usb_mixer_elem_info *cval, int default_min) +{ + /* for failsafe */ + cval->min = default_min; + cval->max = cval->min + 1; + cval->res = 1; + cval->dBmin = cval->dBmax = 0; + + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) { + cval->initialized = 1; + } else { + int minchn = 0; + if (cval->cmask) { + int i; + for (i = 0; i < MAX_CHANNELS; i++) + if (cval->cmask & (1 << i)) { + minchn = i + 1; + break; + } + } + if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || + get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { + snd_printd(KERN_ERR "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->id, cval->mixer->ctrlif, cval->control, cval->id); + return -EINVAL; + } + if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) { + cval->res = 1; + } else { + int last_valid_res = cval->res; + + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + (cval->control << 8) | minchn, cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) + cval->res = last_valid_res; + } + if (cval->res == 0) + cval->res = 1; + + /* Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually + * reacting. They don't return errors but simply clip + * to the lower aligned value. + */ + if (cval->min + cval->res < cval->max) { + int last_valid_res = cval->res; + int saved, test, check; + get_cur_mix_raw(cval, minchn, &saved); + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + if (test < cval->min || test > cval->max || + set_cur_mix_value(cval, minchn, 0, test) || + get_cur_mix_raw(cval, minchn, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + cval->res *= 2; + } + set_cur_mix_value(cval, minchn, 0, saved); + } + + cval->initialized = 1; + } + + /* USB descriptions contain the dB scale in 1/256 dB unit + * while ALSA TLV contains in 1/100 dB unit + */ + cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256; + cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256; + if (cval->dBmin > cval->dBmax) { + /* something is wrong; assume it's either from/to 0dB */ + if (cval->dBmin < 0) + cval->dBmax = 0; + else if (cval->dBmin > 0) + cval->dBmin = 0; + if (cval->dBmin > cval->dBmax) { + /* totally crap, return an error */ + return -EINVAL; + } + } + + return 0; +} + + +/* get a feature/mixer unit info */ +static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = cval->channels; + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) { + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + } else { + if (! cval->initialized) + get_min_max(cval, 0); + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + (cval->max - cval->min + cval->res - 1) / cval->res; + } + return 0; +} + +/* get the current value from feature/mixer unit */ +static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int c, cnt, val, err; + + ucontrol->value.integer.value[0] = cval->min; + if (cval->cmask) { + cnt = 0; + for (c = 0; c < MAX_CHANNELS; c++) { + if (!(cval->cmask & (1 << c))) + continue; + err = get_cur_mix_value(cval, c + 1, cnt, &val); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = get_relative_value(cval, val); + ucontrol->value.integer.value[cnt] = val; + cnt++; + } + return 0; + } else { + /* master channel */ + err = get_cur_mix_value(cval, 0, 0, &val); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = get_relative_value(cval, val); + ucontrol->value.integer.value[0] = val; + } + return 0; +} + +/* put the current value to feature/mixer unit */ +static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int c, cnt, val, oval, err; + int changed = 0; + + if (cval->cmask) { + cnt = 0; + for (c = 0; c < MAX_CHANNELS; c++) { + if (!(cval->cmask & (1 << c))) + continue; + err = get_cur_mix_value(cval, c + 1, cnt, &oval); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = ucontrol->value.integer.value[cnt]; + val = get_abs_value(cval, val); + if (oval != val) { + set_cur_mix_value(cval, c + 1, cnt, val); + changed = 1; + } + cnt++; + } + } else { + /* master channel */ + err = get_cur_mix_value(cval, 0, 0, &oval); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_mix_value(cval, 0, 0, val); + changed = 1; + } + } + return changed; +} + +static struct snd_kcontrol_new usb_feature_unit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later manually */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_feature_get, + .put = mixer_ctl_feature_put, +}; + + +/* + * build a feature control + */ + +static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) +{ + return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); +} + +static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, int unitid) +{ + unsigned int len = 0; + int mapped_name = 0; + int nameid = desc[desc[0] - 1]; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *cval; + const struct usbmix_name_map *map; + + control++; /* change from zero-based to 1-based value */ + + if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) { + /* FIXME: not supported yet */ + return; + } + + map = find_map(state, unitid, control); + if (check_ignored_ctl(map)) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (! cval) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + return; + } + cval->mixer = state->mixer; + cval->id = unitid; + cval->control = control; + cval->cmask = ctl_mask; + cval->val_type = audio_feature_info[control-1].type; + if (ctl_mask == 0) + cval->channels = 1; /* master channel */ + else { + int i, c = 0; + for (i = 0; i < 16; i++) + if (ctl_mask & (1 << i)) + c++; + cval->channels = c; + } + + /* get min/max values */ + get_min_max(cval, 0); + + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + if (! kctl) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + kfree(cval); + return; + } + kctl->private_free = usb_mixer_elem_free; + + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + mapped_name = len != 0; + if (! len && nameid) + len = snd_usb_copy_string_desc(state, nameid, + kctl->id.name, sizeof(kctl->id.name)); + + switch (control) { + case UAC_MUTE_CONTROL: + case UAC_VOLUME_CONTROL: + /* determine the control name. the rule is: + * - if a name id is given in descriptor, use it. + * - if the connected input can be determined, then use the name + * of terminal type. + * - if the connected output can be determined, use it. + * - otherwise, anonymous name. + */ + if (! len) { + len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1); + if (! len) + len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); + if (! len) + len = snprintf(kctl->id.name, sizeof(kctl->id.name), + "Feature %d", unitid); + } + /* determine the stream direction: + * if the connected output is USB stream, then it's likely a + * capture stream. otherwise it should be playback (hopefully :) + */ + if (! mapped_name && ! (state->oterm.type >> 16)) { + if ((state->oterm.type & 0xff00) == 0x0100) { + len = append_ctl_name(kctl, " Capture"); + } else { + len = append_ctl_name(kctl, " Playback"); + } + } + append_ctl_name(kctl, control == UAC_MUTE_CONTROL ? + " Switch" : " Volume"); + if (control == UAC_VOLUME_CONTROL) { + kctl->tlv.c = mixer_vol_tlv; + kctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + check_mapped_dB(map, cval); + } + break; + + default: + if (! len) + strlcpy(kctl->id.name, audio_feature_info[control-1].name, + sizeof(kctl->id.name)); + break; + } + + /* volume control quirks */ + switch (state->chip->usb_id) { + case USB_ID(0x0471, 0x0101): + case USB_ID(0x0471, 0x0104): + case USB_ID(0x0471, 0x0105): + case USB_ID(0x0672, 0x1041): + /* quirk for UDA1321/N101. + * note that detection between firmware 2.1.1.7 (N101) + * and later 2.1.1.21 is not very clear from datasheets. + * I hope that the min value is -15360 for newer firmware --jk + */ + if (!strcmp(kctl->id.name, "PCM Playback Volume") && + cval->min == -15616) { + snd_printk(KERN_INFO + "set volume quirk for UDA1321/N101 chip\n"); + cval->max = -256; + } + break; + + case USB_ID(0x046d, 0x09a4): + if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + snd_printk(KERN_INFO + "set volume quirk for QuickCam E3500\n"); + cval->min = 6080; + cval->max = 8768; + cval->res = 192; + } + break; + + } + + snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); + add_control_to_empty(state, kctl); +} + + + +/* + * parse a feature unit + * + * most of controlls are defined here. + */ +static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr) +{ + int channels, i, j; + struct usb_audio_term iterm; + unsigned int master_bits, first_ch_bits; + int err, csize; + struct uac_feature_unit_descriptor *ftr = _ftr; + + if (ftr->bLength < 7 || ! (csize = ftr->bControlSize) || ftr->bLength < 7 + csize) { + snd_printk(KERN_ERR "usbaudio: unit %u: invalid UAC_FEATURE_UNIT descriptor\n", unitid); + return -EINVAL; + } + + /* parse the source unit */ + if ((err = parse_audio_unit(state, ftr->bSourceID)) < 0) + return err; + + /* determine the input source type and name */ + if (check_input_term(state, ftr->bSourceID, &iterm) < 0) + return -EINVAL; + + channels = (ftr->bLength - 7) / csize - 1; + + master_bits = snd_usb_combine_bytes(ftr->controls, csize); + /* master configuration quirks */ + switch (state->chip->usb_id) { + case USB_ID(0x08bb, 0x2702): + snd_printk(KERN_INFO + "usbmixer: master volume quirk for PCM2702 chip\n"); + /* disable non-functional volume control */ + master_bits &= ~UAC_FU_VOLUME; + break; + } + if (channels > 0) + first_ch_bits = snd_usb_combine_bytes(ftr->controls + csize, csize); + else + first_ch_bits = 0; + /* check all control types */ + for (i = 0; i < 10; i++) { + unsigned int ch_bits = 0; + for (j = 0; j < channels; j++) { + unsigned int mask = snd_usb_combine_bytes(ftr->controls + csize * (j+1), csize); + if (mask & (1 << i)) + ch_bits |= (1 << j); + } + if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ + build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid); + if (master_bits & (1 << i)) + build_feature_ctl(state, _ftr, 0, i, &iterm, unitid); + } + + return 0; +} + + +/* + * Mixer Unit + */ + +/* + * build a mixer unit control + * + * the callbacks are identical with feature unit. + * input channel number (zero based) is given in control field instead. + */ + +static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, + int in_pin, int in_ch, int unitid, + struct usb_audio_term *iterm) +{ + struct usb_mixer_elem_info *cval; + unsigned int input_pins = desc[4]; + unsigned int num_outs = desc[5 + input_pins]; + unsigned int i, len; + struct snd_kcontrol *kctl; + const struct usbmix_name_map *map; + + map = find_map(state, unitid, 0); + if (check_ignored_ctl(map)) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (! cval) + return; + + cval->mixer = state->mixer; + cval->id = unitid; + cval->control = in_ch + 1; /* based on 1 */ + cval->val_type = USB_MIXER_S16; + for (i = 0; i < num_outs; i++) { + if (check_matrix_bitmap(desc + 9 + input_pins, in_ch, i, num_outs)) { + cval->cmask |= (1 << i); + cval->channels++; + } + } + + /* get min/max values */ + get_min_max(cval, 0); + + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + if (! kctl) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + kfree(cval); + return; + } + kctl->private_free = usb_mixer_elem_free; + + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + if (! len) + len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0); + if (! len) + len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); + append_ctl_name(kctl, " Volume"); + + snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n", + cval->id, kctl->id.name, cval->channels, cval->min, cval->max); + add_control_to_empty(state, kctl); +} + + +/* + * parse a mixer unit + */ +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigned char *desc) +{ + struct usb_audio_term iterm; + int input_pins, num_ins, num_outs; + int pin, ich, err; + + if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) { + snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid); + return -EINVAL; + } + /* no bmControls field (e.g. Maya44) -> ignore */ + if (desc[0] <= 10 + input_pins) { + snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid); + return 0; + } + + num_ins = 0; + ich = 0; + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc[5 + pin]); + if (err < 0) + return err; + err = check_input_term(state, desc[5 + pin], &iterm); + if (err < 0) + return err; + num_ins += iterm.channels; + for (; ich < num_ins; ++ich) { + int och, ich_has_controls = 0; + + for (och = 0; och < num_outs; ++och) { + if (check_matrix_bitmap(desc + 9 + input_pins, + ich, och, num_outs)) { + ich_has_controls = 1; + break; + } + } + if (ich_has_controls) + build_mixer_unit_ctl(state, desc, pin, ich, + unitid, &iterm); + } + } + return 0; +} + + +/* + * Processing Unit / Extension Unit + */ + +/* get callback for processing/extension unit */ +static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int err, val; + + err = get_cur_ctl_value(cval, cval->control << 8, &val); + if (err < 0 && cval->mixer->ignore_ctl_error) { + ucontrol->value.integer.value[0] = cval->min; + return 0; + } + if (err < 0) + return err; + val = get_relative_value(cval, val); + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* put callback for processing/extension unit */ +static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, oval, err; + + err = get_cur_ctl_value(cval, cval->control << 8, &oval); + if (err < 0) { + if (cval->mixer->ignore_ctl_error) + return 0; + return err; + } + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, cval->control << 8, val); + return 1; + } + return 0; +} + +/* alsa control interface for processing/extension unit */ +static struct snd_kcontrol_new mixer_procunit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_procunit_get, + .put = mixer_ctl_procunit_put, +}; + + +/* + * predefined data for processing units + */ +struct procunit_value_info { + int control; + char *suffix; + int val_type; + int min_value; +}; + +struct procunit_info { + int type; + char *name; + struct procunit_value_info *values; +}; + +static struct procunit_value_info updown_proc_info[] = { + { USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static struct procunit_value_info prologic_proc_info[] = { + { USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static struct procunit_value_info threed_enh_proc_info[] = { + { USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 }, + { 0 } +}; +static struct procunit_value_info reverb_proc_info[] = { + { USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, + { USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 }, + { USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 }, + { 0 } +}; +static struct procunit_value_info chorus_proc_info[] = { + { USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, + { USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, + { USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, + { 0 } +}; +static struct procunit_value_info dcr_proc_info[] = { + { USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN }, + { USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 }, + { USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 }, + { USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, + { USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 }, + { USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 }, + { 0 } +}; + +static struct procunit_info procunits[] = { + { USB_PROC_UPDOWN, "Up Down", updown_proc_info }, + { USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info }, + { USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info }, + { USB_PROC_REVERB, "Reverb", reverb_proc_info }, + { USB_PROC_CHORUS, "Chorus", chorus_proc_info }, + { USB_PROC_DCR, "DCR", dcr_proc_info }, + { 0 }, +}; +/* + * predefined data for extension units + */ +static struct procunit_value_info clock_rate_xu_info[] = { + { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, + { 0 } +}; +static struct procunit_value_info clock_source_xu_info[] = { + { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN }, + { 0 } +}; +static struct procunit_value_info spdif_format_xu_info[] = { + { USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN }, + { 0 } +}; +static struct procunit_value_info soft_limit_xu_info[] = { + { USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN }, + { 0 } +}; +static struct procunit_info extunits[] = { + { USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info }, + { USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info }, + { USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info }, + { USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info }, + { 0 } +}; +/* + * build a processing/extension unit + */ +static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name) +{ + int num_ins = dsc[6]; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + int i, err, nameid, type, len; + struct procunit_info *info; + struct procunit_value_info *valinfo; + const struct usbmix_name_map *map; + static struct procunit_value_info default_value_info[] = { + { 0x01, "Switch", USB_MIXER_BOOLEAN }, + { 0 } + }; + static struct procunit_info default_info = { + 0, NULL, default_value_info + }; + + if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) { + snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid); + return -EINVAL; + } + + for (i = 0; i < num_ins; i++) { + if ((err = parse_audio_unit(state, dsc[7 + i])) < 0) + return err; + } + + type = combine_word(&dsc[4]); + for (info = list; info && info->type; info++) + if (info->type == type) + break; + if (! info || ! info->type) + info = &default_info; + + for (valinfo = info->values; valinfo->control; valinfo++) { + /* FIXME: bitmap might be longer than 8bit */ + if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1)))) + continue; + map = find_map(state, unitid, valinfo->control); + if (check_ignored_ctl(map)) + continue; + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (! cval) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + return -ENOMEM; + } + cval->mixer = state->mixer; + cval->id = unitid; + cval->control = valinfo->control; + cval->val_type = valinfo->val_type; + cval->channels = 1; + + /* get min/max values */ + if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { + /* FIXME: hard-coded */ + cval->min = 1; + cval->max = dsc[15]; + cval->res = 1; + cval->initialized = 1; + } else { + if (type == USB_XU_CLOCK_RATE) { + /* E-Mu USB 0404/0202/TrackerPre + * samplerate control quirk + */ + cval->min = 0; + cval->max = 5; + cval->res = 1; + cval->initialized = 1; + } else + get_min_max(cval, valinfo->min_value); + } + + kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); + if (! kctl) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + kfree(cval); + return -ENOMEM; + } + kctl->private_free = usb_mixer_elem_free; + + if (check_mapped_name(map, kctl->id.name, + sizeof(kctl->id.name))) + /* nothing */ ; + else if (info->name) + strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); + else { + nameid = dsc[12 + num_ins + dsc[11 + num_ins]]; + len = 0; + if (nameid) + len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); + if (! len) + strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + } + append_ctl_name(kctl, " "); + append_ctl_name(kctl, valinfo->suffix); + + snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n", + cval->id, kctl->id.name, cval->channels, cval->min, cval->max); + if ((err = add_control_to_empty(state, kctl)) < 0) + return err; + } + return 0; +} + + +static int parse_audio_processing_unit(struct mixer_build *state, int unitid, unsigned char *desc) +{ + return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit"); +} + +static int parse_audio_extension_unit(struct mixer_build *state, int unitid, unsigned char *desc) +{ + return build_audio_procunit(state, unitid, desc, extunits, "Extension Unit"); +} + + +/* + * Selector Unit + */ + +/* info callback for selector unit + * use an enumerator type for routing + */ +static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + char **itemlist = (char **)kcontrol->private_value; + + if (snd_BUG_ON(!itemlist)) + return -EINVAL; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = cval->max; + if ((int)uinfo->value.enumerated.item >= cval->max) + uinfo->value.enumerated.item = cval->max - 1; + strcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item]); + return 0; +} + +/* get callback for selector unit */ +static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, err; + + err = get_cur_ctl_value(cval, 0, &val); + if (err < 0) { + if (cval->mixer->ignore_ctl_error) { + ucontrol->value.enumerated.item[0] = 0; + return 0; + } + return err; + } + val = get_relative_value(cval, val); + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +/* put callback for selector unit */ +static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, oval, err; + + err = get_cur_ctl_value(cval, 0, &oval); + if (err < 0) { + if (cval->mixer->ignore_ctl_error) + return 0; + return err; + } + val = ucontrol->value.enumerated.item[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, 0, val); + return 1; + } + return 0; +} + +/* alsa control interface for selector unit */ +static struct snd_kcontrol_new mixer_selectunit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = mixer_ctl_selector_info, + .get = mixer_ctl_selector_get, + .put = mixer_ctl_selector_put, +}; + + +/* private free callback. + * free both private_data and private_value + */ +static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) +{ + int i, num_ins = 0; + + if (kctl->private_data) { + struct usb_mixer_elem_info *cval = kctl->private_data; + num_ins = cval->max; + kfree(cval); + kctl->private_data = NULL; + } + if (kctl->private_value) { + char **itemlist = (char **)kctl->private_value; + for (i = 0; i < num_ins; i++) + kfree(itemlist[i]); + kfree(itemlist); + kctl->private_value = 0; + } +} + +/* + * parse a selector unit + */ +static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsigned char *desc) +{ + unsigned int num_ins = desc[4]; + unsigned int i, nameid, len; + int err; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + const struct usbmix_name_map *map; + char **namelist; + + if (! num_ins || desc[0] < 5 + num_ins) { + snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid); + return -EINVAL; + } + + for (i = 0; i < num_ins; i++) { + if ((err = parse_audio_unit(state, desc[5 + i])) < 0) + return err; + } + + if (num_ins == 1) /* only one ? nonsense! */ + return 0; + + map = find_map(state, unitid, 0); + if (check_ignored_ctl(map)) + return 0; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (! cval) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + return -ENOMEM; + } + cval->mixer = state->mixer; + cval->id = unitid; + cval->val_type = USB_MIXER_U8; + cval->channels = 1; + cval->min = 1; + cval->max = num_ins; + cval->res = 1; + cval->initialized = 1; + + namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL); + if (! namelist) { + snd_printk(KERN_ERR "cannot malloc\n"); + kfree(cval); + return -ENOMEM; + } +#define MAX_ITEM_NAME_LEN 64 + for (i = 0; i < num_ins; i++) { + struct usb_audio_term iterm; + len = 0; + namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); + if (! namelist[i]) { + snd_printk(KERN_ERR "cannot malloc\n"); + while (i--) + kfree(namelist[i]); + kfree(namelist); + kfree(cval); + return -ENOMEM; + } + len = check_mapped_selector_name(state, unitid, i, namelist[i], + MAX_ITEM_NAME_LEN); + if (! len && check_input_term(state, desc[5 + i], &iterm) >= 0) + len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); + if (! len) + sprintf(namelist[i], "Input %d", i); + } + + kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); + if (! kctl) { + snd_printk(KERN_ERR "cannot malloc kcontrol\n"); + kfree(namelist); + kfree(cval); + return -ENOMEM; + } + kctl->private_value = (unsigned long)namelist; + kctl->private_free = usb_mixer_selector_elem_free; + + nameid = desc[desc[0] - 1]; + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + if (len) + ; + else if (nameid) + snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); + else { + len = get_term_name(state, &state->oterm, + kctl->id.name, sizeof(kctl->id.name), 0); + if (! len) + strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); + + if ((state->oterm.type & 0xff00) == 0x0100) + append_ctl_name(kctl, " Capture Source"); + else + append_ctl_name(kctl, " Playback Source"); + } + + snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n", + cval->id, kctl->id.name, num_ins); + if ((err = add_control_to_empty(state, kctl)) < 0) + return err; + + return 0; +} + + +/* + * parse an audio unit recursively + */ + +static int parse_audio_unit(struct mixer_build *state, int unitid) +{ + unsigned char *p1; + + if (test_and_set_bit(unitid, state->unitbitmap)) + return 0; /* the unit already visited */ + + p1 = find_audio_control_unit(state, unitid); + if (!p1) { + snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid); + return -EINVAL; + } + + switch (p1[2]) { + case UAC_INPUT_TERMINAL: + return 0; /* NOP */ + case UAC_MIXER_UNIT: + return parse_audio_mixer_unit(state, unitid, p1); + case UAC_SELECTOR_UNIT: + return parse_audio_selector_unit(state, unitid, p1); + case UAC_FEATURE_UNIT: + return parse_audio_feature_unit(state, unitid, p1); + case UAC_PROCESSING_UNIT_V1: + return parse_audio_processing_unit(state, unitid, p1); + case UAC_EXTENSION_UNIT_V1: + return parse_audio_extension_unit(state, unitid, p1); + default: + snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]); + return -EINVAL; + } +} + +static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) +{ + kfree(mixer->id_elems); + if (mixer->urb) { + kfree(mixer->urb->transfer_buffer); + usb_free_urb(mixer->urb); + } + usb_free_urb(mixer->rc_urb); + kfree(mixer->rc_setup_packet); + kfree(mixer); +} + +static int snd_usb_mixer_dev_free(struct snd_device *device) +{ + struct usb_mixer_interface *mixer = device->device_data; + snd_usb_mixer_free(mixer); + return 0; +} + +/* + * create mixer controls + * + * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers + */ +static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) +{ + struct uac_output_terminal_descriptor_v1 *desc; + struct mixer_build state; + int err; + const struct usbmix_ctl_map *map; + struct usb_host_interface *hostif; + + hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; + memset(&state, 0, sizeof(state)); + state.chip = mixer->chip; + state.mixer = mixer; + state.buffer = hostif->extra; + state.buflen = hostif->extralen; + + /* check the mapping table */ + for (map = usbmix_ctl_maps; map->id; map++) { + if (map->id == state.chip->usb_id) { + state.map = map->map; + state.selector_map = map->selector_map; + mixer->ignore_ctl_error = map->ignore_ctl_error; + break; + } + } + + desc = NULL; + while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, UAC_OUTPUT_TERMINAL)) != NULL) { + if (desc->bLength < 9) + continue; /* invalid descriptor? */ + set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0) + return err; + } + return 0; +} + +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) +{ + struct usb_mixer_elem_info *info; + + for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + info->elem_id); +} + +static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, + int unitid, + struct usb_mixer_elem_info *cval) +{ + static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN", + "S8", "U8", "S16", "U16"}; + snd_iprintf(buffer, " Unit: %i\n", unitid); + if (cval->elem_id) + snd_iprintf(buffer, " Control: name=\"%s\", index=%i\n", + cval->elem_id->name, cval->elem_id->index); + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + "channels=%i, type=\"%s\"\n", cval->id, + cval->control, cval->cmask, cval->channels, + val_types[cval->val_type]); + snd_iprintf(buffer, " Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n", + cval->min, cval->max, cval->dBmin, cval->dBmax); +} + +static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *cval; + int unitid; + + list_for_each_entry(mixer, &chip->mixer_list, list) { + snd_iprintf(buffer, + "USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n", + chip->usb_id, mixer->ctrlif, + mixer->ignore_ctl_error); + snd_iprintf(buffer, "Card: %s\n", chip->card->longname); + for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) { + for (cval = mixer->id_elems[unitid]; cval; + cval = cval->next_id_elem) + snd_usb_mixer_dump_cval(buffer, unitid, cval); + } + } +} + +static void snd_usb_mixer_status_complete(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + + if (urb->status == 0) { + u8 *buf = urb->transfer_buffer; + int i; + + for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) { + snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n", + buf[0], buf[1]); + /* ignore any notifications not from the control interface */ + if ((buf[0] & 0x0f) != 0) + continue; + if (!(buf[0] & 0x40)) + snd_usb_mixer_notify_id(mixer, buf[1]); + else + snd_usb_mixer_rc_memory_change(mixer, buf[1]); + } + } + if (urb->status != -ENOENT && urb->status != -ECONNRESET) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } +} + +/* create the handler for the optional status interrupt endpoint */ +static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) +{ + struct usb_host_interface *hostif; + struct usb_endpoint_descriptor *ep; + void *transfer_buffer; + int buffer_length; + unsigned int epnum; + + hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; + /* we need one interrupt input endpoint */ + if (get_iface_desc(hostif)->bNumEndpoints < 1) + return 0; + ep = get_endpoint(hostif, 0); + if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep)) + return 0; + + epnum = usb_endpoint_num(ep); + buffer_length = le16_to_cpu(ep->wMaxPacketSize); + transfer_buffer = kmalloc(buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) { + kfree(transfer_buffer); + return -ENOMEM; + } + usb_fill_int_urb(mixer->urb, mixer->chip->dev, + usb_rcvintpipe(mixer->chip->dev, epnum), + transfer_buffer, buffer_length, + snd_usb_mixer_status_complete, mixer, ep->bInterval); + usb_submit_urb(mixer->urb, GFP_KERNEL); + return 0; +} + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error) +{ + static struct snd_device_ops dev_ops = { + .dev_free = snd_usb_mixer_dev_free + }; + struct usb_mixer_interface *mixer; + struct snd_info_entry *entry; + struct usb_host_interface *host_iface; + int err, protocol; + + strcpy(chip->card->mixername, "USB Mixer"); + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + mixer->chip = chip; + mixer->ctrlif = ctrlif; + mixer->ignore_ctl_error = ignore_error; + mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems), + GFP_KERNEL); + if (!mixer->id_elems) { + kfree(mixer); + return -ENOMEM; + } + + host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; + protocol = host_iface->desc.bInterfaceProtocol; + + /* FIXME! */ + if (protocol != UAC_VERSION_1) { + snd_printk(KERN_WARNING "mixer interface protocol 0x%02x not yet supported\n", + protocol); + return 0; + } + + if ((err = snd_usb_mixer_controls(mixer)) < 0 || + (err = snd_usb_mixer_status_create(mixer)) < 0) + goto _error; + + snd_usb_mixer_apply_create_quirk(mixer); + + err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops); + if (err < 0) + goto _error; + + if (list_empty(&chip->mixer_list) && + !snd_card_proc_new(chip->card, "usbmixer", &entry)) + snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read); + + list_add(&mixer->list, &chip->mixer_list); + return 0; + +_error: + snd_usb_mixer_free(mixer); + return err; +} + +void snd_usb_mixer_disconnect(struct list_head *p) +{ + struct usb_mixer_interface *mixer; + + mixer = list_entry(p, struct usb_mixer_interface, list); + usb_kill_urb(mixer->urb); + usb_kill_urb(mixer->rc_urb); +} diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h new file mode 100644 index 00000000000..63101ae201c --- /dev/null +++ b/sound/usb/mixer.h @@ -0,0 +1,52 @@ +#ifndef __USBMIXER_H +#define __USBMIXER_H + +struct usb_mixer_interface { + struct snd_usb_audio *chip; + unsigned int ctrlif; + struct list_head list; + unsigned int ignore_ctl_error; + struct urb *urb; + /* array[MAX_ID_ELEMS], indexed by unit id */ + struct usb_mixer_elem_info **id_elems; + + /* Sound Blaster remote control stuff */ + const struct rc_config *rc_cfg; + u32 rc_code; + wait_queue_head_t rc_waitq; + struct urb *rc_urb; + struct usb_ctrlrequest *rc_setup_packet; + u8 rc_buffer[6]; + + u8 audigy2nx_leds[3]; + u8 xonar_u1_status; +}; + +#define MAX_CHANNELS 10 /* max logical channels */ + +struct usb_mixer_elem_info { + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ + struct snd_ctl_elem_id *elem_id; + unsigned int id; + unsigned int control; /* CS or ICN (high byte) */ + unsigned int cmask; /* channel mask bitmap: 0 = master */ + int channels; + int val_type; + int min, max, res; + int dBmin, dBmax; + int cached; + int cache_val[MAX_CHANNELS]; + u8 initialized; +}; + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error); +void snd_usb_mixer_disconnect(struct list_head *p); + +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); + +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set); + +#endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c new file mode 100644 index 00000000000..d93fc89beba --- /dev/null +++ b/sound/usb/mixer_maps.c @@ -0,0 +1,376 @@ +/* + * Additional mixer mapping + * + * Copyright (c) 2002 by Takashi Iwai + * + * 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. + * + * 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 + * + */ + +struct usbmix_dB_map { + u32 min; + u32 max; +}; + +struct usbmix_name_map { + int id; + const char *name; + int control; + struct usbmix_dB_map *dB; +}; + +struct usbmix_selector_map { + int id; + int count; + const char **names; +}; + +struct usbmix_ctl_map { + u32 id; + const struct usbmix_name_map *map; + const struct usbmix_selector_map *selector_map; + int ignore_ctl_error; +}; + +/* + * USB control mappers for SB Exitigy + */ + +/* + * Topology of SB Extigy (see on the wide screen :) + +USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24] + ^ | | | | +USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26] + ^ ^ | | | | +Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19] + | | | | +Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20] + | | | +Mic-IN[9] --+->FU[10]----------------------------+ | + || | + || +----------------------------------------------------+ + VV V + ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13] +*/ + +static struct usbmix_name_map extigy_map[] = { + /* 1: IT pcm */ + { 2, "PCM Playback" }, /* FU */ + /* 3: IT pcm */ + /* 4: IT digital in */ + { 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */ + { 6, "Digital In" }, /* FU */ + /* 7: IT line */ + { 8, "Line Playback" }, /* FU */ + /* 9: IT mic */ + { 10, "Mic Playback" }, /* FU */ + { 11, "Capture Source" }, /* SU */ + { 12, "Capture" }, /* FU */ + /* 13: OT pcm capture */ + /* 14: MU (w/o controls) */ + /* 15: PU (3D enh) */ + /* 16: MU (w/o controls) */ + { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ + { 17, "Channel Routing", 2 }, /* PU: mode select */ + { 18, "Tone Control - Bass", UAC_BASS_CONTROL }, /* FU */ + { 18, "Tone Control - Treble", UAC_TREBLE_CONTROL }, /* FU */ + { 18, "Master Playback" }, /* FU; others */ + /* 19: OT speaker */ + /* 20: OT headphone */ + { 21, NULL }, /* DISABLED: EU (for what?) */ + { 22, "Digital Out Playback" }, /* FU */ + { 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */ + /* 24: OT digital out */ + { 25, "IEC958 Optical Playback" }, /* FU */ + { 26, "IEC958 Optical Playback" }, /* OT */ + { 27, NULL }, /* DISABLED: EU (for what?) */ + /* 28: FU speaker (mute) */ + { 29, NULL }, /* Digital Input Playback Source? */ + { 0 } /* terminator */ +}; + +/* Sound Blaster MP3+ controls mapping + * The default mixer channels have totally misleading names, + * e.g. no Master and fake PCM volume + * Pavel Mihaylov + */ +static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0}; /* just guess */ +static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */ + +static struct usbmix_name_map mp3plus_map[] = { + /* 1: IT pcm */ + /* 2: IT mic */ + /* 3: IT line */ + /* 4: IT digital in */ + /* 5: OT digital out */ + /* 6: OT speaker */ + /* 7: OT pcm capture */ + { 8, "Capture Source" }, /* FU, default PCM Capture Source */ + /* (Mic, Input 1 = Line input, Input 2 = Optical input) */ + { 9, "Master Playback" }, /* FU, default Speaker 1 */ + /* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */ + { 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 }, + /* FU, Mic Capture */ + { 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */ + { 11, "Line Capture", .dB = &mp3plus_dB_2 }, + /* FU, default PCM Capture */ + { 12, "Digital In Playback" }, /* FU, default PCM 1 */ + { 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 }, + /* FU, default Mic Playback */ + { 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */ + /* 15: MU */ + { 0 } /* terminator */ +}; + +/* Topology of SB Audigy 2 NX + + +----------------------------->EU[27]--+ + | v + | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24] + | | ^ +USB_IN[1]-+------------+ +->EU[17]->+->FU[11]-+ + | v | v | +Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20] + | ^ | | +Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19] + | | v + +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15] + | ^ + +->FU[13]--------------------------------------+ +*/ +static struct usbmix_name_map audigy2nx_map[] = { + /* 1: IT pcm playback */ + /* 4: IT digital in */ + { 6, "Digital In Playback" }, /* FU */ + /* 7: IT line in */ + { 8, "Line Playback" }, /* FU */ + { 11, "What-U-Hear Capture" }, /* FU */ + { 12, "Line Capture" }, /* FU */ + { 13, "Digital In Capture" }, /* FU */ + { 14, "Capture Source" }, /* SU */ + /* 15: OT pcm capture */ + /* 16: MU w/o controls */ + { 17, NULL }, /* DISABLED: EU (for what?) */ + { 18, "Master Playback" }, /* FU */ + /* 19: OT speaker */ + /* 20: OT headphone */ + { 21, NULL }, /* DISABLED: EU (for what?) */ + { 22, "Digital Out Playback" }, /* FU */ + { 23, NULL }, /* DISABLED: EU (for what?) */ + /* 24: OT digital out */ + { 27, NULL }, /* DISABLED: EU (for what?) */ + { 28, "Speaker Playback" }, /* FU */ + { 29, "Digital Out Source" }, /* SU */ + { 30, "Headphone Playback" }, /* FU */ + { 31, "Headphone Source" }, /* SU */ + { 0 } /* terminator */ +}; + +static struct usbmix_selector_map audigy2nx_selectors[] = { + { + .id = 14, /* Capture Source */ + .count = 3, + .names = (const char*[]) {"Line", "Digital In", "What-U-Hear"} + }, + { + .id = 29, /* Digital Out Source */ + .count = 3, + .names = (const char*[]) {"Front", "PCM", "Digital In"} + }, + { + .id = 31, /* Headphone Source */ + .count = 2, + .names = (const char*[]) {"Front", "Side"} + }, + { 0 } /* terminator */ +}; + +/* Creative SoundBlaster Live! 24-bit External */ +static struct usbmix_name_map live24ext_map[] = { + /* 2: PCM Playback Volume */ + { 5, "Mic Capture" }, /* FU, default PCM Capture Volume */ + { 0 } /* terminator */ +}; + +/* LineX FM Transmitter entry - needed to bypass controls bug */ +static struct usbmix_name_map linex_map[] = { + /* 1: IT pcm */ + /* 2: OT Speaker */ + { 3, "Master" }, /* FU: master volume - left / right / mute */ + { 0 } /* terminator */ +}; + +static struct usbmix_name_map maya44_map[] = { + /* 1: IT line */ + { 2, "Line Playback" }, /* FU */ + /* 3: IT line */ + { 4, "Line Playback" }, /* FU */ + /* 5: IT pcm playback */ + /* 6: MU */ + { 7, "Master Playback" }, /* FU */ + /* 8: OT speaker */ + /* 9: IT line */ + { 10, "Line Capture" }, /* FU */ + /* 11: MU */ + /* 12: OT pcm capture */ + { } +}; + +/* Section "justlink_map" below added by James Courtier-Dutton + * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK + * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.) + * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device, + * so this map removes all unwanted sliders from alsamixer + */ + +static struct usbmix_name_map justlink_map[] = { + /* 1: IT pcm playback */ + /* 2: Not present */ + { 3, NULL}, /* IT mic (No mic input on device) */ + /* 4: Not present */ + /* 5: OT speacker */ + /* 6: OT pcm capture */ + { 7, "Master Playback" }, /* Mute/volume for speaker */ + { 8, NULL }, /* Capture Switch (No capture inputs on device) */ + { 9, NULL }, /* Capture Mute/volume (No capture inputs on device */ + /* 0xa: Not present */ + /* 0xb: MU (w/o controls) */ + { 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */ + { 0 } /* terminator */ +}; + +/* TerraTec Aureon 5.1 MkII USB */ +static struct usbmix_name_map aureon_51_2_map[] = { + /* 1: IT USB */ + /* 2: IT Mic */ + /* 3: IT Line */ + /* 4: IT SPDIF */ + /* 5: OT SPDIF */ + /* 6: OT Speaker */ + /* 7: OT USB */ + { 8, "Capture Source" }, /* SU */ + { 9, "Master Playback" }, /* FU */ + { 10, "Mic Capture" }, /* FU */ + { 11, "Line Capture" }, /* FU */ + { 12, "IEC958 In Capture" }, /* FU */ + { 13, "Mic Playback" }, /* FU */ + { 14, "Line Playback" }, /* FU */ + /* 15: MU */ + {} /* terminator */ +}; + +static struct usbmix_name_map scratch_live_map[] = { + /* 1: IT Line 1 (USB streaming) */ + /* 2: OT Line 1 (Speaker) */ + /* 3: IT Line 1 (Line connector) */ + { 4, "Line 1 In" }, /* FU */ + /* 5: OT Line 1 (USB streaming) */ + /* 6: IT Line 2 (USB streaming) */ + /* 7: OT Line 2 (Speaker) */ + /* 8: IT Line 2 (Line connector) */ + { 9, "Line 2 In" }, /* FU */ + /* 10: OT Line 2 (USB streaming) */ + /* 11: IT Mic (Line connector) */ + /* 12: OT Mic (USB streaming) */ + { 0 } /* terminator */ +}; + +/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+" + * most importand difference is SU[8], it should be set to "Capture Source" + * to make alsamixer and PA working properly. + * FIXME: or mp3plus_map should use "Capture Source" too, + * so this maps can be merget + */ +static struct usbmix_name_map hercules_usb51_map[] = { + { 8, "Capture Source" }, /* SU, default "PCM Capture Source" */ + { 9, "Master Playback" }, /* FU, default "Speaker Playback" */ + { 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */ + { 11, "Line Capture" }, /* FU, default "PCM Capture" */ + { 13, "Mic Bypass Playback" }, /* FU, default "Mic Playback" */ + { 14, "Line Bypass Playback" }, /* FU, default "Line Playback" */ + { 0 } /* terminator */ +}; + +/* + * Control map entries + */ + +static struct usbmix_ctl_map usbmix_ctl_maps[] = { + { + .id = USB_ID(0x041e, 0x3000), + .map = extigy_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x041e, 0x3010), + .map = mp3plus_map, + }, + { + .id = USB_ID(0x041e, 0x3020), + .map = audigy2nx_map, + .selector_map = audigy2nx_selectors, + }, + { + .id = USB_ID(0x041e, 0x3040), + .map = live24ext_map, + }, + { + .id = USB_ID(0x041e, 0x3048), + .map = audigy2nx_map, + .selector_map = audigy2nx_selectors, + }, + { + /* Hercules DJ Console (Windows Edition) */ + .id = USB_ID(0x06f8, 0xb000), + .ignore_ctl_error = 1, + }, + { + /* Hercules DJ Console (Macintosh Edition) */ + .id = USB_ID(0x06f8, 0xd002), + .ignore_ctl_error = 1, + }, + { + /* Hercules Gamesurround Muse Pocket LT + * (USB 5.1 Channel Audio Adapter) + */ + .id = USB_ID(0x06f8, 0xc000), + .map = hercules_usb51_map, + }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x0a92, 0x0091), + .map = maya44_map, + }, + { + .id = USB_ID(0x0c45, 0x1158), + .map = justlink_map, + }, + { + .id = USB_ID(0x0ccd, 0x0028), + .map = aureon_51_2_map, + }, + { + .id = USB_ID(0x13e5, 0x0001), + .map = scratch_live_map, + .ignore_ctl_error = 1, + }, + { 0 } /* terminator */ +}; + diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index d2f4dcdf59e..56b665938b1 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -35,7 +35,7 @@ #include #include "usbaudio.h" -#include "usbmixer.h" +#include "mixer.h" #include "mixer_quirks.h" #include "helper.h" diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index a82cfeda21f..d4ced64e465 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -24,7 +24,7 @@ #include "usbaudio.h" #include "card.h" -#include "usbmixer.h" +#include "mixer.h" #include "mixer_quirks.h" #include "midi.h" #include "quirks.h" diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c deleted file mode 100644 index ec2436e9532..00000000000 --- a/sound/usb/usbmixer.c +++ /dev/null @@ -1,1920 +0,0 @@ -/* - * (Tentative) USB Audio Driver for ALSA - * - * Mixer control part - * - * Copyright (c) 2002 by Takashi Iwai - * - * Many codes borrowed from audio.c by - * Alan Cox (alan@lxorguk.ukuu.org.uk) - * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * 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. - * - * 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 - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "usbaudio.h" -#include "usbmixer.h" -#include "helper.h" -#include "mixer_quirks.h" - -#define MAX_ID_ELEMS 256 - -struct usb_audio_term { - int id; - int type; - int channels; - unsigned int chconfig; - int name; -}; - -struct usbmix_name_map; - -struct mixer_build { - struct snd_usb_audio *chip; - struct usb_mixer_interface *mixer; - unsigned char *buffer; - unsigned int buflen; - DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS); - struct usb_audio_term oterm; - const struct usbmix_name_map *map; - const struct usbmix_selector_map *selector_map; -}; - -enum { - USB_MIXER_BOOLEAN, - USB_MIXER_INV_BOOLEAN, - USB_MIXER_S8, - USB_MIXER_U8, - USB_MIXER_S16, - USB_MIXER_U16, -}; - -enum { - USB_PROC_UPDOWN = 1, - USB_PROC_UPDOWN_SWITCH = 1, - USB_PROC_UPDOWN_MODE_SEL = 2, - - USB_PROC_PROLOGIC = 2, - USB_PROC_PROLOGIC_SWITCH = 1, - USB_PROC_PROLOGIC_MODE_SEL = 2, - - USB_PROC_3DENH = 3, - USB_PROC_3DENH_SWITCH = 1, - USB_PROC_3DENH_SPACE = 2, - - USB_PROC_REVERB = 4, - USB_PROC_REVERB_SWITCH = 1, - USB_PROC_REVERB_LEVEL = 2, - USB_PROC_REVERB_TIME = 3, - USB_PROC_REVERB_DELAY = 4, - - USB_PROC_CHORUS = 5, - USB_PROC_CHORUS_SWITCH = 1, - USB_PROC_CHORUS_LEVEL = 2, - USB_PROC_CHORUS_RATE = 3, - USB_PROC_CHORUS_DEPTH = 4, - - USB_PROC_DCR = 6, - USB_PROC_DCR_SWITCH = 1, - USB_PROC_DCR_RATIO = 2, - USB_PROC_DCR_MAX_AMP = 3, - USB_PROC_DCR_THRESHOLD = 4, - USB_PROC_DCR_ATTACK = 5, - USB_PROC_DCR_RELEASE = 6, -}; - -/*E-mu 0202(0404) eXtension Unit(XU) control*/ -enum { - USB_XU_CLOCK_RATE = 0xe301, - USB_XU_CLOCK_SOURCE = 0xe302, - USB_XU_DIGITAL_IO_STATUS = 0xe303, - USB_XU_DEVICE_OPTIONS = 0xe304, - USB_XU_DIRECT_MONITORING = 0xe305, - USB_XU_METERING = 0xe306 -}; -enum { - USB_XU_CLOCK_SOURCE_SELECTOR = 0x02, /* clock source*/ - USB_XU_CLOCK_RATE_SELECTOR = 0x03, /* clock rate */ - USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01, /* the spdif format */ - USB_XU_SOFT_LIMIT_SELECTOR = 0x03 /* soft limiter */ -}; - -/* - * manual mapping of mixer names - * if the mixer topology is too complicated and the parsed names are - * ambiguous, add the entries in usbmixer_maps.c. - */ -#include "usbmixer_maps.c" - -static const struct usbmix_name_map * -find_map(struct mixer_build *state, int unitid, int control) -{ - const struct usbmix_name_map *p = state->map; - - if (!p) - return NULL; - - for (p = state->map; p->id; p++) { - if (p->id == unitid && - (!control || !p->control || control == p->control)) - return p; - } - return NULL; -} - -/* get the mapped name if the unit matches */ -static int -check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen) -{ - if (!p || !p->name) - return 0; - - buflen--; - return strlcpy(buf, p->name, buflen); -} - -/* check whether the control should be ignored */ -static inline int -check_ignored_ctl(const struct usbmix_name_map *p) -{ - if (!p || p->name || p->dB) - return 0; - return 1; -} - -/* dB mapping */ -static inline void check_mapped_dB(const struct usbmix_name_map *p, - struct usb_mixer_elem_info *cval) -{ - if (p && p->dB) { - cval->dBmin = p->dB->min; - cval->dBmax = p->dB->max; - } -} - -/* get the mapped selector source name */ -static int check_mapped_selector_name(struct mixer_build *state, int unitid, - int index, char *buf, int buflen) -{ - const struct usbmix_selector_map *p; - - if (! state->selector_map) - return 0; - for (p = state->selector_map; p->id; p++) { - if (p->id == unitid && index < p->count) - return strlcpy(buf, p->names[index], buflen); - } - return 0; -} - -/* - * find an audio control unit with the given unit id - */ -static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit) -{ - unsigned char *p; - - p = NULL; - while ((p = snd_usb_find_desc(state->buffer, state->buflen, p, - USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC_EXTENSION_UNIT_V1 && p[3] == unit) - return p; - } - return NULL; -} - - -/* - * copy a string with the given id - */ -static int snd_usb_copy_string_desc(struct mixer_build *state, int index, char *buf, int maxlen) -{ - int len = usb_string(state->chip->dev, index, buf, maxlen - 1); - buf[len] = 0; - return len; -} - -/* - * convert from the byte/word on usb descriptor to the zero-based integer - */ -static int convert_signed_value(struct usb_mixer_elem_info *cval, int val) -{ - switch (cval->val_type) { - case USB_MIXER_BOOLEAN: - return !!val; - case USB_MIXER_INV_BOOLEAN: - return !val; - case USB_MIXER_U8: - val &= 0xff; - break; - case USB_MIXER_S8: - val &= 0xff; - if (val >= 0x80) - val -= 0x100; - break; - case USB_MIXER_U16: - val &= 0xffff; - break; - case USB_MIXER_S16: - val &= 0xffff; - if (val >= 0x8000) - val -= 0x10000; - break; - } - return val; -} - -/* - * convert from the zero-based int to the byte/word for usb descriptor - */ -static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val) -{ - switch (cval->val_type) { - case USB_MIXER_BOOLEAN: - return !!val; - case USB_MIXER_INV_BOOLEAN: - return !val; - case USB_MIXER_S8: - case USB_MIXER_U8: - return val & 0xff; - case USB_MIXER_S16: - case USB_MIXER_U16: - return val & 0xffff; - } - return 0; /* not reached */ -} - -static int get_relative_value(struct usb_mixer_elem_info *cval, int val) -{ - if (! cval->res) - cval->res = 1; - if (val < cval->min) - return 0; - else if (val >= cval->max) - return (cval->max - cval->min + cval->res - 1) / cval->res; - else - return (val - cval->min) / cval->res; -} - -static int get_abs_value(struct usb_mixer_elem_info *cval, int val) -{ - if (val < 0) - return cval->min; - if (! cval->res) - cval->res = 1; - val *= cval->res; - val += cval->min; - if (val > cval->max) - return cval->max; - return val; -} - - -/* - * retrieve a mixer value - */ - -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) -{ - unsigned char buf[2]; - int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; - int timeout = 10; - - while (timeout-- > 0) { - if (snd_usb_ctl_msg(cval->mixer->chip->dev, - usb_rcvctrlpipe(cval->mixer->chip->dev, 0), - request, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, cval->mixer->ctrlif | (cval->id << 8), - buf, val_len, 100) >= val_len) { - *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); - return 0; - } - } - snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", - request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); - return -EINVAL; -} - -static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value) -{ - return get_ctl_value(cval, UAC_GET_CUR, validx, value); -} - -/* channel = 0: master, 1 = first channel */ -static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, - int channel, int *value) -{ - return get_ctl_value(cval, UAC_GET_CUR, (cval->control << 8) | channel, value); -} - -static int get_cur_mix_value(struct usb_mixer_elem_info *cval, - int channel, int index, int *value) -{ - int err; - - if (cval->cached & (1 << channel)) { - *value = cval->cache_val[index]; - return 0; - } - err = get_cur_mix_raw(cval, channel, value); - if (err < 0) { - if (!cval->mixer->ignore_ctl_error) - snd_printd(KERN_ERR "cannot get current value for " - "control %d ch %d: err = %d\n", - cval->control, channel, err); - return err; - } - cval->cached |= 1 << channel; - cval->cache_val[index] = *value; - return 0; -} - - -/* - * set a mixer value - */ - -int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, - int request, int validx, int value_set) -{ - unsigned char buf[2]; - int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; - int timeout = 10; - - value_set = convert_bytes_value(cval, value_set); - buf[0] = value_set & 0xff; - buf[1] = (value_set >> 8) & 0xff; - while (timeout-- > 0) - if (snd_usb_ctl_msg(cval->mixer->chip->dev, - usb_sndctrlpipe(cval->mixer->chip->dev, 0), - request, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - validx, cval->mixer->ctrlif | (cval->id << 8), - buf, val_len, 100) >= 0) - return 0; - snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", - request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type, buf[0], buf[1]); - return -EINVAL; -} - -static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value) -{ - return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); -} - -static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, - int index, int value) -{ - int err; - err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, - value); - if (err < 0) - return err; - cval->cached |= 1 << channel; - cval->cache_val[index] = value; - return 0; -} - -/* - * TLV callback for mixer volume controls - */ -static int mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, - unsigned int size, unsigned int __user *_tlv) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - DECLARE_TLV_DB_MINMAX(scale, 0, 0); - - if (size < sizeof(scale)) - return -ENOMEM; - scale[2] = cval->dBmin; - scale[3] = cval->dBmax; - if (copy_to_user(_tlv, scale, sizeof(scale))) - return -EFAULT; - return 0; -} - -/* - * parser routines begin here... - */ - -static int parse_audio_unit(struct mixer_build *state, int unitid); - - -/* - * check if the input/output channel routing is enabled on the given bitmap. - * used for mixer unit parser - */ -static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs) -{ - int idx = ich * num_outs + och; - return bmap[idx >> 3] & (0x80 >> (idx & 7)); -} - - -/* - * add an alsa control element - * search and increment the index until an empty slot is found. - * - * if failed, give up and free the control instance. - */ - -static int add_control_to_empty(struct mixer_build *state, struct snd_kcontrol *kctl) -{ - struct usb_mixer_elem_info *cval = kctl->private_data; - int err; - - while (snd_ctl_find_id(state->chip->card, &kctl->id)) - kctl->id.index++; - if ((err = snd_ctl_add(state->chip->card, kctl)) < 0) { - snd_printd(KERN_ERR "cannot add control (err = %d)\n", err); - return err; - } - cval->elem_id = &kctl->id; - cval->next_id_elem = state->mixer->id_elems[cval->id]; - state->mixer->id_elems[cval->id] = cval; - return 0; -} - - -/* - * get a terminal name string - */ - -static struct iterm_name_combo { - int type; - char *name; -} iterm_names[] = { - { 0x0300, "Output" }, - { 0x0301, "Speaker" }, - { 0x0302, "Headphone" }, - { 0x0303, "HMD Audio" }, - { 0x0304, "Desktop Speaker" }, - { 0x0305, "Room Speaker" }, - { 0x0306, "Com Speaker" }, - { 0x0307, "LFE" }, - { 0x0600, "External In" }, - { 0x0601, "Analog In" }, - { 0x0602, "Digital In" }, - { 0x0603, "Line" }, - { 0x0604, "Legacy In" }, - { 0x0605, "IEC958 In" }, - { 0x0606, "1394 DA Stream" }, - { 0x0607, "1394 DV Stream" }, - { 0x0700, "Embedded" }, - { 0x0701, "Noise Source" }, - { 0x0702, "Equalization Noise" }, - { 0x0703, "CD" }, - { 0x0704, "DAT" }, - { 0x0705, "DCC" }, - { 0x0706, "MiniDisk" }, - { 0x0707, "Analog Tape" }, - { 0x0708, "Phonograph" }, - { 0x0709, "VCR Audio" }, - { 0x070a, "Video Disk Audio" }, - { 0x070b, "DVD Audio" }, - { 0x070c, "TV Tuner Audio" }, - { 0x070d, "Satellite Rec Audio" }, - { 0x070e, "Cable Tuner Audio" }, - { 0x070f, "DSS Audio" }, - { 0x0710, "Radio Receiver" }, - { 0x0711, "Radio Transmitter" }, - { 0x0712, "Multi-Track Recorder" }, - { 0x0713, "Synthesizer" }, - { 0 }, -}; - -static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm, - unsigned char *name, int maxlen, int term_only) -{ - struct iterm_name_combo *names; - - if (iterm->name) - return snd_usb_copy_string_desc(state, iterm->name, name, maxlen); - - /* virtual type - not a real terminal */ - if (iterm->type >> 16) { - if (term_only) - return 0; - switch (iterm->type >> 16) { - case UAC_SELECTOR_UNIT: - strcpy(name, "Selector"); return 8; - case UAC_PROCESSING_UNIT_V1: - strcpy(name, "Process Unit"); return 12; - case UAC_EXTENSION_UNIT_V1: - strcpy(name, "Ext Unit"); return 8; - case UAC_MIXER_UNIT: - strcpy(name, "Mixer"); return 5; - default: - return sprintf(name, "Unit %d", iterm->id); - } - } - - switch (iterm->type & 0xff00) { - case 0x0100: - strcpy(name, "PCM"); return 3; - case 0x0200: - strcpy(name, "Mic"); return 3; - case 0x0400: - strcpy(name, "Headset"); return 7; - case 0x0500: - strcpy(name, "Phone"); return 5; - } - - for (names = iterm_names; names->type; names++) - if (names->type == iterm->type) { - strcpy(name, names->name); - return strlen(names->name); - } - return 0; -} - - -/* - * parse the source unit recursively until it reaches to a terminal - * or a branched unit. - */ -static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) -{ - unsigned char *p1; - - memset(term, 0, sizeof(*term)); - while ((p1 = find_audio_control_unit(state, id)) != NULL) { - term->id = id; - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - term->type = combine_word(p1 + 4); - term->channels = p1[7]; - term->chconfig = combine_word(p1 + 8); - term->name = p1[11]; - return 0; - case UAC_FEATURE_UNIT: - id = p1[4]; - break; /* continue to parse */ - case UAC_MIXER_UNIT: - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[5 + p1[4]]; - term->chconfig = combine_word(p1 + 6 + p1[4]); - term->name = p1[p1[0] - 1]; - return 0; - case UAC_SELECTOR_UNIT: - /* call recursively to retrieve the channel info */ - if (check_input_term(state, p1[5], term) < 0) - return -ENODEV; - term->type = p1[2] << 16; /* virtual type */ - term->id = id; - term->name = p1[9 + p1[0] - 1]; - return 0; - case UAC_PROCESSING_UNIT_V1: - case UAC_EXTENSION_UNIT_V1: - if (p1[6] == 1) { - id = p1[7]; - break; /* continue to parse */ - } - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[7 + p1[6]]; - term->chconfig = combine_word(p1 + 8 + p1[6]); - term->name = p1[12 + p1[6] + p1[11 + p1[6]]]; - return 0; - default: - return -ENODEV; - } - } - return -ENODEV; -} - - -/* - * Feature Unit - */ - -/* feature unit control information */ -struct usb_feature_control_info { - const char *name; - unsigned int type; /* control type (mute, volume, etc.) */ -}; - -static struct usb_feature_control_info audio_feature_info[] = { - { "Mute", USB_MIXER_INV_BOOLEAN }, - { "Volume", USB_MIXER_S16 }, - { "Tone Control - Bass", USB_MIXER_S8 }, - { "Tone Control - Mid", USB_MIXER_S8 }, - { "Tone Control - Treble", USB_MIXER_S8 }, - { "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */ - { "Auto Gain Control", USB_MIXER_BOOLEAN }, - { "Delay Control", USB_MIXER_U16 }, - { "Bass Boost", USB_MIXER_BOOLEAN }, - { "Loudness", USB_MIXER_BOOLEAN }, -}; - - -/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - - -/* - * interface to ALSA control for feature/mixer units - */ - -/* - * retrieve the minimum and maximum values for the specified control - */ -static int get_min_max(struct usb_mixer_elem_info *cval, int default_min) -{ - /* for failsafe */ - cval->min = default_min; - cval->max = cval->min + 1; - cval->res = 1; - cval->dBmin = cval->dBmax = 0; - - if (cval->val_type == USB_MIXER_BOOLEAN || - cval->val_type == USB_MIXER_INV_BOOLEAN) { - cval->initialized = 1; - } else { - int minchn = 0; - if (cval->cmask) { - int i; - for (i = 0; i < MAX_CHANNELS; i++) - if (cval->cmask & (1 << i)) { - minchn = i + 1; - break; - } - } - if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - snd_printd(KERN_ERR "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->id, cval->mixer->ctrlif, cval->control, cval->id); - return -EINVAL; - } - if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) { - cval->res = 1; - } else { - int last_valid_res = cval->res; - - while (cval->res > 1) { - if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } - if (cval->res == 0) - cval->res = 1; - - /* Additional checks for the proper resolution - * - * Some devices report smaller resolutions than actually - * reacting. They don't return errors but simply clip - * to the lower aligned value. - */ - if (cval->min + cval->res < cval->max) { - int last_valid_res = cval->res; - int saved, test, check; - get_cur_mix_raw(cval, minchn, &saved); - for (;;) { - test = saved; - if (test < cval->max) - test += cval->res; - else - test -= cval->res; - if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || - get_cur_mix_raw(cval, minchn, &check)) { - cval->res = last_valid_res; - break; - } - if (test == check) - break; - cval->res *= 2; - } - set_cur_mix_value(cval, minchn, 0, saved); - } - - cval->initialized = 1; - } - - /* USB descriptions contain the dB scale in 1/256 dB unit - * while ALSA TLV contains in 1/100 dB unit - */ - cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256; - cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256; - if (cval->dBmin > cval->dBmax) { - /* something is wrong; assume it's either from/to 0dB */ - if (cval->dBmin < 0) - cval->dBmax = 0; - else if (cval->dBmin > 0) - cval->dBmin = 0; - if (cval->dBmin > cval->dBmax) { - /* totally crap, return an error */ - return -EINVAL; - } - } - - return 0; -} - - -/* get a feature/mixer unit info */ -static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - - if (cval->val_type == USB_MIXER_BOOLEAN || - cval->val_type == USB_MIXER_INV_BOOLEAN) - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - else - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = cval->channels; - if (cval->val_type == USB_MIXER_BOOLEAN || - cval->val_type == USB_MIXER_INV_BOOLEAN) { - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - } else { - if (! cval->initialized) - get_min_max(cval, 0); - uinfo->value.integer.min = 0; - uinfo->value.integer.max = - (cval->max - cval->min + cval->res - 1) / cval->res; - } - return 0; -} - -/* get the current value from feature/mixer unit */ -static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int c, cnt, val, err; - - ucontrol->value.integer.value[0] = cval->min; - if (cval->cmask) { - cnt = 0; - for (c = 0; c < MAX_CHANNELS; c++) { - if (!(cval->cmask & (1 << c))) - continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); - if (err < 0) - return cval->mixer->ignore_ctl_error ? 0 : err; - val = get_relative_value(cval, val); - ucontrol->value.integer.value[cnt] = val; - cnt++; - } - return 0; - } else { - /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); - if (err < 0) - return cval->mixer->ignore_ctl_error ? 0 : err; - val = get_relative_value(cval, val); - ucontrol->value.integer.value[0] = val; - } - return 0; -} - -/* put the current value to feature/mixer unit */ -static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int c, cnt, val, oval, err; - int changed = 0; - - if (cval->cmask) { - cnt = 0; - for (c = 0; c < MAX_CHANNELS; c++) { - if (!(cval->cmask & (1 << c))) - continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); - if (err < 0) - return cval->mixer->ignore_ctl_error ? 0 : err; - val = ucontrol->value.integer.value[cnt]; - val = get_abs_value(cval, val); - if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); - changed = 1; - } - cnt++; - } - } else { - /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); - if (err < 0) - return cval->mixer->ignore_ctl_error ? 0 : err; - val = ucontrol->value.integer.value[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); - changed = 1; - } - } - return changed; -} - -static struct snd_kcontrol_new usb_feature_unit_ctl = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "", /* will be filled later manually */ - .info = mixer_ctl_feature_info, - .get = mixer_ctl_feature_get, - .put = mixer_ctl_feature_put, -}; - - -/* - * build a feature control - */ - -static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) -{ - return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); -} - -static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, - unsigned int ctl_mask, int control, - struct usb_audio_term *iterm, int unitid) -{ - unsigned int len = 0; - int mapped_name = 0; - int nameid = desc[desc[0] - 1]; - struct snd_kcontrol *kctl; - struct usb_mixer_elem_info *cval; - const struct usbmix_name_map *map; - - control++; /* change from zero-based to 1-based value */ - - if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) { - /* FIXME: not supported yet */ - return; - } - - map = find_map(state, unitid, control); - if (check_ignored_ctl(map)) - return; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = control; - cval->cmask = ctl_mask; - cval->val_type = audio_feature_info[control-1].type; - if (ctl_mask == 0) - cval->channels = 1; /* master channel */ - else { - int i, c = 0; - for (i = 0; i < 16; i++) - if (ctl_mask & (1 << i)) - c++; - cval->channels = c; - } - - /* get min/max values */ - get_min_max(cval, 0); - - kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return; - } - kctl->private_free = usb_mixer_elem_free; - - len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); - mapped_name = len != 0; - if (! len && nameid) - len = snd_usb_copy_string_desc(state, nameid, - kctl->id.name, sizeof(kctl->id.name)); - - switch (control) { - case UAC_MUTE_CONTROL: - case UAC_VOLUME_CONTROL: - /* determine the control name. the rule is: - * - if a name id is given in descriptor, use it. - * - if the connected input can be determined, then use the name - * of terminal type. - * - if the connected output can be determined, use it. - * - otherwise, anonymous name. - */ - if (! len) { - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = snprintf(kctl->id.name, sizeof(kctl->id.name), - "Feature %d", unitid); - } - /* determine the stream direction: - * if the connected output is USB stream, then it's likely a - * capture stream. otherwise it should be playback (hopefully :) - */ - if (! mapped_name && ! (state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) { - len = append_ctl_name(kctl, " Capture"); - } else { - len = append_ctl_name(kctl, " Playback"); - } - } - append_ctl_name(kctl, control == UAC_MUTE_CONTROL ? - " Switch" : " Volume"); - if (control == UAC_VOLUME_CONTROL) { - kctl->tlv.c = mixer_vol_tlv; - kctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_TLV_READ | - SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; - check_mapped_dB(map, cval); - } - break; - - default: - if (! len) - strlcpy(kctl->id.name, audio_feature_info[control-1].name, - sizeof(kctl->id.name)); - break; - } - - /* volume control quirks */ - switch (state->chip->usb_id) { - case USB_ID(0x0471, 0x0101): - case USB_ID(0x0471, 0x0104): - case USB_ID(0x0471, 0x0105): - case USB_ID(0x0672, 0x1041): - /* quirk for UDA1321/N101. - * note that detection between firmware 2.1.1.7 (N101) - * and later 2.1.1.21 is not very clear from datasheets. - * I hope that the min value is -15360 for newer firmware --jk - */ - if (!strcmp(kctl->id.name, "PCM Playback Volume") && - cval->min == -15616) { - snd_printk(KERN_INFO - "set volume quirk for UDA1321/N101 chip\n"); - cval->max = -256; - } - break; - - case USB_ID(0x046d, 0x09a4): - if (!strcmp(kctl->id.name, "Mic Capture Volume")) { - snd_printk(KERN_INFO - "set volume quirk for QuickCam E3500\n"); - cval->min = 6080; - cval->max = 8768; - cval->res = 192; - } - break; - - } - - snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); - add_control_to_empty(state, kctl); -} - - - -/* - * parse a feature unit - * - * most of controlls are defined here. - */ -static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr) -{ - int channels, i, j; - struct usb_audio_term iterm; - unsigned int master_bits, first_ch_bits; - int err, csize; - struct uac_feature_unit_descriptor *ftr = _ftr; - - if (ftr->bLength < 7 || ! (csize = ftr->bControlSize) || ftr->bLength < 7 + csize) { - snd_printk(KERN_ERR "usbaudio: unit %u: invalid UAC_FEATURE_UNIT descriptor\n", unitid); - return -EINVAL; - } - - /* parse the source unit */ - if ((err = parse_audio_unit(state, ftr->bSourceID)) < 0) - return err; - - /* determine the input source type and name */ - if (check_input_term(state, ftr->bSourceID, &iterm) < 0) - return -EINVAL; - - channels = (ftr->bLength - 7) / csize - 1; - - master_bits = snd_usb_combine_bytes(ftr->controls, csize); - /* master configuration quirks */ - switch (state->chip->usb_id) { - case USB_ID(0x08bb, 0x2702): - snd_printk(KERN_INFO - "usbmixer: master volume quirk for PCM2702 chip\n"); - /* disable non-functional volume control */ - master_bits &= ~UAC_FU_VOLUME; - break; - } - if (channels > 0) - first_ch_bits = snd_usb_combine_bytes(ftr->controls + csize, csize); - else - first_ch_bits = 0; - /* check all control types */ - for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; - for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(ftr->controls + csize * (j+1), csize); - if (mask & (1 << i)) - ch_bits |= (1 << j); - } - if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid); - if (master_bits & (1 << i)) - build_feature_ctl(state, _ftr, 0, i, &iterm, unitid); - } - - return 0; -} - - -/* - * Mixer Unit - */ - -/* - * build a mixer unit control - * - * the callbacks are identical with feature unit. - * input channel number (zero based) is given in control field instead. - */ - -static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, - int in_pin, int in_ch, int unitid, - struct usb_audio_term *iterm) -{ - struct usb_mixer_elem_info *cval; - unsigned int input_pins = desc[4]; - unsigned int num_outs = desc[5 + input_pins]; - unsigned int i, len; - struct snd_kcontrol *kctl; - const struct usbmix_name_map *map; - - map = find_map(state, unitid, 0); - if (check_ignored_ctl(map)) - return; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) - return; - - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = in_ch + 1; /* based on 1 */ - cval->val_type = USB_MIXER_S16; - for (i = 0; i < num_outs; i++) { - if (check_matrix_bitmap(desc + 9 + input_pins, in_ch, i, num_outs)) { - cval->cmask |= (1 << i); - cval->channels++; - } - } - - /* get min/max values */ - get_min_max(cval, 0); - - kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return; - } - kctl->private_free = usb_mixer_elem_free; - - len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); - if (! len) - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) - len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); - append_ctl_name(kctl, " Volume"); - - snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max); - add_control_to_empty(state, kctl); -} - - -/* - * parse a mixer unit - */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) { - snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid); - return -EINVAL; - } - /* no bmControls field (e.g. Maya44) -> ignore */ - if (desc[0] <= 10 + input_pins) { - snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid); - return 0; - } - - num_ins = 0; - ich = 0; - for (pin = 0; pin < input_pins; pin++) { - err = parse_audio_unit(state, desc[5 + pin]); - if (err < 0) - return err; - err = check_input_term(state, desc[5 + pin], &iterm); - if (err < 0) - return err; - num_ins += iterm.channels; - for (; ich < num_ins; ++ich) { - int och, ich_has_controls = 0; - - for (och = 0; och < num_outs; ++och) { - if (check_matrix_bitmap(desc + 9 + input_pins, - ich, och, num_outs)) { - ich_has_controls = 1; - break; - } - } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, - unitid, &iterm); - } - } - return 0; -} - - -/* - * Processing Unit / Extension Unit - */ - -/* get callback for processing/extension unit */ -static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int err, val; - - err = get_cur_ctl_value(cval, cval->control << 8, &val); - if (err < 0 && cval->mixer->ignore_ctl_error) { - ucontrol->value.integer.value[0] = cval->min; - return 0; - } - if (err < 0) - return err; - val = get_relative_value(cval, val); - ucontrol->value.integer.value[0] = val; - return 0; -} - -/* put callback for processing/extension unit */ -static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int val, oval, err; - - err = get_cur_ctl_value(cval, cval->control << 8, &oval); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) - return 0; - return err; - } - val = ucontrol->value.integer.value[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_ctl_value(cval, cval->control << 8, val); - return 1; - } - return 0; -} - -/* alsa control interface for processing/extension unit */ -static struct snd_kcontrol_new mixer_procunit_ctl = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "", /* will be filled later */ - .info = mixer_ctl_feature_info, - .get = mixer_ctl_procunit_get, - .put = mixer_ctl_procunit_put, -}; - - -/* - * predefined data for processing units - */ -struct procunit_value_info { - int control; - char *suffix; - int val_type; - int min_value; -}; - -struct procunit_info { - int type; - char *name; - struct procunit_value_info *values; -}; - -static struct procunit_value_info updown_proc_info[] = { - { USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, - { 0 } -}; -static struct procunit_value_info prologic_proc_info[] = { - { USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, - { 0 } -}; -static struct procunit_value_info threed_enh_proc_info[] = { - { USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 }, - { 0 } -}; -static struct procunit_value_info reverb_proc_info[] = { - { USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 }, - { USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 }, - { 0 } -}; -static struct procunit_value_info chorus_proc_info[] = { - { USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, - { USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, - { 0 } -}; -static struct procunit_value_info dcr_proc_info[] = { - { USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 }, - { USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 }, - { USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, - { USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 }, - { USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 }, - { 0 } -}; - -static struct procunit_info procunits[] = { - { USB_PROC_UPDOWN, "Up Down", updown_proc_info }, - { USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info }, - { USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info }, - { USB_PROC_REVERB, "Reverb", reverb_proc_info }, - { USB_PROC_CHORUS, "Chorus", chorus_proc_info }, - { USB_PROC_DCR, "DCR", dcr_proc_info }, - { 0 }, -}; -/* - * predefined data for extension units - */ -static struct procunit_value_info clock_rate_xu_info[] = { - { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, - { 0 } -}; -static struct procunit_value_info clock_source_xu_info[] = { - { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN }, - { 0 } -}; -static struct procunit_value_info spdif_format_xu_info[] = { - { USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN }, - { 0 } -}; -static struct procunit_value_info soft_limit_xu_info[] = { - { USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN }, - { 0 } -}; -static struct procunit_info extunits[] = { - { USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info }, - { USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info }, - { USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info }, - { USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info }, - { 0 } -}; -/* - * build a processing/extension unit - */ -static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name) -{ - int num_ins = dsc[6]; - struct usb_mixer_elem_info *cval; - struct snd_kcontrol *kctl; - int i, err, nameid, type, len; - struct procunit_info *info; - struct procunit_value_info *valinfo; - const struct usbmix_name_map *map; - static struct procunit_value_info default_value_info[] = { - { 0x01, "Switch", USB_MIXER_BOOLEAN }, - { 0 } - }; - static struct procunit_info default_info = { - 0, NULL, default_value_info - }; - - if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) { - snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } - - for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, dsc[7 + i])) < 0) - return err; - } - - type = combine_word(&dsc[4]); - for (info = list; info && info->type; info++) - if (info->type == type) - break; - if (! info || ! info->type) - info = &default_info; - - for (valinfo = info->values; valinfo->control; valinfo++) { - /* FIXME: bitmap might be longer than 8bit */ - if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1)))) - continue; - map = find_map(state, unitid, valinfo->control); - if (check_ignored_ctl(map)) - continue; - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return -ENOMEM; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = valinfo->control; - cval->val_type = valinfo->val_type; - cval->channels = 1; - - /* get min/max values */ - if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { - /* FIXME: hard-coded */ - cval->min = 1; - cval->max = dsc[15]; - cval->res = 1; - cval->initialized = 1; - } else { - if (type == USB_XU_CLOCK_RATE) { - /* E-Mu USB 0404/0202/TrackerPre - * samplerate control quirk - */ - cval->min = 0; - cval->max = 5; - cval->res = 1; - cval->initialized = 1; - } else - get_min_max(cval, valinfo->min_value); - } - - kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return -ENOMEM; - } - kctl->private_free = usb_mixer_elem_free; - - if (check_mapped_name(map, kctl->id.name, - sizeof(kctl->id.name))) - /* nothing */ ; - else if (info->name) - strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); - else { - nameid = dsc[12 + num_ins + dsc[11 + num_ins]]; - len = 0; - if (nameid) - len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - if (! len) - strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); - } - append_ctl_name(kctl, " "); - append_ctl_name(kctl, valinfo->suffix); - - snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max); - if ((err = add_control_to_empty(state, kctl)) < 0) - return err; - } - return 0; -} - - -static int parse_audio_processing_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit"); -} - -static int parse_audio_extension_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - return build_audio_procunit(state, unitid, desc, extunits, "Extension Unit"); -} - - -/* - * Selector Unit - */ - -/* info callback for selector unit - * use an enumerator type for routing - */ -static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - char **itemlist = (char **)kcontrol->private_value; - - if (snd_BUG_ON(!itemlist)) - return -EINVAL; - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = cval->max; - if ((int)uinfo->value.enumerated.item >= cval->max) - uinfo->value.enumerated.item = cval->max - 1; - strcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item]); - return 0; -} - -/* get callback for selector unit */ -static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int val, err; - - err = get_cur_ctl_value(cval, 0, &val); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) { - ucontrol->value.enumerated.item[0] = 0; - return 0; - } - return err; - } - val = get_relative_value(cval, val); - ucontrol->value.enumerated.item[0] = val; - return 0; -} - -/* put callback for selector unit */ -static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - int val, oval, err; - - err = get_cur_ctl_value(cval, 0, &oval); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) - return 0; - return err; - } - val = ucontrol->value.enumerated.item[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_ctl_value(cval, 0, val); - return 1; - } - return 0; -} - -/* alsa control interface for selector unit */ -static struct snd_kcontrol_new mixer_selectunit_ctl = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "", /* will be filled later */ - .info = mixer_ctl_selector_info, - .get = mixer_ctl_selector_get, - .put = mixer_ctl_selector_put, -}; - - -/* private free callback. - * free both private_data and private_value - */ -static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) -{ - int i, num_ins = 0; - - if (kctl->private_data) { - struct usb_mixer_elem_info *cval = kctl->private_data; - num_ins = cval->max; - kfree(cval); - kctl->private_data = NULL; - } - if (kctl->private_value) { - char **itemlist = (char **)kctl->private_value; - for (i = 0; i < num_ins; i++) - kfree(itemlist[i]); - kfree(itemlist); - kctl->private_value = 0; - } -} - -/* - * parse a selector unit - */ -static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - unsigned int num_ins = desc[4]; - unsigned int i, nameid, len; - int err; - struct usb_mixer_elem_info *cval; - struct snd_kcontrol *kctl; - const struct usbmix_name_map *map; - char **namelist; - - if (! num_ins || desc[0] < 5 + num_ins) { - snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid); - return -EINVAL; - } - - for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, desc[5 + i])) < 0) - return err; - } - - if (num_ins == 1) /* only one ? nonsense! */ - return 0; - - map = find_map(state, unitid, 0); - if (check_ignored_ctl(map)) - return 0; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return -ENOMEM; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->val_type = USB_MIXER_U8; - cval->channels = 1; - cval->min = 1; - cval->max = num_ins; - cval->res = 1; - cval->initialized = 1; - - namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL); - if (! namelist) { - snd_printk(KERN_ERR "cannot malloc\n"); - kfree(cval); - return -ENOMEM; - } -#define MAX_ITEM_NAME_LEN 64 - for (i = 0; i < num_ins; i++) { - struct usb_audio_term iterm; - len = 0; - namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); - if (! namelist[i]) { - snd_printk(KERN_ERR "cannot malloc\n"); - while (i--) - kfree(namelist[i]); - kfree(namelist); - kfree(cval); - return -ENOMEM; - } - len = check_mapped_selector_name(state, unitid, i, namelist[i], - MAX_ITEM_NAME_LEN); - if (! len && check_input_term(state, desc[5 + i], &iterm) >= 0) - len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); - if (! len) - sprintf(namelist[i], "Input %d", i); - } - - kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(namelist); - kfree(cval); - return -ENOMEM; - } - kctl->private_value = (unsigned long)namelist; - kctl->private_free = usb_mixer_selector_elem_free; - - nameid = desc[desc[0] - 1]; - len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); - if (len) - ; - else if (nameid) - snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - else { - len = get_term_name(state, &state->oterm, - kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) - strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); - - if ((state->oterm.type & 0xff00) == 0x0100) - append_ctl_name(kctl, " Capture Source"); - else - append_ctl_name(kctl, " Playback Source"); - } - - snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n", - cval->id, kctl->id.name, num_ins); - if ((err = add_control_to_empty(state, kctl)) < 0) - return err; - - return 0; -} - - -/* - * parse an audio unit recursively - */ - -static int parse_audio_unit(struct mixer_build *state, int unitid) -{ - unsigned char *p1; - - if (test_and_set_bit(unitid, state->unitbitmap)) - return 0; /* the unit already visited */ - - p1 = find_audio_control_unit(state, unitid); - if (!p1) { - snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid); - return -EINVAL; - } - - switch (p1[2]) { - case UAC_INPUT_TERMINAL: - return 0; /* NOP */ - case UAC_MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case UAC_SELECTOR_UNIT: - return parse_audio_selector_unit(state, unitid, p1); - case UAC_FEATURE_UNIT: - return parse_audio_feature_unit(state, unitid, p1); - case UAC_PROCESSING_UNIT_V1: - return parse_audio_processing_unit(state, unitid, p1); - case UAC_EXTENSION_UNIT_V1: - return parse_audio_extension_unit(state, unitid, p1); - default: - snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } -} - -static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) -{ - kfree(mixer->id_elems); - if (mixer->urb) { - kfree(mixer->urb->transfer_buffer); - usb_free_urb(mixer->urb); - } - usb_free_urb(mixer->rc_urb); - kfree(mixer->rc_setup_packet); - kfree(mixer); -} - -static int snd_usb_mixer_dev_free(struct snd_device *device) -{ - struct usb_mixer_interface *mixer = device->device_data; - snd_usb_mixer_free(mixer); - return 0; -} - -/* - * create mixer controls - * - * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers - */ -static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) -{ - struct uac_output_terminal_descriptor_v1 *desc; - struct mixer_build state; - int err; - const struct usbmix_ctl_map *map; - struct usb_host_interface *hostif; - - hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; - memset(&state, 0, sizeof(state)); - state.chip = mixer->chip; - state.mixer = mixer; - state.buffer = hostif->extra; - state.buflen = hostif->extralen; - - /* check the mapping table */ - for (map = usbmix_ctl_maps; map->id; map++) { - if (map->id == state.chip->usb_id) { - state.map = map->map; - state.selector_map = map->selector_map; - mixer->ignore_ctl_error = map->ignore_ctl_error; - break; - } - } - - desc = NULL; - while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, UAC_OUTPUT_TERMINAL)) != NULL) { - if (desc->bLength < 9) - continue; /* invalid descriptor? */ - set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ - state.oterm.id = desc->bTerminalID; - state.oterm.type = le16_to_cpu(desc->wTerminalType); - state.oterm.name = desc->iTerminal; - err = parse_audio_unit(&state, desc->bSourceID); - if (err < 0) - return err; - } - return 0; -} - -void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) -{ - struct usb_mixer_elem_info *info; - - for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) - snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, - info->elem_id); -} - -static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, - int unitid, - struct usb_mixer_elem_info *cval) -{ - static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN", - "S8", "U8", "S16", "U16"}; - snd_iprintf(buffer, " Unit: %i\n", unitid); - if (cval->elem_id) - snd_iprintf(buffer, " Control: name=\"%s\", index=%i\n", - cval->elem_id->name, cval->elem_id->index); - snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " - "channels=%i, type=\"%s\"\n", cval->id, - cval->control, cval->cmask, cval->channels, - val_types[cval->val_type]); - snd_iprintf(buffer, " Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n", - cval->min, cval->max, cval->dBmin, cval->dBmax); -} - -static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, - struct snd_info_buffer *buffer) -{ - struct snd_usb_audio *chip = entry->private_data; - struct usb_mixer_interface *mixer; - struct usb_mixer_elem_info *cval; - int unitid; - - list_for_each_entry(mixer, &chip->mixer_list, list) { - snd_iprintf(buffer, - "USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n", - chip->usb_id, mixer->ctrlif, - mixer->ignore_ctl_error); - snd_iprintf(buffer, "Card: %s\n", chip->card->longname); - for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) { - for (cval = mixer->id_elems[unitid]; cval; - cval = cval->next_id_elem) - snd_usb_mixer_dump_cval(buffer, unitid, cval); - } - } -} - -static void snd_usb_mixer_status_complete(struct urb *urb) -{ - struct usb_mixer_interface *mixer = urb->context; - - if (urb->status == 0) { - u8 *buf = urb->transfer_buffer; - int i; - - for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) { - snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n", - buf[0], buf[1]); - /* ignore any notifications not from the control interface */ - if ((buf[0] & 0x0f) != 0) - continue; - if (!(buf[0] & 0x40)) - snd_usb_mixer_notify_id(mixer, buf[1]); - else - snd_usb_mixer_rc_memory_change(mixer, buf[1]); - } - } - if (urb->status != -ENOENT && urb->status != -ECONNRESET) { - urb->dev = mixer->chip->dev; - usb_submit_urb(urb, GFP_ATOMIC); - } -} - -/* create the handler for the optional status interrupt endpoint */ -static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) -{ - struct usb_host_interface *hostif; - struct usb_endpoint_descriptor *ep; - void *transfer_buffer; - int buffer_length; - unsigned int epnum; - - hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; - /* we need one interrupt input endpoint */ - if (get_iface_desc(hostif)->bNumEndpoints < 1) - return 0; - ep = get_endpoint(hostif, 0); - if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep)) - return 0; - - epnum = usb_endpoint_num(ep); - buffer_length = le16_to_cpu(ep->wMaxPacketSize); - transfer_buffer = kmalloc(buffer_length, GFP_KERNEL); - if (!transfer_buffer) - return -ENOMEM; - mixer->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!mixer->urb) { - kfree(transfer_buffer); - return -ENOMEM; - } - usb_fill_int_urb(mixer->urb, mixer->chip->dev, - usb_rcvintpipe(mixer->chip->dev, epnum), - transfer_buffer, buffer_length, - snd_usb_mixer_status_complete, mixer, ep->bInterval); - usb_submit_urb(mixer->urb, GFP_KERNEL); - return 0; -} - -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, - int ignore_error) -{ - static struct snd_device_ops dev_ops = { - .dev_free = snd_usb_mixer_dev_free - }; - struct usb_mixer_interface *mixer; - struct snd_info_entry *entry; - struct usb_host_interface *host_iface; - int err, protocol; - - strcpy(chip->card->mixername, "USB Mixer"); - - mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); - if (!mixer) - return -ENOMEM; - mixer->chip = chip; - mixer->ctrlif = ctrlif; - mixer->ignore_ctl_error = ignore_error; - mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems), - GFP_KERNEL); - if (!mixer->id_elems) { - kfree(mixer); - return -ENOMEM; - } - - host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; - protocol = host_iface->desc.bInterfaceProtocol; - - /* FIXME! */ - if (protocol != UAC_VERSION_1) { - snd_printk(KERN_WARNING "mixer interface protocol 0x%02x not yet supported\n", - protocol); - return 0; - } - - if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) - goto _error; - - snd_usb_mixer_apply_create_quirk(mixer); - - err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops); - if (err < 0) - goto _error; - - if (list_empty(&chip->mixer_list) && - !snd_card_proc_new(chip->card, "usbmixer", &entry)) - snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read); - - list_add(&mixer->list, &chip->mixer_list); - return 0; - -_error: - snd_usb_mixer_free(mixer); - return err; -} - -void snd_usb_mixer_disconnect(struct list_head *p) -{ - struct usb_mixer_interface *mixer; - - mixer = list_entry(p, struct usb_mixer_interface, list); - usb_kill_urb(mixer->urb); - usb_kill_urb(mixer->rc_urb); -} diff --git a/sound/usb/usbmixer.h b/sound/usb/usbmixer.h deleted file mode 100644 index 63101ae201c..00000000000 --- a/sound/usb/usbmixer.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef __USBMIXER_H -#define __USBMIXER_H - -struct usb_mixer_interface { - struct snd_usb_audio *chip; - unsigned int ctrlif; - struct list_head list; - unsigned int ignore_ctl_error; - struct urb *urb; - /* array[MAX_ID_ELEMS], indexed by unit id */ - struct usb_mixer_elem_info **id_elems; - - /* Sound Blaster remote control stuff */ - const struct rc_config *rc_cfg; - u32 rc_code; - wait_queue_head_t rc_waitq; - struct urb *rc_urb; - struct usb_ctrlrequest *rc_setup_packet; - u8 rc_buffer[6]; - - u8 audigy2nx_leds[3]; - u8 xonar_u1_status; -}; - -#define MAX_CHANNELS 10 /* max logical channels */ - -struct usb_mixer_elem_info { - struct usb_mixer_interface *mixer; - struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ - struct snd_ctl_elem_id *elem_id; - unsigned int id; - unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ - int channels; - int val_type; - int min, max, res; - int dBmin, dBmax; - int cached; - int cache_val[MAX_CHANNELS]; - u8 initialized; -}; - -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, - int ignore_error); -void snd_usb_mixer_disconnect(struct list_head *p); - -void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); - -int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, - int request, int validx, int value_set); - -#endif /* __USBMIXER_H */ diff --git a/sound/usb/usbmixer_maps.c b/sound/usb/usbmixer_maps.c deleted file mode 100644 index d93fc89beba..00000000000 --- a/sound/usb/usbmixer_maps.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Additional mixer mapping - * - * Copyright (c) 2002 by Takashi Iwai - * - * 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. - * - * 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 - * - */ - -struct usbmix_dB_map { - u32 min; - u32 max; -}; - -struct usbmix_name_map { - int id; - const char *name; - int control; - struct usbmix_dB_map *dB; -}; - -struct usbmix_selector_map { - int id; - int count; - const char **names; -}; - -struct usbmix_ctl_map { - u32 id; - const struct usbmix_name_map *map; - const struct usbmix_selector_map *selector_map; - int ignore_ctl_error; -}; - -/* - * USB control mappers for SB Exitigy - */ - -/* - * Topology of SB Extigy (see on the wide screen :) - -USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24] - ^ | | | | -USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26] - ^ ^ | | | | -Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19] - | | | | -Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20] - | | | -Mic-IN[9] --+->FU[10]----------------------------+ | - || | - || +----------------------------------------------------+ - VV V - ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13] -*/ - -static struct usbmix_name_map extigy_map[] = { - /* 1: IT pcm */ - { 2, "PCM Playback" }, /* FU */ - /* 3: IT pcm */ - /* 4: IT digital in */ - { 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */ - { 6, "Digital In" }, /* FU */ - /* 7: IT line */ - { 8, "Line Playback" }, /* FU */ - /* 9: IT mic */ - { 10, "Mic Playback" }, /* FU */ - { 11, "Capture Source" }, /* SU */ - { 12, "Capture" }, /* FU */ - /* 13: OT pcm capture */ - /* 14: MU (w/o controls) */ - /* 15: PU (3D enh) */ - /* 16: MU (w/o controls) */ - { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ - { 17, "Channel Routing", 2 }, /* PU: mode select */ - { 18, "Tone Control - Bass", UAC_BASS_CONTROL }, /* FU */ - { 18, "Tone Control - Treble", UAC_TREBLE_CONTROL }, /* FU */ - { 18, "Master Playback" }, /* FU; others */ - /* 19: OT speaker */ - /* 20: OT headphone */ - { 21, NULL }, /* DISABLED: EU (for what?) */ - { 22, "Digital Out Playback" }, /* FU */ - { 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */ - /* 24: OT digital out */ - { 25, "IEC958 Optical Playback" }, /* FU */ - { 26, "IEC958 Optical Playback" }, /* OT */ - { 27, NULL }, /* DISABLED: EU (for what?) */ - /* 28: FU speaker (mute) */ - { 29, NULL }, /* Digital Input Playback Source? */ - { 0 } /* terminator */ -}; - -/* Sound Blaster MP3+ controls mapping - * The default mixer channels have totally misleading names, - * e.g. no Master and fake PCM volume - * Pavel Mihaylov - */ -static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0}; /* just guess */ -static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */ - -static struct usbmix_name_map mp3plus_map[] = { - /* 1: IT pcm */ - /* 2: IT mic */ - /* 3: IT line */ - /* 4: IT digital in */ - /* 5: OT digital out */ - /* 6: OT speaker */ - /* 7: OT pcm capture */ - { 8, "Capture Source" }, /* FU, default PCM Capture Source */ - /* (Mic, Input 1 = Line input, Input 2 = Optical input) */ - { 9, "Master Playback" }, /* FU, default Speaker 1 */ - /* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */ - { 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 }, - /* FU, Mic Capture */ - { 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */ - { 11, "Line Capture", .dB = &mp3plus_dB_2 }, - /* FU, default PCM Capture */ - { 12, "Digital In Playback" }, /* FU, default PCM 1 */ - { 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 }, - /* FU, default Mic Playback */ - { 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */ - /* 15: MU */ - { 0 } /* terminator */ -}; - -/* Topology of SB Audigy 2 NX - - +----------------------------->EU[27]--+ - | v - | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24] - | | ^ -USB_IN[1]-+------------+ +->EU[17]->+->FU[11]-+ - | v | v | -Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20] - | ^ | | -Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19] - | | v - +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15] - | ^ - +->FU[13]--------------------------------------+ -*/ -static struct usbmix_name_map audigy2nx_map[] = { - /* 1: IT pcm playback */ - /* 4: IT digital in */ - { 6, "Digital In Playback" }, /* FU */ - /* 7: IT line in */ - { 8, "Line Playback" }, /* FU */ - { 11, "What-U-Hear Capture" }, /* FU */ - { 12, "Line Capture" }, /* FU */ - { 13, "Digital In Capture" }, /* FU */ - { 14, "Capture Source" }, /* SU */ - /* 15: OT pcm capture */ - /* 16: MU w/o controls */ - { 17, NULL }, /* DISABLED: EU (for what?) */ - { 18, "Master Playback" }, /* FU */ - /* 19: OT speaker */ - /* 20: OT headphone */ - { 21, NULL }, /* DISABLED: EU (for what?) */ - { 22, "Digital Out Playback" }, /* FU */ - { 23, NULL }, /* DISABLED: EU (for what?) */ - /* 24: OT digital out */ - { 27, NULL }, /* DISABLED: EU (for what?) */ - { 28, "Speaker Playback" }, /* FU */ - { 29, "Digital Out Source" }, /* SU */ - { 30, "Headphone Playback" }, /* FU */ - { 31, "Headphone Source" }, /* SU */ - { 0 } /* terminator */ -}; - -static struct usbmix_selector_map audigy2nx_selectors[] = { - { - .id = 14, /* Capture Source */ - .count = 3, - .names = (const char*[]) {"Line", "Digital In", "What-U-Hear"} - }, - { - .id = 29, /* Digital Out Source */ - .count = 3, - .names = (const char*[]) {"Front", "PCM", "Digital In"} - }, - { - .id = 31, /* Headphone Source */ - .count = 2, - .names = (const char*[]) {"Front", "Side"} - }, - { 0 } /* terminator */ -}; - -/* Creative SoundBlaster Live! 24-bit External */ -static struct usbmix_name_map live24ext_map[] = { - /* 2: PCM Playback Volume */ - { 5, "Mic Capture" }, /* FU, default PCM Capture Volume */ - { 0 } /* terminator */ -}; - -/* LineX FM Transmitter entry - needed to bypass controls bug */ -static struct usbmix_name_map linex_map[] = { - /* 1: IT pcm */ - /* 2: OT Speaker */ - { 3, "Master" }, /* FU: master volume - left / right / mute */ - { 0 } /* terminator */ -}; - -static struct usbmix_name_map maya44_map[] = { - /* 1: IT line */ - { 2, "Line Playback" }, /* FU */ - /* 3: IT line */ - { 4, "Line Playback" }, /* FU */ - /* 5: IT pcm playback */ - /* 6: MU */ - { 7, "Master Playback" }, /* FU */ - /* 8: OT speaker */ - /* 9: IT line */ - { 10, "Line Capture" }, /* FU */ - /* 11: MU */ - /* 12: OT pcm capture */ - { } -}; - -/* Section "justlink_map" below added by James Courtier-Dutton - * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK - * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.) - * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device, - * so this map removes all unwanted sliders from alsamixer - */ - -static struct usbmix_name_map justlink_map[] = { - /* 1: IT pcm playback */ - /* 2: Not present */ - { 3, NULL}, /* IT mic (No mic input on device) */ - /* 4: Not present */ - /* 5: OT speacker */ - /* 6: OT pcm capture */ - { 7, "Master Playback" }, /* Mute/volume for speaker */ - { 8, NULL }, /* Capture Switch (No capture inputs on device) */ - { 9, NULL }, /* Capture Mute/volume (No capture inputs on device */ - /* 0xa: Not present */ - /* 0xb: MU (w/o controls) */ - { 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */ - { 0 } /* terminator */ -}; - -/* TerraTec Aureon 5.1 MkII USB */ -static struct usbmix_name_map aureon_51_2_map[] = { - /* 1: IT USB */ - /* 2: IT Mic */ - /* 3: IT Line */ - /* 4: IT SPDIF */ - /* 5: OT SPDIF */ - /* 6: OT Speaker */ - /* 7: OT USB */ - { 8, "Capture Source" }, /* SU */ - { 9, "Master Playback" }, /* FU */ - { 10, "Mic Capture" }, /* FU */ - { 11, "Line Capture" }, /* FU */ - { 12, "IEC958 In Capture" }, /* FU */ - { 13, "Mic Playback" }, /* FU */ - { 14, "Line Playback" }, /* FU */ - /* 15: MU */ - {} /* terminator */ -}; - -static struct usbmix_name_map scratch_live_map[] = { - /* 1: IT Line 1 (USB streaming) */ - /* 2: OT Line 1 (Speaker) */ - /* 3: IT Line 1 (Line connector) */ - { 4, "Line 1 In" }, /* FU */ - /* 5: OT Line 1 (USB streaming) */ - /* 6: IT Line 2 (USB streaming) */ - /* 7: OT Line 2 (Speaker) */ - /* 8: IT Line 2 (Line connector) */ - { 9, "Line 2 In" }, /* FU */ - /* 10: OT Line 2 (USB streaming) */ - /* 11: IT Mic (Line connector) */ - /* 12: OT Mic (USB streaming) */ - { 0 } /* terminator */ -}; - -/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+" - * most importand difference is SU[8], it should be set to "Capture Source" - * to make alsamixer and PA working properly. - * FIXME: or mp3plus_map should use "Capture Source" too, - * so this maps can be merget - */ -static struct usbmix_name_map hercules_usb51_map[] = { - { 8, "Capture Source" }, /* SU, default "PCM Capture Source" */ - { 9, "Master Playback" }, /* FU, default "Speaker Playback" */ - { 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */ - { 11, "Line Capture" }, /* FU, default "PCM Capture" */ - { 13, "Mic Bypass Playback" }, /* FU, default "Mic Playback" */ - { 14, "Line Bypass Playback" }, /* FU, default "Line Playback" */ - { 0 } /* terminator */ -}; - -/* - * Control map entries - */ - -static struct usbmix_ctl_map usbmix_ctl_maps[] = { - { - .id = USB_ID(0x041e, 0x3000), - .map = extigy_map, - .ignore_ctl_error = 1, - }, - { - .id = USB_ID(0x041e, 0x3010), - .map = mp3plus_map, - }, - { - .id = USB_ID(0x041e, 0x3020), - .map = audigy2nx_map, - .selector_map = audigy2nx_selectors, - }, - { - .id = USB_ID(0x041e, 0x3040), - .map = live24ext_map, - }, - { - .id = USB_ID(0x041e, 0x3048), - .map = audigy2nx_map, - .selector_map = audigy2nx_selectors, - }, - { - /* Hercules DJ Console (Windows Edition) */ - .id = USB_ID(0x06f8, 0xb000), - .ignore_ctl_error = 1, - }, - { - /* Hercules DJ Console (Macintosh Edition) */ - .id = USB_ID(0x06f8, 0xd002), - .ignore_ctl_error = 1, - }, - { - /* Hercules Gamesurround Muse Pocket LT - * (USB 5.1 Channel Audio Adapter) - */ - .id = USB_ID(0x06f8, 0xc000), - .map = hercules_usb51_map, - }, - { - .id = USB_ID(0x08bb, 0x2702), - .map = linex_map, - .ignore_ctl_error = 1, - }, - { - .id = USB_ID(0x0a92, 0x0091), - .map = maya44_map, - }, - { - .id = USB_ID(0x0c45, 0x1158), - .map = justlink_map, - }, - { - .id = USB_ID(0x0ccd, 0x0028), - .map = aureon_51_2_map, - }, - { - .id = USB_ID(0x13e5, 0x0001), - .map = scratch_live_map, - .ignore_ctl_error = 1, - }, - { 0 } /* terminator */ -}; - -- cgit v1.2.3-18-g5258 From 99fc86450c439039d2ef88d06b222fd51a779176 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:24 +0100 Subject: ALSA: usb-mixer: parse descriptors with structs Introduce a number of new structs for mixer, selector, feature and processing units and some static inline helpers to access fields which have dynamic offsets. Use them in mixer.c to parse the descriptors. This is necessary for the upcoming audio v2 parsers. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio.h | 129 ++++++++++++++++++++++++++++++++++++++++++---- sound/usb/mixer.c | 87 +++++++++++++++++-------------- 2 files changed, 166 insertions(+), 50 deletions(-) diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index cdad728543a..bc78a83d0f4 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -181,6 +181,125 @@ struct uac_feature_unit_descriptor_##ch { \ __u8 iFeature; \ } __attribute__ ((packed)) +/* 4.3.2.3 Mixer Unit Descriptor */ +struct uac_mixer_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bUnitID; + __u8 bNrInPins; + __u8 baSourceID[]; +} __attribute__ ((packed)); + +static inline __u8 uac_mixer_unit_bNrChannels(struct uac_mixer_unit_descriptor *desc) +{ + return desc->baSourceID[desc->bNrInPins]; +} + +static inline __u16 uac_mixer_unit_wChannelConfig(struct uac_mixer_unit_descriptor *desc) +{ + return (desc->baSourceID[desc->bNrInPins + 2] << 8) | + desc->baSourceID[desc->bNrInPins + 1]; +} + +static inline __u8 uac_mixer_unit_iChannelNames(struct uac_mixer_unit_descriptor *desc) +{ + return desc->baSourceID[desc->bNrInPins + 3]; +} + +static inline __u8 *uac_mixer_unit_bmControls(struct uac_mixer_unit_descriptor *desc) +{ + return &desc->baSourceID[desc->bNrInPins + 4]; +} + +static inline __u8 uac_mixer_unit_iMixer(struct uac_mixer_unit_descriptor *desc) +{ + __u8 *raw = (__u8 *) desc; + return raw[desc->bLength - 1]; +} + +/* 4.3.2.4 Selector Unit Descriptor */ +struct uac_selector_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bUintID; + __u8 bNrInPins; + __u8 baSourceID[]; +} __attribute__ ((packed)); + +static inline __u8 uac_selector_unit_iSelector(struct uac_selector_unit_descriptor *desc) +{ + __u8 *raw = (__u8 *) desc; + return raw[desc->bLength - 1]; +} + +/* 4.3.2.5 Feature Unit Descriptor */ +struct uac_feature_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bUnitID; + __u8 bSourceID; + __u8 bControlSize; + __u8 bmaControls[0]; /* variable length */ +} __attribute__((packed)); + +static inline __u8 uac_feature_unit_iFeature(struct uac_feature_unit_descriptor *desc) +{ + __u8 *raw = (__u8 *) desc; + return raw[desc->bLength - 1]; +} + +/* 4.3.2.6 Processing Unit Descriptors */ +struct uac_processing_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bUnitID; + __u16 wProcessType; + __u8 bNrInPins; + __u8 baSourceID[]; +} __attribute__ ((packed)); + +static inline __u8 uac_processing_unit_bNrChannels(struct uac_processing_unit_descriptor *desc) +{ + return desc->baSourceID[desc->bNrInPins]; +} + +static inline __u16 uac_processing_unit_wChannelConfig(struct uac_processing_unit_descriptor *desc) +{ + return (desc->baSourceID[desc->bNrInPins + 2] << 8) | + desc->baSourceID[desc->bNrInPins + 1]; +} + +static inline __u8 uac_processing_unit_iChannelNames(struct uac_processing_unit_descriptor *desc) +{ + return desc->baSourceID[desc->bNrInPins + 3]; +} + +static inline __u8 uac_processing_unit_bControlSize(struct uac_processing_unit_descriptor *desc) +{ + return desc->baSourceID[desc->bNrInPins + 4]; +} + +static inline __u8 *uac_processing_unit_bmControls(struct uac_processing_unit_descriptor *desc) +{ + return &desc->baSourceID[desc->bNrInPins + 5]; +} + +static inline __u8 uac_processing_unit_iProcessing(struct uac_processing_unit_descriptor *desc) +{ + __u8 control_size = uac_processing_unit_bControlSize(desc); + return desc->baSourceID[desc->bNrInPins + control_size]; +} + +static inline __u8 *uac_processing_unit_specific(struct uac_processing_unit_descriptor *desc) +{ + __u8 control_size = uac_processing_unit_bControlSize(desc); + return &desc->baSourceID[desc->bNrInPins + control_size + 1]; +} + /* 4.5.2 Class-Specific AS Interface Descriptor */ struct uac_as_header_descriptor_v1 { __u8 bLength; /* in bytes: 7 */ @@ -315,16 +434,6 @@ struct uac_iso_endpoint_descriptor { /* A.10.2 Feature Unit Control Selectors */ -struct uac_feature_unit_descriptor { - __u8 bLength; - __u8 bDescriptorType; - __u8 bDescriptorSubtype; - __u8 bUnitID; - __u8 bSourceID; - __u8 bControlSize; - __u8 controls[0]; /* variable length */ -} __attribute__((packed)); - #define UAC_FU_CONTROL_UNDEFINED 0x00 #define UAC_MUTE_CONTROL 0x01 #define UAC_VOLUME_CONTROL 0x02 diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 4e7c2fd9e3b..994b0385235 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -860,13 +860,14 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); } -static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, +static void build_feature_ctl(struct mixer_build *state, void *raw_desc, unsigned int ctl_mask, int control, struct usb_audio_term *iterm, int unitid) { + struct uac_feature_unit_descriptor *desc = raw_desc; unsigned int len = 0; int mapped_name = 0; - int nameid = desc[desc[0] - 1]; + int nameid = uac_feature_unit_iFeature(desc); struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; @@ -1032,7 +1033,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void channels = (ftr->bLength - 7) / csize - 1; - master_bits = snd_usb_combine_bytes(ftr->controls, csize); + master_bits = snd_usb_combine_bytes(ftr->bmaControls, csize); /* master configuration quirks */ switch (state->chip->usb_id) { case USB_ID(0x08bb, 0x2702): @@ -1043,14 +1044,14 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void break; } if (channels > 0) - first_ch_bits = snd_usb_combine_bytes(ftr->controls + csize, csize); + first_ch_bits = snd_usb_combine_bytes(ftr->bmaControls + csize, csize); else first_ch_bits = 0; /* check all control types */ for (i = 0; i < 10; i++) { unsigned int ch_bits = 0; for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(ftr->controls + csize * (j+1), csize); + unsigned int mask = snd_usb_combine_bytes(ftr->bmaControls + csize * (j+1), csize); if (mask & (1 << i)) ch_bits |= (1 << j); } @@ -1075,13 +1076,13 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void * input channel number (zero based) is given in control field instead. */ -static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, +static void build_mixer_unit_ctl(struct mixer_build *state, + struct uac_mixer_unit_descriptor *desc, int in_pin, int in_ch, int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; - unsigned int input_pins = desc[4]; - unsigned int num_outs = desc[5 + input_pins]; + unsigned int num_outs = uac_mixer_unit_bNrChannels(desc); unsigned int i, len; struct snd_kcontrol *kctl; const struct usbmix_name_map *map; @@ -1099,7 +1100,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, cval->control = in_ch + 1; /* based on 1 */ cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { - if (check_matrix_bitmap(desc + 9 + input_pins, in_ch, i, num_outs)) { + if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc), in_ch, i, num_outs)) { cval->cmask |= (1 << i); cval->channels++; } @@ -1132,18 +1133,19 @@ static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigned char *desc) +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *raw_desc) { + struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; int input_pins, num_ins, num_outs; int pin, ich, err; - if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) { + if (desc->bLength < 11 || ! (input_pins = desc->bNrInPins) || ! (num_outs = uac_mixer_unit_bNrChannels(desc))) { snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid); return -EINVAL; } /* no bmControls field (e.g. Maya44) -> ignore */ - if (desc[0] <= 10 + input_pins) { + if (desc->bLength <= 10 + input_pins) { snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid); return 0; } @@ -1151,10 +1153,10 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigne num_ins = 0; ich = 0; for (pin = 0; pin < input_pins; pin++) { - err = parse_audio_unit(state, desc[5 + pin]); + err = parse_audio_unit(state, desc->baSourceID[pin]); if (err < 0) return err; - err = check_input_term(state, desc[5 + pin], &iterm); + err = check_input_term(state, desc->baSourceID[pin], &iterm); if (err < 0) return err; num_ins += iterm.channels; @@ -1162,7 +1164,7 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigne int och, ich_has_controls = 0; for (och = 0; och < num_outs; ++och) { - if (check_matrix_bitmap(desc + 9 + input_pins, + if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc), ich, och, num_outs)) { ich_has_controls = 1; break; @@ -1323,9 +1325,10 @@ static struct procunit_info extunits[] = { /* * build a processing/extension unit */ -static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name) +static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw_desc, struct procunit_info *list, char *name) { - int num_ins = dsc[6]; + struct uac_processing_unit_descriptor *desc = raw_desc; + int num_ins = desc->bNrInPins; struct usb_mixer_elem_info *cval; struct snd_kcontrol *kctl; int i, err, nameid, type, len; @@ -1340,17 +1343,17 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned 0, NULL, default_value_info }; - if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) { + if (desc->bLength < 13 || desc->bLength < 13 + num_ins || desc->bLength < num_ins + uac_processing_unit_bControlSize(desc)) { snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid); return -EINVAL; } for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, dsc[7 + i])) < 0) + if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0) return err; } - type = combine_word(&dsc[4]); + type = le16_to_cpu(desc->wProcessType); for (info = list; info && info->type; info++) if (info->type == type) break; @@ -1358,8 +1361,9 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned info = &default_info; for (valinfo = info->values; valinfo->control; valinfo++) { - /* FIXME: bitmap might be longer than 8bit */ - if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1)))) + __u8 *controls = uac_processing_unit_bmControls(desc); + + if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; map = find_map(state, unitid, valinfo->control); if (check_ignored_ctl(map)) @@ -1377,9 +1381,10 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned /* get min/max values */ if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { + __u8 *control_spec = uac_processing_unit_specific(desc); /* FIXME: hard-coded */ cval->min = 1; - cval->max = dsc[15]; + cval->max = control_spec[0]; cval->res = 1; cval->initialized = 1; } else { @@ -1409,7 +1414,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned else if (info->name) strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); else { - nameid = dsc[12 + num_ins + dsc[11 + num_ins]]; + nameid = uac_processing_unit_iProcessing(desc); len = 0; if (nameid) len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); @@ -1428,14 +1433,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned } -static int parse_audio_processing_unit(struct mixer_build *state, int unitid, unsigned char *desc) +static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc) { - return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit"); + return build_audio_procunit(state, unitid, raw_desc, procunits, "Processing Unit"); } -static int parse_audio_extension_unit(struct mixer_build *state, int unitid, unsigned char *desc) +static int parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc) { - return build_audio_procunit(state, unitid, desc, extunits, "Extension Unit"); + /* Note that we parse extension units with processing unit descriptors. + * That's ok as the layout is the same */ + return build_audio_procunit(state, unitid, raw_desc, extunits, "Extension Unit"); } @@ -1537,9 +1544,9 @@ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) /* * parse a selector unit */ -static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsigned char *desc) +static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void *raw_desc) { - unsigned int num_ins = desc[4]; + struct uac_selector_unit_descriptor *desc = raw_desc; unsigned int i, nameid, len; int err; struct usb_mixer_elem_info *cval; @@ -1547,17 +1554,17 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsi const struct usbmix_name_map *map; char **namelist; - if (! num_ins || desc[0] < 5 + num_ins) { + if (!desc->bNrInPins || desc->bLength < 5 + desc->bNrInPins) { snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid); return -EINVAL; } - for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, desc[5 + i])) < 0) + for (i = 0; i < desc->bNrInPins; i++) { + if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0) return err; } - if (num_ins == 1) /* only one ? nonsense! */ + if (desc->bNrInPins == 1) /* only one ? nonsense! */ return 0; map = find_map(state, unitid, 0); @@ -1574,18 +1581,18 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsi cval->val_type = USB_MIXER_U8; cval->channels = 1; cval->min = 1; - cval->max = num_ins; + cval->max = desc->bNrInPins; cval->res = 1; cval->initialized = 1; - namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL); + namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL); if (! namelist) { snd_printk(KERN_ERR "cannot malloc\n"); kfree(cval); return -ENOMEM; } #define MAX_ITEM_NAME_LEN 64 - for (i = 0; i < num_ins; i++) { + for (i = 0; i < desc->bNrInPins; i++) { struct usb_audio_term iterm; len = 0; namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); @@ -1599,7 +1606,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsi } len = check_mapped_selector_name(state, unitid, i, namelist[i], MAX_ITEM_NAME_LEN); - if (! len && check_input_term(state, desc[5 + i], &iterm) >= 0) + if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0) len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); if (! len) sprintf(namelist[i], "Input %d", i); @@ -1615,7 +1622,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsi kctl->private_value = (unsigned long)namelist; kctl->private_free = usb_mixer_selector_elem_free; - nameid = desc[desc[0] - 1]; + nameid = uac_selector_unit_iSelector(desc); len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (len) ; @@ -1634,7 +1641,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, unsi } snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n", - cval->id, kctl->id.name, num_ins); + cval->id, kctl->id.name, desc->bNrInPins); if ((err = add_control_to_empty(state, kctl)) < 0) return err; -- cgit v1.2.3-18-g5258 From 23caaf19b11eda7054348452e1618d4512a86907 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Thu, 11 Mar 2010 21:13:25 +0100 Subject: ALSA: usb-mixer: Add support for Audio Class v2.0 USB Audio Class v2.0 compliant devices have different descriptors and a different way of setting/getting min/max/res/cur properties. This patch adds support for them. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v2.h | 47 +++++++ include/linux/usb/audio.h | 71 +++++++--- sound/usb/mixer.c | 322 ++++++++++++++++++++++++++++++++----------- sound/usb/mixer.h | 3 + 4 files changed, 343 insertions(+), 100 deletions(-) diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index 3b8560d233b..0952231e6c3 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -43,6 +43,53 @@ struct uac_clock_selector_descriptor { __u8 baCSourceID[]; } __attribute__((packed)); +/* 4.7.2.4 Input terminal descriptor */ + +struct uac2_input_terminal_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bTerminalID; + __u16 wTerminalType; + __u8 bAssocTerminal; + __u8 bCSourceID; + __u8 bNrChannels; + __u32 bmChannelConfig; + __u8 iChannelNames; + __u16 bmControls; + __u8 iTerminal; +} __attribute__((packed)); + +/* 4.7.2.5 Output terminal descriptor */ + +struct uac2_output_terminal_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bTerminalID; + __u16 wTerminalType; + __u8 bAssocTerminal; + __u8 bSourceID; + __u8 bCSourceID; + __u16 bmControls; + __u8 iTerminal; +} __attribute__((packed)); + + + +/* 4.7.2.8 Feature Unit Descriptor */ + +struct uac2_feature_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bUnitID; + __u8 bSourceID; + /* bmaControls is actually u32, + * but u8 is needed for the hybrid parser */ + __u8 bmaControls[0]; /* variable length */ +} __attribute__((packed)); + /* 4.9.2 Class-Specific AS Interface Descriptor */ struct uac_as_header_descriptor_v2 { diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index bc78a83d0f4..905a87caf3f 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -196,20 +196,33 @@ static inline __u8 uac_mixer_unit_bNrChannels(struct uac_mixer_unit_descriptor * return desc->baSourceID[desc->bNrInPins]; } -static inline __u16 uac_mixer_unit_wChannelConfig(struct uac_mixer_unit_descriptor *desc) +static inline __u32 uac_mixer_unit_wChannelConfig(struct uac_mixer_unit_descriptor *desc, + int protocol) { - return (desc->baSourceID[desc->bNrInPins + 2] << 8) | - desc->baSourceID[desc->bNrInPins + 1]; + if (protocol == UAC_VERSION_1) + return (desc->baSourceID[desc->bNrInPins + 2] << 8) | + desc->baSourceID[desc->bNrInPins + 1]; + else + return (desc->baSourceID[desc->bNrInPins + 4] << 24) | + (desc->baSourceID[desc->bNrInPins + 3] << 16) | + (desc->baSourceID[desc->bNrInPins + 2] << 8) | + (desc->baSourceID[desc->bNrInPins + 1]); } -static inline __u8 uac_mixer_unit_iChannelNames(struct uac_mixer_unit_descriptor *desc) +static inline __u8 uac_mixer_unit_iChannelNames(struct uac_mixer_unit_descriptor *desc, + int protocol) { - return desc->baSourceID[desc->bNrInPins + 3]; + return (protocol == UAC_VERSION_1) ? + desc->baSourceID[desc->bNrInPins + 3] : + desc->baSourceID[desc->bNrInPins + 5]; } -static inline __u8 *uac_mixer_unit_bmControls(struct uac_mixer_unit_descriptor *desc) +static inline __u8 *uac_mixer_unit_bmControls(struct uac_mixer_unit_descriptor *desc, + int protocol) { - return &desc->baSourceID[desc->bNrInPins + 4]; + return (protocol == UAC_VERSION_1) ? + &desc->baSourceID[desc->bNrInPins + 4] : + &desc->baSourceID[desc->bNrInPins + 6]; } static inline __u8 uac_mixer_unit_iMixer(struct uac_mixer_unit_descriptor *desc) @@ -267,36 +280,54 @@ static inline __u8 uac_processing_unit_bNrChannels(struct uac_processing_unit_de return desc->baSourceID[desc->bNrInPins]; } -static inline __u16 uac_processing_unit_wChannelConfig(struct uac_processing_unit_descriptor *desc) +static inline __u32 uac_processing_unit_wChannelConfig(struct uac_processing_unit_descriptor *desc, + int protocol) { - return (desc->baSourceID[desc->bNrInPins + 2] << 8) | - desc->baSourceID[desc->bNrInPins + 1]; + if (protocol == UAC_VERSION_1) + return (desc->baSourceID[desc->bNrInPins + 2] << 8) | + desc->baSourceID[desc->bNrInPins + 1]; + else + return (desc->baSourceID[desc->bNrInPins + 4] << 24) | + (desc->baSourceID[desc->bNrInPins + 3] << 16) | + (desc->baSourceID[desc->bNrInPins + 2] << 8) | + (desc->baSourceID[desc->bNrInPins + 1]); } -static inline __u8 uac_processing_unit_iChannelNames(struct uac_processing_unit_descriptor *desc) +static inline __u8 uac_processing_unit_iChannelNames(struct uac_processing_unit_descriptor *desc, + int protocol) { - return desc->baSourceID[desc->bNrInPins + 3]; + return (protocol == UAC_VERSION_1) ? + desc->baSourceID[desc->bNrInPins + 3] : + desc->baSourceID[desc->bNrInPins + 5]; } -static inline __u8 uac_processing_unit_bControlSize(struct uac_processing_unit_descriptor *desc) +static inline __u8 uac_processing_unit_bControlSize(struct uac_processing_unit_descriptor *desc, + int protocol) { - return desc->baSourceID[desc->bNrInPins + 4]; + return (protocol == UAC_VERSION_1) ? + desc->baSourceID[desc->bNrInPins + 4] : + desc->baSourceID[desc->bNrInPins + 6]; } -static inline __u8 *uac_processing_unit_bmControls(struct uac_processing_unit_descriptor *desc) +static inline __u8 *uac_processing_unit_bmControls(struct uac_processing_unit_descriptor *desc, + int protocol) { - return &desc->baSourceID[desc->bNrInPins + 5]; + return (protocol == UAC_VERSION_1) ? + &desc->baSourceID[desc->bNrInPins + 5] : + &desc->baSourceID[desc->bNrInPins + 7]; } -static inline __u8 uac_processing_unit_iProcessing(struct uac_processing_unit_descriptor *desc) +static inline __u8 uac_processing_unit_iProcessing(struct uac_processing_unit_descriptor *desc, + int protocol) { - __u8 control_size = uac_processing_unit_bControlSize(desc); + __u8 control_size = uac_processing_unit_bControlSize(desc, protocol); return desc->baSourceID[desc->bNrInPins + control_size]; } -static inline __u8 *uac_processing_unit_specific(struct uac_processing_unit_descriptor *desc) +static inline __u8 *uac_processing_unit_specific(struct uac_processing_unit_descriptor *desc, + int protocol) { - __u8 control_size = uac_processing_unit_bControlSize(desc); + __u8 control_size = uac_processing_unit_bControlSize(desc, protocol); return &desc->baSourceID[desc->bNrInPins + control_size + 1]; } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 994b0385235..1deef623c08 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -197,6 +198,7 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid, /* * find an audio control unit with the given unit id + * this doesn't return any clock related units, so they need to be handled elsewhere */ static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit) { @@ -205,7 +207,7 @@ static void *find_audio_control_unit(struct mixer_build *state, unsigned char un p = NULL; while ((p = snd_usb_find_desc(state->buffer, state->buflen, p, USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC_EXTENSION_UNIT_V1 && p[3] == unit) + if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC2_EXTENSION_UNIT_V2 && p[3] == unit) return p; } return NULL; @@ -302,7 +304,7 @@ static int get_abs_value(struct usb_mixer_elem_info *cval, int val) * retrieve a mixer value */ -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) { unsigned char buf[2]; int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; @@ -324,6 +326,58 @@ static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int vali return -EINVAL; } +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +{ + unsigned char buf[14]; /* enough space for one range of 4 bytes */ + unsigned char *val; + int ret; + __u8 bRequest; + + bRequest = (request == UAC_GET_CUR) ? + UAC2_CS_CUR : UAC2_CS_RANGE; + + ret = snd_usb_ctl_msg(cval->mixer->chip->dev, + usb_rcvctrlpipe(cval->mixer->chip->dev, 0), + bRequest, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, cval->mixer->ctrlif | (cval->id << 8), + buf, sizeof(buf), 1000); + + if (ret < 0) { + snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); + return ret; + } + + switch (request) { + case UAC_GET_CUR: + val = buf; + break; + case UAC_GET_MIN: + val = buf + sizeof(__u16); + break; + case UAC_GET_MAX: + val = buf + sizeof(__u16) * 2; + break; + case UAC_GET_RES: + val = buf + sizeof(__u16) * 3; + break; + default: + return -EINVAL; + } + + *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(val, sizeof(__u16))); + + return 0; +} + +static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +{ + return (cval->mixer->protocol == UAC_VERSION_1) ? + get_ctl_value_v1(cval, request, validx, value_ret) : + get_ctl_value_v2(cval, request, validx, value_ret); +} + static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value) { return get_ctl_value(cval, UAC_GET_CUR, validx, value); @@ -348,8 +402,7 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval, err = get_cur_mix_raw(cval, channel, value); if (err < 0) { if (!cval->mixer->ignore_ctl_error) - snd_printd(KERN_ERR "cannot get current value for " - "control %d ch %d: err = %d\n", + snd_printd(KERN_ERR "cannot get current value for control %d ch %d: err = %d\n", cval->control, channel, err); return err; } @@ -367,8 +420,22 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int value_set) { unsigned char buf[2]; - int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; - int timeout = 10; + int val_len, timeout = 10; + + if (cval->mixer->protocol == UAC_VERSION_1) { + val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; + } else { /* UAC_VERSION_2 */ + /* audio class v2 controls are always 2 bytes in size */ + val_len = sizeof(__u16); + + /* FIXME */ + if (request != UAC_SET_CUR) { + snd_printdd(KERN_WARNING "RANGE setting not yet supported\n"); + return -EINVAL; + } + + request = UAC2_CS_CUR; + } value_set = convert_bytes_value(cval, value_set); buf[0] = value_set & 0xff; @@ -564,46 +631,65 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm */ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) { - unsigned char *p1; + void *p1; memset(term, 0, sizeof(*term)); while ((p1 = find_audio_control_unit(state, id)) != NULL) { + unsigned char *hdr = p1; term->id = id; - switch (p1[2]) { + switch (hdr[2]) { case UAC_INPUT_TERMINAL: - term->type = combine_word(p1 + 4); - term->channels = p1[7]; - term->chconfig = combine_word(p1 + 8); - term->name = p1[11]; + if (state->mixer->protocol == UAC_VERSION_1) { + struct uac_input_terminal_descriptor *d = p1; + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le16_to_cpu(d->wChannelConfig); + term->name = d->iTerminal; + } else { /* UAC_VERSION_2 */ + struct uac2_input_terminal_descriptor *d = p1; + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le32_to_cpu(d->bmChannelConfig); + term->name = d->iTerminal; + } return 0; - case UAC_FEATURE_UNIT: - id = p1[4]; + case UAC_FEATURE_UNIT: { + /* the header is the same for v1 and v2 */ + struct uac_feature_unit_descriptor *d = p1; + id = d->bUnitID; break; /* continue to parse */ - case UAC_MIXER_UNIT: - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[5 + p1[4]]; - term->chconfig = combine_word(p1 + 6 + p1[4]); - term->name = p1[p1[0] - 1]; + } + case UAC_MIXER_UNIT: { + struct uac_mixer_unit_descriptor *d = p1; + term->type = d->bDescriptorSubtype << 16; /* virtual type */ + term->channels = uac_mixer_unit_bNrChannels(d); + term->chconfig = uac_mixer_unit_wChannelConfig(d, state->mixer->protocol); + term->name = uac_mixer_unit_iMixer(d); return 0; - case UAC_SELECTOR_UNIT: + } + case UAC_SELECTOR_UNIT: { + struct uac_selector_unit_descriptor *d = p1; /* call recursively to retrieve the channel info */ - if (check_input_term(state, p1[5], term) < 0) + if (check_input_term(state, d->baSourceID[0], term) < 0) return -ENODEV; - term->type = p1[2] << 16; /* virtual type */ + term->type = d->bDescriptorSubtype << 16; /* virtual type */ term->id = id; - term->name = p1[9 + p1[0] - 1]; + term->name = uac_selector_unit_iSelector(d); return 0; + } case UAC_PROCESSING_UNIT_V1: - case UAC_EXTENSION_UNIT_V1: - if (p1[6] == 1) { - id = p1[7]; + case UAC_EXTENSION_UNIT_V1: { + struct uac_processing_unit_descriptor *d = p1; + if (d->bNrInPins) { + id = d->baSourceID[0]; break; /* continue to parse */ } - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[7 + p1[6]]; - term->chconfig = combine_word(p1 + 8 + p1[6]); - term->name = p1[12 + p1[6] + p1[11 + p1[6]]]; + term->type = d->bDescriptorSubtype << 16; /* virtual type */ + term->channels = uac_processing_unit_bNrChannels(d); + term->chconfig = uac_processing_unit_wChannelConfig(d, state->mixer->protocol); + term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol); return 0; + } default: return -ENODEV; } @@ -850,6 +936,15 @@ static struct snd_kcontrol_new usb_feature_unit_ctl = { .put = mixer_ctl_feature_put, }; +/* the read-only variant */ +static struct snd_kcontrol_new usb_feature_unit_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later manually */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_feature_get, + .put = NULL, +}; + /* * build a feature control @@ -862,7 +957,8 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) static void build_feature_ctl(struct mixer_build *state, void *raw_desc, unsigned int ctl_mask, int control, - struct usb_audio_term *iterm, int unitid) + struct usb_audio_term *iterm, int unitid, + int read_only) { struct uac_feature_unit_descriptor *desc = raw_desc; unsigned int len = 0; @@ -906,7 +1002,11 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, /* get min/max values */ get_min_max(cval, 0); - kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + if (read_only) + kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval); + else + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + if (! kctl) { snd_printk(KERN_ERR "cannot malloc kcontrol\n"); kfree(cval); @@ -1016,24 +1116,34 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void struct usb_audio_term iterm; unsigned int master_bits, first_ch_bits; int err, csize; - struct uac_feature_unit_descriptor *ftr = _ftr; + struct uac_feature_unit_descriptor *hdr = _ftr; + __u8 *bmaControls; + + if (state->mixer->protocol == UAC_VERSION_1) { + csize = hdr->bControlSize; + channels = (hdr->bLength - 7) / csize - 1; + bmaControls = hdr->bmaControls; + } else { + struct uac2_feature_unit_descriptor *ftr = _ftr; + csize = 4; + channels = (hdr->bLength - 6) / 4; + bmaControls = ftr->bmaControls; + } - if (ftr->bLength < 7 || ! (csize = ftr->bControlSize) || ftr->bLength < 7 + csize) { + if (hdr->bLength < 7 || !csize || hdr->bLength < 7 + csize) { snd_printk(KERN_ERR "usbaudio: unit %u: invalid UAC_FEATURE_UNIT descriptor\n", unitid); return -EINVAL; } /* parse the source unit */ - if ((err = parse_audio_unit(state, ftr->bSourceID)) < 0) + if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0) return err; /* determine the input source type and name */ - if (check_input_term(state, ftr->bSourceID, &iterm) < 0) + if (check_input_term(state, hdr->bSourceID, &iterm) < 0) return -EINVAL; - channels = (ftr->bLength - 7) / csize - 1; - - master_bits = snd_usb_combine_bytes(ftr->bmaControls, csize); + master_bits = snd_usb_combine_bytes(bmaControls, csize); /* master configuration quirks */ switch (state->chip->usb_id) { case USB_ID(0x08bb, 0x2702): @@ -1044,21 +1154,54 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void break; } if (channels > 0) - first_ch_bits = snd_usb_combine_bytes(ftr->bmaControls + csize, csize); + first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize); else first_ch_bits = 0; - /* check all control types */ - for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; - for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(ftr->bmaControls + csize * (j+1), csize); - if (mask & (1 << i)) - ch_bits |= (1 << j); + + if (state->mixer->protocol == UAC_VERSION_1) { + /* check all control types */ + for (i = 0; i < 10; i++) { + unsigned int ch_bits = 0; + for (j = 0; j < channels; j++) { + unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); + if (mask & (1 << i)) + ch_bits |= (1 << j); + } + /* audio class v1 controls are never read-only */ + if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ + build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, 0); + if (master_bits & (1 << i)) + build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0); + } + } else { /* UAC_VERSION_2 */ + for (i = 0; i < 30/2; i++) { + /* From the USB Audio spec v2.0: + bmaControls() is a (ch+1)-element array of 4-byte bitmaps, + each containing a set of bit pairs. If a Control is present, + it must be Host readable. If a certain Control is not + present then the bit pair must be set to 0b00. + If a Control is present but read-only, the bit pair must be + set to 0b01. If a Control is also Host programmable, the bit + pair must be set to 0b11. The value 0b10 is not allowed. */ + unsigned int ch_bits = 0; + unsigned int ch_read_only = 0; + + for (j = 0; j < channels; j++) { + unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); + if (mask & (1 << (i * 2))) { + ch_bits |= (1 << j); + if (~mask & (1 << ((i * 2) + 1))) + ch_read_only |= (1 << j); + } + } + + /* FIXME: the whole unit is read-only if any of the channels is marked read-only */ + if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ + build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, !!ch_read_only); + if (master_bits & (1 << i * 2)) + build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, + ~master_bits & (1 << ((i * 2) + 1))); } - if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid); - if (master_bits & (1 << i)) - build_feature_ctl(state, _ftr, 0, i, &iterm, unitid); } return 0; @@ -1100,7 +1243,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, cval->control = in_ch + 1; /* based on 1 */ cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { - if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc), in_ch, i, num_outs)) { + if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), in_ch, i, num_outs)) { cval->cmask |= (1 << i); cval->channels++; } @@ -1164,7 +1307,7 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *r int och, ich_has_controls = 0; for (och = 0; och < num_outs; ++och) { - if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc), + if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), ich, och, num_outs)) { ich_has_controls = 1; break; @@ -1343,7 +1486,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw 0, NULL, default_value_info }; - if (desc->bLength < 13 || desc->bLength < 13 + num_ins || desc->bLength < num_ins + uac_processing_unit_bControlSize(desc)) { + if (desc->bLength < 13 || desc->bLength < 13 + num_ins || + desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) { snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid); return -EINVAL; } @@ -1361,7 +1505,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw info = &default_info; for (valinfo = info->values; valinfo->control; valinfo++) { - __u8 *controls = uac_processing_unit_bmControls(desc); + __u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol); if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; @@ -1381,7 +1525,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw /* get min/max values */ if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { - __u8 *control_spec = uac_processing_unit_specific(desc); + __u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol); /* FIXME: hard-coded */ cval->min = 1; cval->max = control_spec[0]; @@ -1414,7 +1558,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw else if (info->name) strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); else { - nameid = uac_processing_unit_iProcessing(desc); + nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); len = 0; if (nameid) len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); @@ -1676,9 +1820,17 @@ static int parse_audio_unit(struct mixer_build *state, int unitid) case UAC_FEATURE_UNIT: return parse_audio_feature_unit(state, unitid, p1); case UAC_PROCESSING_UNIT_V1: - return parse_audio_processing_unit(state, unitid, p1); + /* UAC2_EFFECT_UNIT has the same value */ + if (state->mixer->protocol == UAC_VERSION_1) + return parse_audio_processing_unit(state, unitid, p1); + else + return 0; /* FIXME - effect units not implemented yet */ case UAC_EXTENSION_UNIT_V1: - return parse_audio_extension_unit(state, unitid, p1); + /* UAC2_PROCESSING_UNIT_V2 has the same value */ + if (state->mixer->protocol == UAC_VERSION_1) + return parse_audio_extension_unit(state, unitid, p1); + else /* UAC_VERSION_2 */ + return parse_audio_processing_unit(state, unitid, p1); default: snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]); return -EINVAL; @@ -1711,11 +1863,11 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) */ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) { - struct uac_output_terminal_descriptor_v1 *desc; struct mixer_build state; int err; const struct usbmix_ctl_map *map; struct usb_host_interface *hostif; + void *p; hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; memset(&state, 0, sizeof(state)); @@ -1734,18 +1886,35 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } } - desc = NULL; - while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, UAC_OUTPUT_TERMINAL)) != NULL) { - if (desc->bLength < 9) - continue; /* invalid descriptor? */ - set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ - state.oterm.id = desc->bTerminalID; - state.oterm.type = le16_to_cpu(desc->wTerminalType); - state.oterm.name = desc->iTerminal; - err = parse_audio_unit(&state, desc->bSourceID); - if (err < 0) - return err; + p = NULL; + while ((p = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) { + if (mixer->protocol == UAC_VERSION_1) { + struct uac_output_terminal_descriptor_v1 *desc = p; + + if (desc->bLength < sizeof(*desc)) + continue; /* invalid descriptor? */ + set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0) + return err; + } else { /* UAC_VERSION_2 */ + struct uac2_output_terminal_descriptor *desc = p; + + if (desc->bLength < sizeof(*desc)) + continue; /* invalid descriptor? */ + set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0) + return err; + } } + return 0; } @@ -1868,7 +2037,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, struct usb_mixer_interface *mixer; struct snd_info_entry *entry; struct usb_host_interface *host_iface; - int err, protocol; + int err; strcpy(chip->card->mixername, "USB Mixer"); @@ -1886,14 +2055,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, } host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; - protocol = host_iface->desc.bInterfaceProtocol; - - /* FIXME! */ - if (protocol != UAC_VERSION_1) { - snd_printk(KERN_WARNING "mixer interface protocol 0x%02x not yet supported\n", - protocol); - return 0; - } + mixer->protocol = host_iface->desc.bInterfaceProtocol; if ((err = snd_usb_mixer_controls(mixer)) < 0 || (err = snd_usb_mixer_status_create(mixer)) < 0) diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 63101ae201c..130123854a6 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -10,6 +10,9 @@ struct usb_mixer_interface { /* array[MAX_ID_ELEMS], indexed by unit id */ struct usb_mixer_elem_info **id_elems; + /* the usb audio specification version this interface complies to */ + int protocol; + /* Sound Blaster remote control stuff */ const struct rc_config *rc_cfg; u32 rc_code; -- cgit v1.2.3-18-g5258 From b4452d1fbf3fe30435850d2971d700e0ab3852b6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 14 Mar 2010 22:05:40 +0000 Subject: ASoC: Remove version display from WM8750 Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8750.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 475c67ac781..c0c48750124 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -29,8 +29,6 @@ #include "wm8750.h" -#define WM8750_VERSION "0.12" - /* codec private data */ struct wm8750_priv { unsigned int sysclk; @@ -912,7 +910,6 @@ static int wm8750_probe(struct platform_device *pdev) struct wm8750_priv *wm8750; int ret; - pr_info("WM8750 Audio Codec %s", WM8750_VERSION); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; -- cgit v1.2.3-18-g5258 From e82f5cfa6389893e3e17f928d4c4f402b10c79eb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 18:28:45 +0000 Subject: ASoC: Remove unused 'muted' flag from DAPM widgets Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/soc-dapm.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 2c8eb0a331c..5915fc05984 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -425,7 +425,6 @@ struct snd_soc_dapm_widget { unsigned char connected:1; /* connected codec pin */ unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ - unsigned char muted:1; /* muted for pop reduction */ unsigned char suspend:1; /* was active before suspend */ int (*power_check)(struct snd_soc_dapm_widget *w); -- cgit v1.2.3-18-g5258 From da34183e640ed380f708bf9ebcd592afda619028 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 19:23:37 +0000 Subject: ASoC: Allow pins to be force enabled Allow pins to be forced on regardless of their power state. This is intended for use with microphone bias supplies which need to be enabled in order to support microphone detection - in systems without appropriate hardware leaving the microphone unbiased when not in use saves power. The force done at power check time in order to avoid disrupting other power detection logic. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/soc-dapm.h | 3 +++ sound/soc/soc-dapm.c | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 5915fc05984..d5d6ba862df 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -339,6 +339,8 @@ int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_sync(struct snd_soc_codec *codec); +int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, + const char *pin); /* dapm widget types */ enum snd_soc_dapm_type { @@ -426,6 +428,7 @@ struct snd_soc_dapm_widget { unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ unsigned char suspend:1; /* was active before suspend */ + unsigned char force:1; /* force state */ int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 86ded22e36a..bbb2135a0b2 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -979,7 +979,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) break; default: - power = w->power_check(w); + if (!w->force) + power = w->power_check(w); + else + power = 1; if (power) sys_power = 1; break; @@ -2133,6 +2136,36 @@ int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin) } EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); +/** + * snd_soc_dapm_force_enable_pin - force a pin to be enabled + * @codec: SoC codec + * @pin: pin name + * + * Enables input/output pin regardless of any other state. This is + * intended for use with microphone bias supplies used in microphone + * jack detection. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, const char *pin) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + pr_debug("dapm: %s: pin %s\n", codec->name, pin); + w->connected = 1; + w->force = 1; + return 0; + } + } + + pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin); + /** * snd_soc_dapm_disable_pin - disable pin. * @codec: SoC codec -- cgit v1.2.3-18-g5258 From 73b34ead7429789f35eea147a3e185abd61c7d94 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 17:46:02 +0000 Subject: ASoC: Add GPIO configuration support for WM8903 Allow users to pass in a default configuration for the GPIOs of the WM8903 as platform data. This allows configuration of the pin muxing of the device. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8903.h | 216 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8903.c | 13 ++- sound/soc/codecs/wm8903.h | 195 ----------------------------------------- 3 files changed, 228 insertions(+), 196 deletions(-) create mode 100644 include/sound/wm8903.h diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h new file mode 100644 index 00000000000..00458589fe8 --- /dev/null +++ b/include/sound/wm8903.h @@ -0,0 +1,216 @@ +/* + * linux/sound/wm8903.h -- Platform data for WM8903 + * + * Copyright 2010 Wolfson Microelectronics. PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_WM8903_H +#define __LINUX_SND_WM8903_H + +/* Used to enable configuration of a GPIO to all zeros */ +#define WM8903_GPIO_NO_CONFIG 0x8000 + +/* + * R116 (0x74) - GPIO Control 1 + */ +#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ +#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ +#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ +#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ +#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8903_GP1_PD 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ +#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8903_GP1_PU 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ +#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ +#define WM8903_GP1_DB 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ +#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ + +/* + * R117 (0x75) - GPIO Control 2 + */ +#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ +#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ +#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ +#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ +#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8903_GP2_PD 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ +#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8903_GP2_PU 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ +#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ +#define WM8903_GP2_DB 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ +#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ + +/* + * R118 (0x76) - GPIO Control 3 + */ +#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ +#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ +#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ +#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ +#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8903_GP3_PD 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ +#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8903_GP3_PU 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ +#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ +#define WM8903_GP3_DB 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ +#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ + +/* + * R119 (0x77) - GPIO Control 4 + */ +#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ +#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ +#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ +#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ +#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8903_GP4_PD 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ +#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8903_GP4_PU 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ +#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ +#define WM8903_GP4_DB 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ +#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ + +/* + * R120 (0x78) - GPIO Control 5 + */ +#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ +#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ +#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ +#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ +#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8903_GP5_PD 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ +#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8903_GP5_PU 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ +#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ +#define WM8903_GP5_DB 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ +#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ + +struct wm8903_platform_data { + u32 gpio_cfg[5]; /* Default register values for GPIO pin mux */ +}; + +#endif diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 3595bd57c4e..36952d77c27 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1531,7 +1531,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, { struct wm8903_priv *wm8903; struct snd_soc_codec *codec; - int ret; + int ret, i; u16 val; wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); @@ -1578,6 +1578,17 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, wm8903_reset(codec); + /* Set up GPIOs */ + if (pdata) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { + if (!pdata->gpio_cfg[i]) + continue; + + snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, + pdata->gpio_cfg[i] & 0xffff); + } + } + /* power on device */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index 0ea27e2b996..551736cf219 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -1134,201 +1134,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8903; #define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ #define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ -/* - * R116 (0x74) - GPIO Control 1 - */ -#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ -#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ -#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ -#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ -#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ -#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ -#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ -#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ -#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ -#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ -#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ -#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ -#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ -#define WM8903_GP1_PD 0x0008 /* GP1_PD */ -#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ -#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ -#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ -#define WM8903_GP1_PU 0x0004 /* GP1_PU */ -#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ -#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ -#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ -#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ -#define WM8903_GP1_DB 0x0001 /* GP1_DB */ -#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ -#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ -#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ - -/* - * R117 (0x75) - GPIO Control 2 - */ -#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ -#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ -#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ -#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ -#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ -#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ -#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ -#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ -#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ -#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ -#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ -#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ -#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ -#define WM8903_GP2_PD 0x0008 /* GP2_PD */ -#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ -#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ -#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ -#define WM8903_GP2_PU 0x0004 /* GP2_PU */ -#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ -#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ -#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ -#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ -#define WM8903_GP2_DB 0x0001 /* GP2_DB */ -#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ -#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ -#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ - -/* - * R118 (0x76) - GPIO Control 3 - */ -#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ -#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ -#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ -#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ -#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ -#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ -#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ -#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ -#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ -#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ -#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ -#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ -#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ -#define WM8903_GP3_PD 0x0008 /* GP3_PD */ -#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ -#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ -#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ -#define WM8903_GP3_PU 0x0004 /* GP3_PU */ -#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ -#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ -#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ -#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ -#define WM8903_GP3_DB 0x0001 /* GP3_DB */ -#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ -#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ -#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ - -/* - * R119 (0x77) - GPIO Control 4 - */ -#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ -#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ -#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ -#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ -#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ -#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ -#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ -#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ -#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ -#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ -#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ -#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ -#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ -#define WM8903_GP4_PD 0x0008 /* GP4_PD */ -#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ -#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ -#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ -#define WM8903_GP4_PU 0x0004 /* GP4_PU */ -#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ -#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ -#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ -#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ -#define WM8903_GP4_DB 0x0001 /* GP4_DB */ -#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ -#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ -#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ - -/* - * R120 (0x78) - GPIO Control 5 - */ -#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ -#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ -#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ -#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ -#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ -#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ -#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ -#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ -#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ -#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ -#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ -#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ -#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ -#define WM8903_GP5_PD 0x0008 /* GP5_PD */ -#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ -#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ -#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ -#define WM8903_GP5_PU 0x0004 /* GP5_PU */ -#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ -#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ -#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ -#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ -#define WM8903_GP5_DB 0x0001 /* GP5_DB */ -#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ -#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ -#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ - /* * R121 (0x79) - Interrupt Status 1 */ -- cgit v1.2.3-18-g5258 From 37f88e8407f75fc6ced5cefb633c314556de3ad1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 18:14:34 +0000 Subject: ASoC: Initial WM8903 microphone bias and short detection Provide support for WM8903 microphone presence and short detection using the GPIOs to route out a logic signal suitable for handling using snd_soc_jack_add_gpios() on the processor GPIOs. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8903.h | 29 +++++++++++++++++++++++++++++ sound/soc/codecs/wm8903.c | 24 ++++++++++++++++-------- sound/soc/codecs/wm8903.h | 22 ---------------------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h index 00458589fe8..22b66a76feb 100644 --- a/include/sound/wm8903.h +++ b/include/sound/wm8903.h @@ -14,6 +14,28 @@ /* Used to enable configuration of a GPIO to all zeros */ #define WM8903_GPIO_NO_CONFIG 0x8000 +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + /* * R116 (0x74) - GPIO Control 1 */ @@ -210,6 +232,13 @@ #define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ struct wm8903_platform_data { + /* Default register value for R6 (Mic bias), used to configure + * microphone detection. In conjunction with gpio_cfg this + * can be used to route the microphone status signals out onto + * the GPIOs for use with snd_soc_jack_add_gpios(). + */ + u16 micdet_cfg; + u32 gpio_cfg[5]; /* Default register values for GPIO pin mux */ }; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 36952d77c27..467e6c335c5 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -11,7 +11,6 @@ * * TODO: * - TDM mode configuration. - * - Mic detect. * - Digital microphone support. * - Interrupt support (mic detect and sequencer). */ @@ -246,10 +245,10 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) BUG_ON(start > 48); - /* Enable the sequencer */ + /* Enable the sequencer if it's not already on */ reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); - reg[0] |= WM8903_WSEQ_ENA; - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, + reg[0] | WM8903_WSEQ_ENA); dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); @@ -268,9 +267,8 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) dev_dbg(&i2c->dev, "Sequence complete\n"); - /* Disable the sequencer again */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, - reg[0] & ~WM8903_WSEQ_ENA); + /* Disable the sequencer again if we enabled it */ + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); return 0; } @@ -1578,7 +1576,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, wm8903_reset(codec); - /* Set up GPIOs */ + /* Set up GPIOs and microphone detection */ if (pdata) { for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { if (!pdata->gpio_cfg[i]) @@ -1587,6 +1585,16 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, pdata->gpio_cfg[i] & 0xffff); } + + snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, + pdata->micdet_cfg); + + /* Microphone detection needs the WSEQ clock */ + if (pdata->micdet_cfg) + snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + + wm8903->mic_delay = pdata->micdet_delay; } /* power on device */ diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index 551736cf219..8f19a241378 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -172,28 +172,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8903; #define WM8903_VMID_RES_250K 3 #define WM8903_VMID_RES_5K 4 -/* - * R6 (0x06) - Mic Bias Control 0 - */ -#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ -#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ -#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ -#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ -#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ -#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ -#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ -#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ - /* * R8 (0x08) - Analogue DAC 0 */ -- cgit v1.2.3-18-g5258 From 8abd16a65d81756706016720e2cc7eeb81d06a2e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 18:25:26 +0000 Subject: ASoC: Add WM8903 interrupt support Currently used to detect completion of the write sequencer. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8903.h | 2 ++ sound/soc/codecs/wm8903.c | 66 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h index 22b66a76feb..49bb1a844bf 100644 --- a/include/sound/wm8903.h +++ b/include/sound/wm8903.h @@ -232,6 +232,8 @@ #define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ struct wm8903_platform_data { + bool irq_active_low; /* Set if IRQ active low, default high */ + /* Default register value for R6 (Mic bias), used to configure * microphone detection. In conjunction with gpio_cfg this * can be used to route the microphone status signals out onto diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 467e6c335c5..7aa2adbe6fb 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -12,12 +12,12 @@ * TODO: * - TDM mode configuration. * - Digital microphone support. - * - Interrupt support (mic detect and sequencer). */ #include #include #include +#include #include #include #include @@ -29,6 +29,7 @@ #include #include #include +#include #include "wm8903.h" @@ -220,6 +221,8 @@ struct wm8903_priv { int playback_active; int capture_active; + struct completion wseq; + struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; }; @@ -242,6 +245,7 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) { u16 reg[5]; struct i2c_client *i2c = codec->control_data; + struct wm8903_priv *wm8903 = codec->private_data; BUG_ON(start > 48); @@ -256,11 +260,11 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) start | WM8903_WSEQ_START); /* Wait for it to complete. If we have the interrupt wired up then - * we could block waiting for an interrupt, though polling may still - * be desirable for diagnostic purposes. + * that will break us out of the poll early. */ do { - msleep(10); + wait_for_completion_timeout(&wm8903->wseq, + msecs_to_jiffies(10)); reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); } while (reg[4] & WM8903_WSEQ_BUSY); @@ -1433,6 +1437,22 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, return 0; } +static irqreturn_t wm8903_irq(int irq, void *data) +{ + struct wm8903_priv *wm8903 = data; + struct snd_soc_codec *codec = &wm8903->codec; + int reg; + + reg = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1); + + if (reg & WM8903_WSEQ_BUSY_EINT) { + dev_dbg(codec->dev, "Write sequencer done\n"); + complete(&wm8903->wseq); + } + + return IRQ_HANDLED; +} + #define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ SNDRV_PCM_RATE_11025 | \ SNDRV_PCM_RATE_16000 | \ @@ -1527,9 +1547,11 @@ static struct snd_soc_codec *wm8903_codec; static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct wm8903_platform_data *pdata = dev_get_platdata(&i2c->dev); struct wm8903_priv *wm8903; struct snd_soc_codec *codec; int ret, i; + int trigger, irq_pol; u16 val; wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); @@ -1553,6 +1575,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->reg_cache = &wm8903->reg_cache[0]; codec->private_data = wm8903; codec->volatile_register = wm8903_volatile_register; + init_completion(&wm8903->wseq); i2c_set_clientdata(i2c, codec); codec->control_data = i2c; @@ -1596,6 +1619,32 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, wm8903->mic_delay = pdata->micdet_delay; } + + if (i2c->irq) { + if (pdata && pdata->irq_active_low) { + trigger = IRQF_TRIGGER_LOW; + irq_pol = WM8903_IRQ_POL; + } else { + trigger = IRQF_TRIGGER_HIGH; + irq_pol = 0; + } + + snd_soc_update_bits(codec, WM8903_INTERRUPT_CONTROL, + WM8903_IRQ_POL, irq_pol); + + ret = request_threaded_irq(i2c->irq, NULL, wm8903_irq, + trigger | IRQF_ONESHOT, + "wm8903", wm8903); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request IRQ: %d\n", + ret); + goto err; + } + + /* Enable write sequencer interrupts */ + snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_IM_WSEQ_BUSY_EINT, 0); + } /* power on device */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1637,7 +1686,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_irq; } ret = snd_soc_register_dai(&wm8903_dai); @@ -1650,6 +1699,9 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, err_codec: snd_soc_unregister_codec(codec); +err_irq: + if (i2c->irq) + free_irq(i2c->irq, wm8903); err: wm8903_codec = NULL; kfree(wm8903); @@ -1659,12 +1711,16 @@ err: static __devexit int wm8903_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct wm8903_priv *priv = codec->private_data; snd_soc_unregister_dai(&wm8903_dai); snd_soc_unregister_codec(codec); wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (client->irq) + free_irq(client->irq, priv); + kfree(codec->private_data); wm8903_codec = NULL; -- cgit v1.2.3-18-g5258 From 7245387e36e16ae918467685c34510606fd74b7c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 15 Mar 2010 21:22:58 +0000 Subject: ASoC: Implement interrupt driven microphone detection for WM8903 Support use of the WM8903 IRQ for reporting of microphone presence and short detection. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8903.h | 2 + sound/soc/codecs/wm8903.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/wm8903.h | 4 ++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h index 49bb1a844bf..b4a0db2307e 100644 --- a/include/sound/wm8903.h +++ b/include/sound/wm8903.h @@ -241,6 +241,8 @@ struct wm8903_platform_data { */ u16 micdet_cfg; + int micdet_delay; /* Delay after microphone detection (ms) */ + u32 gpio_cfg[5]; /* Default register values for GPIO pin mux */ }; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 7aa2adbe6fb..b5427b47d6f 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -223,6 +224,12 @@ struct wm8903_priv { struct completion wseq; + struct snd_soc_jack *mic_jack; + int mic_det; + int mic_short; + int mic_last_report; + int mic_delay; + struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; }; @@ -1437,19 +1444,112 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, return 0; } +/** + * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ + * + * @codec: WM8903 codec + * @jack: jack to report detection events on + * @det: value to report for presence detection + * @shrt: value to report for short detection + * + * Enable microphone detection via IRQ on the WM8903. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8903 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * The current threasholds for detection should be configured using + * micdet_cfg in the platform data. Using this function will force on + * the microphone bias for the device. + */ +int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int det, int shrt) +{ + struct wm8903_priv *wm8903 = codec->private_data; + int irq_mask = 0; + + dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n", + det, shrt); + + /* Store the configuration */ + wm8903->mic_jack = jack; + wm8903->mic_det = det; + wm8903->mic_short = shrt; + + /* Enable interrupts we've got a report configured for */ + if (det) + irq_mask &= ~WM8903_MICDET_EINT; + if (shrt) + irq_mask &= ~WM8903_MICSHRT_EINT; + + snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_MICDET_EINT | WM8903_MICSHRT_EINT, + irq_mask); + + /* Enable mic detection, this may not have been set through + * platform data (eg, if the defaults are OK). */ + snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, WM8903_MICDET_ENA); + + /* Force the microphone bias on; this will trigger an initial + * detection. */ + snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8903_mic_detect); + static irqreturn_t wm8903_irq(int irq, void *data) { struct wm8903_priv *wm8903 = data; struct snd_soc_codec *codec = &wm8903->codec; - int reg; + int mic_report; + int int_pol; + int int_val = 0; + int mask = ~snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1_MASK); - reg = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1); + int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask; - if (reg & WM8903_WSEQ_BUSY_EINT) { + if (int_val & WM8903_WSEQ_BUSY_EINT) { dev_dbg(codec->dev, "Write sequencer done\n"); complete(&wm8903->wseq); } + /* + * The rest is microphone jack detection. We need to manually + * invert the polarity of the interrupt after each event - to + * simplify the code keep track of the last state we reported + * and just invert the relevant bits in both the report and + * the polarity register. + */ + mic_report = wm8903->mic_last_report; + int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1); + + if (int_val & WM8903_MICSHRT_EINT) { + dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_short; + int_pol ^= WM8903_MICSHRT_INV; + } + + if (int_val & WM8903_MICDET_EINT) { + dev_dbg(codec->dev, "Microphone detect (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_det; + int_pol ^= WM8903_MICDET_INV; + + msleep(wm8903->mic_delay); + } + + snd_soc_update_bits(codec, WM8903_INTERRUPT_POLARITY_1, + WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol); + + snd_soc_jack_report(wm8903->mic_jack, mic_report, + wm8903->mic_short | wm8903->mic_det); + + wm8903->mic_last_report = mic_report; + return IRQ_HANDLED; } diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index 8f19a241378..ce384a2ad82 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -18,6 +18,10 @@ extern struct snd_soc_dai wm8903_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8903; +extern int wm8903_mic_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int det, int shrt); + #define WM8903_MCLK_DIV_2 1 #define WM8903_CLK_SYS 2 #define WM8903_BCLK 3 -- cgit v1.2.3-18-g5258 From cdce4e9ba7ff86a5f43b971a9af88b25a3f6c9a4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 16 Mar 2010 12:01:28 +0000 Subject: ASoC: Allow configuration of WM8904 GPIO pin functions Provide platform data allowing the configuration of the GPIO pins on the WM8904 to be selected, allowing alternate functions to be enabled. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8904.h | 74 +++++++++++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/wm8904.c | 12 ++++++++ sound/soc/codecs/wm8904.h | 64 ---------------------------------------- 3 files changed, 84 insertions(+), 66 deletions(-) diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h index d66575a601b..ddeeebf7c42 100644 --- a/include/sound/wm8904.h +++ b/include/sound/wm8904.h @@ -15,8 +15,76 @@ #ifndef __MFD_WM8994_PDATA_H__ #define __MFD_WM8994_PDATA_H__ -#define WM8904_DRC_REGS 4 -#define WM8904_EQ_REGS 25 +/* Used to enable configuration of a GPIO to all zeros */ +#define WM8904_GPIO_NO_CONFIG 0x8000 + +/* + * R121 (0x79) - GPIO Control 1 + */ +#define WM8904_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ +#define WM8904_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ +#define WM8904_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ +#define WM8904_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ +#define WM8904_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ + +/* + * R122 (0x7A) - GPIO Control 2 + */ +#define WM8904_GPIO2_PU 0x0020 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_MASK 0x0020 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_SHIFT 5 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_WIDTH 1 /* GPIO2_PU */ +#define WM8904_GPIO2_PD 0x0010 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_MASK 0x0010 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_SHIFT 4 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_WIDTH 1 /* GPIO2_PD */ +#define WM8904_GPIO2_SEL_MASK 0x000F /* GPIO2_SEL - [3:0] */ +#define WM8904_GPIO2_SEL_SHIFT 0 /* GPIO2_SEL - [3:0] */ +#define WM8904_GPIO2_SEL_WIDTH 4 /* GPIO2_SEL - [3:0] */ + +/* + * R123 (0x7B) - GPIO Control 3 + */ +#define WM8904_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_MASK 0x0020 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_SHIFT 5 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_WIDTH 1 /* GPIO3_PU */ +#define WM8904_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_MASK 0x0010 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_SHIFT 4 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_WIDTH 1 /* GPIO3_PD */ +#define WM8904_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ +#define WM8904_GPIO3_SEL_SHIFT 0 /* GPIO3_SEL - [3:0] */ +#define WM8904_GPIO3_SEL_WIDTH 4 /* GPIO3_SEL - [3:0] */ + +/* + * R124 (0x7C) - GPIO Control 4 + */ +#define WM8904_GPI7_ENA 0x0200 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_MASK 0x0200 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_SHIFT 9 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ +#define WM8904_GPI8_ENA 0x0100 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_MASK 0x0100 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_SHIFT 8 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA 0x0080 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_MASK 0x0080 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_SHIFT 7 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_WIDTH 1 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_SEL_MASK 0x000F /* GPIO_BCLK_SEL - [3:0] */ +#define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */ +#define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */ + +#define WM8904_GPIO_REGS 4 +#define WM8904_DRC_REGS 4 +#define WM8904_EQ_REGS 25 /** * DRC configurations are specified with a label and a set of register @@ -52,6 +120,8 @@ struct wm8904_pdata { int num_retune_mobile_cfgs; struct wm8904_retune_mobile_cfg *retune_mobile_cfgs; + + u32 gpio_cfg[WM8904_GPIO_REGS]; }; #endif diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 593e47d0e0e..ce3f004217a 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2425,6 +2425,7 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_wm8904); static int wm8904_register(struct wm8904_priv *wm8904, enum snd_soc_control_type control) { + struct wm8904_pdata *pdata = wm8904->pdata; int ret; struct snd_soc_codec *codec = &wm8904->codec; int i; @@ -2530,6 +2531,17 @@ static int wm8904_register(struct wm8904_priv *wm8904, WM8904_LINEOUTRZC; wm8904->reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE; + /* Apply configuration from the platform data. */ + if (wm8904->pdata) { + for (i = 0; i < WM8904_GPIO_REGS; i++) { + if (!pdata->gpio_cfg[i]) + continue; + + wm8904->reg_cache[WM8904_GPIO_CONTROL_1 + i] + = pdata->gpio_cfg[i] & 0xffff; + } + } + /* Set Class W by default - this will be managed by the Class * G widget at runtime where bypass paths are available. */ diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h index b68886df34e..10603a75c1a 100644 --- a/sound/soc/codecs/wm8904.h +++ b/sound/soc/codecs/wm8904.h @@ -1199,70 +1199,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8904; #define WM8904_FLL_CLK_REF_SRC_SHIFT 0 /* FLL_CLK_REF_SRC - [1:0] */ #define WM8904_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */ -/* - * R121 (0x79) - GPIO Control 1 - */ -#define WM8904_GPIO1_PU 0x0020 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ -#define WM8904_GPIO1_PD 0x0010 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ -#define WM8904_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ -#define WM8904_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ -#define WM8904_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ - -/* - * R122 (0x7A) - GPIO Control 2 - */ -#define WM8904_GPIO2_PU 0x0020 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_MASK 0x0020 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_SHIFT 5 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_WIDTH 1 /* GPIO2_PU */ -#define WM8904_GPIO2_PD 0x0010 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_MASK 0x0010 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_SHIFT 4 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_WIDTH 1 /* GPIO2_PD */ -#define WM8904_GPIO2_SEL_MASK 0x000F /* GPIO2_SEL - [3:0] */ -#define WM8904_GPIO2_SEL_SHIFT 0 /* GPIO2_SEL - [3:0] */ -#define WM8904_GPIO2_SEL_WIDTH 4 /* GPIO2_SEL - [3:0] */ - -/* - * R123 (0x7B) - GPIO Control 3 - */ -#define WM8904_GPIO3_PU 0x0020 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_MASK 0x0020 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_SHIFT 5 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_WIDTH 1 /* GPIO3_PU */ -#define WM8904_GPIO3_PD 0x0010 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_MASK 0x0010 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_SHIFT 4 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_WIDTH 1 /* GPIO3_PD */ -#define WM8904_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ -#define WM8904_GPIO3_SEL_SHIFT 0 /* GPIO3_SEL - [3:0] */ -#define WM8904_GPIO3_SEL_WIDTH 4 /* GPIO3_SEL - [3:0] */ - -/* - * R124 (0x7C) - GPIO Control 4 - */ -#define WM8904_GPI7_ENA 0x0200 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_MASK 0x0200 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_SHIFT 9 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ -#define WM8904_GPI8_ENA 0x0100 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_MASK 0x0100 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_SHIFT 8 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA 0x0080 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_MASK 0x0080 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_SHIFT 7 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_WIDTH 1 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_SEL_MASK 0x000F /* GPIO_BCLK_SEL - [3:0] */ -#define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */ -#define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */ - /* * R126 (0x7E) - Digital Pulls */ -- cgit v1.2.3-18-g5258 From fbc2dae854da5fc44e2ce2f97446c8bd283c7e17 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 16 Mar 2010 14:26:45 +0000 Subject: ASoC: Support GPIO based microphone detection for WM8904 The WM8904 allows microphone detection signals to be brought out as alternate functions of the GPIO signals which can be detected using interrupt inputs on the CPU. Allow this to be configured using platform data. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8904.h | 36 ++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8904.c | 5 +++++ sound/soc/codecs/wm8904.h | 33 --------------------------------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h index ddeeebf7c42..898be3a8db9 100644 --- a/include/sound/wm8904.h +++ b/include/sound/wm8904.h @@ -18,6 +18,40 @@ /* Used to enable configuration of a GPIO to all zeros */ #define WM8904_GPIO_NO_CONFIG 0x8000 +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8904_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8904_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8904_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8904_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8904_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8904_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8904_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8904_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R7 (0x07) - Mic Bias Control 1 + */ +#define WM8904_MIC_DET_FILTER_ENA 0x8000 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_MASK 0x8000 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_SHIFT 15 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_WIDTH 1 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA 0x4000 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_MASK 0x4000 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_SHIFT 14 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_WIDTH 1 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MICBIAS_SEL_MASK 0x0007 /* MICBIAS_SEL - [2:0] */ +#define WM8904_MICBIAS_SEL_SHIFT 0 /* MICBIAS_SEL - [2:0] */ +#define WM8904_MICBIAS_SEL_WIDTH 3 /* MICBIAS_SEL - [2:0] */ + + /* * R121 (0x79) - GPIO Control 1 */ @@ -82,6 +116,7 @@ #define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */ #define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */ +#define WM8904_MIC_REGS 2 #define WM8904_GPIO_REGS 4 #define WM8904_DRC_REGS 4 #define WM8904_EQ_REGS 25 @@ -122,6 +157,7 @@ struct wm8904_pdata { struct wm8904_retune_mobile_cfg *retune_mobile_cfgs; u32 gpio_cfg[WM8904_GPIO_REGS]; + u32 mic_cfg[WM8904_MIC_REGS]; }; #endif diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index ce3f004217a..c5b50d73f2d 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2540,6 +2540,11 @@ static int wm8904_register(struct wm8904_priv *wm8904, wm8904->reg_cache[WM8904_GPIO_CONTROL_1 + i] = pdata->gpio_cfg[i] & 0xffff; } + + /* Zero is the default value for these anyway */ + for (i = 0; i < WM8904_MIC_REGS; i++) + wm8904->reg_cache[WM8904_MIC_BIAS_CONTROL_0 + i] + = pdata->mic_cfg[i]; } /* Set Class W by default - this will be managed by the Class diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h index 10603a75c1a..abe5059b300 100644 --- a/sound/soc/codecs/wm8904.h +++ b/sound/soc/codecs/wm8904.h @@ -185,39 +185,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8904; #define WM8904_VMID_ENA_SHIFT 0 /* VMID_ENA */ #define WM8904_VMID_ENA_WIDTH 1 /* VMID_ENA */ -/* - * R6 (0x06) - Mic Bias Control 0 - */ -#define WM8904_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ -#define WM8904_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ -#define WM8904_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ -#define WM8904_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ -#define WM8904_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ -#define WM8904_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ -#define WM8904_MICDET_ENA 0x0002 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ -#define WM8904_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ - -/* - * R7 (0x07) - Mic Bias Control 1 - */ -#define WM8904_MIC_DET_FILTER_ENA 0x8000 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_MASK 0x8000 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_SHIFT 15 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_WIDTH 1 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA 0x4000 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_MASK 0x4000 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_SHIFT 14 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_WIDTH 1 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MICBIAS_SEL_MASK 0x0007 /* MICBIAS_SEL - [2:0] */ -#define WM8904_MICBIAS_SEL_SHIFT 0 /* MICBIAS_SEL - [2:0] */ -#define WM8904_MICBIAS_SEL_WIDTH 3 /* MICBIAS_SEL - [2:0] */ - /* * R8 (0x08) - Analogue DAC 0 */ -- cgit v1.2.3-18-g5258 From 2a0761a35b70f170a9c14ecbcb6a959fb6e7bdf3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 16 Mar 2010 15:54:12 +0000 Subject: ASoC: Implement WM835x microphone jack detection support The WM8350 provides microphone presence and short circuit detection. Integrate this with the ASoC jack reporting API. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/linux/mfd/wm8350/audio.h | 2 ++ sound/soc/codecs/wm8350.c | 58 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8350.h | 3 +++ 3 files changed, 63 insertions(+) diff --git a/include/linux/mfd/wm8350/audio.h b/include/linux/mfd/wm8350/audio.h index d899dc0223b..a95141eafce 100644 --- a/include/linux/mfd/wm8350/audio.h +++ b/include/linux/mfd/wm8350/audio.h @@ -492,6 +492,8 @@ */ #define WM8350_JACK_L_LVL 0x0800 #define WM8350_JACK_R_LVL 0x0400 +#define WM8350_JACK_MICSCD_LVL 0x0200 +#define WM8350_JACK_MICSD_LVL 0x0100 /* * WM8350 Platform setup diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index df2c6d9617f..ff6c10155ee 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -54,6 +54,7 @@ struct wm8350_output { struct wm8350_jack_data { struct snd_soc_jack *jack; int report; + int short_report; }; struct wm8350_data { @@ -62,6 +63,7 @@ struct wm8350_data { struct wm8350_output out2; struct wm8350_jack_data hpl; struct wm8350_jack_data hpr; + struct wm8350_jack_data mic; struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; int fll_freq_out; int fll_freq_in; @@ -1430,6 +1432,55 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, } EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect); +static irqreturn_t wm8350_mic_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->codec.control_data; + u16 reg; + int report = 0; + + reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS); + if (reg & WM8350_JACK_MICSCD_LVL) + report |= priv->mic.short_report; + if (reg & WM8350_JACK_MICSD_LVL) + report |= priv->mic.report; + + snd_soc_jack_report(priv->mic.jack, report, + priv->mic.report | priv->mic.short_report); + + return IRQ_HANDLED; +} + +/** + * wm8350_mic_jack_detect - Enable microphone jack detection. + * + * @codec: WM8350 codec + * @jack: jack to report detection events on + * @detect_report: value to report when presence detected + * @short_report: value to report when microphone short detected + * + * Enables the microphone jack detection of the WM8350. + */ +int wm8350_mic_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int detect_report, int short_report) +{ + struct wm8350_data *priv = codec->private_data; + struct wm8350 *wm8350 = codec->control_data; + + priv->mic.jack = jack; + priv->mic.report = detect_report; + priv->mic.short_report = short_report; + + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, WM8350_MIC_DET_ENA); + + snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); + static struct snd_soc_codec *wm8350_codec; static int wm8350_probe(struct platform_device *pdev) @@ -1493,6 +1544,10 @@ static int wm8350_probe(struct platform_device *pdev) wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, wm8350_hp_jack_handler, 0, "Right jack detect", priv); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, + wm8350_mic_handler, 0, "Microphone short", priv); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD, + wm8350_mic_handler, 0, "Microphone detect", priv); ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1521,11 +1576,14 @@ static int wm8350_remove(struct platform_device *pdev) WM8350_JDL_ENA | WM8350_JDR_ENA); wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICD, priv); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv); wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv); wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv); priv->hpl.jack = NULL; priv->hpr.jack = NULL; + priv->mic.jack = NULL; /* cancel any work waiting to be queued. */ ret = cancel_delayed_work(&codec->delayed_work); diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h index d088eb4b88b..9ed0467c71d 100644 --- a/sound/soc/codecs/wm8350.h +++ b/sound/soc/codecs/wm8350.h @@ -25,5 +25,8 @@ enum wm8350_jack { int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report); +int wm8350_mic_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int detect_report, int short_report); #endif -- cgit v1.2.3-18-g5258 From a6c65736bc2e63392334bd800f1b2754ab55db1e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 3 Mar 2010 17:45:21 +0000 Subject: ASoC: Remove current PGA control handling A code audit reveals that there are currently no users of the widget controls on PGAs. This is likely to continue to be the case since while there are useful things that can be done with integrating the PGA gain and mute controls with the power sequencing userspace generally wants stereo controls for output stages which this doesn't map onto well. In preparation for implementing something more useful strip out the existing code, leaving the parameters there for use by the new code. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-dapm.c | 93 ++-------------------------------------------------- 1 file changed, 3 insertions(+), 90 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index bbb2135a0b2..61128419400 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -321,54 +321,6 @@ static int dapm_update_bits(struct snd_soc_dapm_widget *widget) return change; } -/* ramps the volume up or down to minimise pops before or after a - * DAPM power event */ -static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) -{ - const struct snd_kcontrol_new *k = widget->kcontrols; - - if (widget->muted && !power) - return 0; - if (!widget->muted && power) - return 0; - - if (widget->num_kcontrols && k) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)k->private_value; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - - if (power) { - int i; - /* power up has happended, increase volume to last level */ - if (invert) { - for (i = max; i > widget->saved_value; i--) - snd_soc_update_bits(widget->codec, reg, mask, i); - } else { - for (i = 0; i < widget->saved_value; i++) - snd_soc_update_bits(widget->codec, reg, mask, i); - } - widget->muted = 0; - } else { - /* power down is about to occur, decrease volume to mute */ - int val = snd_soc_read(widget->codec, reg); - int i = widget->saved_value = (val >> shift) & mask; - if (invert) { - for (; i < mask; i++) - snd_soc_update_bits(widget->codec, reg, mask, i); - } else { - for (; i > 0; i--) - snd_soc_update_bits(widget->codec, reg, mask, i); - } - widget->muted = 1; - } - } - return 0; -} - /* create new dapm mixer control */ static int dapm_new_mixer(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *w) @@ -463,20 +415,10 @@ err: static int dapm_new_pga(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *w) { - struct snd_kcontrol *kcontrol; - int ret = 0; - - if (!w->num_kcontrols) - return -EINVAL; + if (w->num_kcontrols) + pr_err("asoc: PGA controls not supported: '%s'\n", w->name); - kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name); - ret = snd_ctl_add(codec->card, kcontrol); - if (ret < 0) { - printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name); - return ret; - } - - return ret; + return 0; } /* reset 'walked' bit for each dapm path */ @@ -632,16 +574,8 @@ static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w) return ret; } - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !w->power) - dapm_set_pga(w, w->power); - dapm_update_bits(w); - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && w->power) - dapm_set_pga(w, w->power); - /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -808,10 +742,6 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pr_err("%s: pre event failed: %d\n", w->name, ret); } - - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !w->power) - dapm_set_pga(w, w->power); } if (reg >= 0) { @@ -823,10 +753,6 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, } list_for_each_entry(w, pending, power_list) { - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && w->power) - dapm_set_pga(w, w->power); - /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -1596,12 +1522,6 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, unsigned int invert = mc->invert; unsigned int mask = (1 << fls(max)) - 1; - /* return the saved value if we are powered down */ - if (widget->id == snd_soc_dapm_pga && !widget->power) { - ucontrol->value.integer.value[0] = widget->saved_value; - return 0; - } - ucontrol->value.integer.value[0] = (snd_soc_read(widget->codec, reg) >> shift) & mask; if (shift != rshift) @@ -1661,13 +1581,6 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - /* save volume value if the widget is powered down */ - if (widget->id == snd_soc_dapm_pga && !widget->power) { - widget->saved_value = val; - mutex_unlock(&widget->codec->mutex); - return 1; - } - if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) { if (val) /* new connection */ -- cgit v1.2.3-18-g5258 From dd76769dd53bdb804a4b02a3eb256f4740f78720 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Mar 2010 11:51:33 +0000 Subject: ASoC: Refresh WM8750 bias management The WM8750 is using some delayed work to manage the ramping of the bias at startup and resume out of line from the normal flow. This predates the support within ASoC core for moving the resume out of line from the main system resume which provides equivalent functionality with better interaction with applications. Change to doing the ramp in line to make use of the core functionality. Signed-off-by: Mark Brown Acked-by: Mark Brown --- sound/soc/codecs/wm8750.c | 50 +++++++++-------------------------------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index c0c48750124..ee084083a49 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -611,10 +611,16 @@ static int wm8750_set_bias_level(struct snd_soc_codec *codec, snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; case SND_SOC_BIAS_PREPARE: - /* set vmid to 5k for quick power up */ - snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Set VMID to 5k */ + snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); + + /* ...and ramp */ + msleep(1000); + } + /* mute dac and set vmid to 500k, enable VREF */ snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; @@ -658,13 +664,6 @@ struct snd_soc_dai wm8750_dai = { }; EXPORT_SYMBOL_GPL(wm8750_dai); -static void wm8750_work(struct work_struct *work) -{ - struct snd_soc_codec *codec = - container_of(work, struct snd_soc_codec, delayed_work.work); - wm8750_set_bias_level(codec, codec->bias_level); -} - static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -693,14 +692,6 @@ static int wm8750_resume(struct platform_device *pdev) wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* charge wm8750 caps */ - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { - wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->bias_level = SND_SOC_BIAS_ON; - schedule_delayed_work(&codec->delayed_work, - msecs_to_jiffies(1000)); - } - return 0; } @@ -744,9 +735,7 @@ static int wm8750_init(struct snd_soc_device *socdev, } /* charge output caps */ - wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->bias_level = SND_SOC_BIAS_STANDBY; - schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); + wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set the update bits */ reg = snd_soc_read(codec, WM8750_LDAC); @@ -926,7 +915,6 @@ static int wm8750_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); wm8750_socdev = socdev; - INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); ret = -ENODEV; @@ -950,25 +938,6 @@ static int wm8750_probe(struct platform_device *pdev) return ret; } -/* - * This function forces any delayed work to be queued and run. - */ -static int run_delayed_work(struct delayed_work *dwork) -{ - int ret; - - /* cancel any work waiting to be queued. */ - ret = cancel_delayed_work(dwork); - - /* if there was any work waiting then we run it now and - * wait for it's completion */ - if (ret) { - schedule_delayed_work(dwork, 0); - flush_scheduled_work(); - } - return ret; -} - /* power down chip */ static int wm8750_remove(struct platform_device *pdev) { @@ -977,7 +946,6 @@ static int wm8750_remove(struct platform_device *pdev) if (codec->control_data) wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); - run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) -- cgit v1.2.3-18-g5258 From 1c6e555c3a66af29a51690748ca54314e62c16d5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Mar 2010 15:36:38 +0000 Subject: ALSA: Rename jack switch table in preparation for button support Avoids confusion when we have button support. Signed-off-by: Mark Brown --- sound/core/jack.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sound/core/jack.c b/sound/core/jack.c index f705eec7372..f6f091f2b38 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -23,7 +23,7 @@ #include #include -static int jack_types[] = { +static int jack_switch_types[] = { SW_HEADPHONE_INSERT, SW_MICROPHONE_INSERT, SW_LINEOUT_INSERT, @@ -112,10 +112,10 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, jack->type = type; - for (i = 0; i < ARRAY_SIZE(jack_types); i++) + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) if (type & (1 << i)) input_set_capability(jack->input_dev, EV_SW, - jack_types[i]); + jack_switch_types[i]); err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops); if (err < 0) @@ -163,10 +163,11 @@ void snd_jack_report(struct snd_jack *jack, int status) if (!jack) return; - for (i = 0; i < ARRAY_SIZE(jack_types); i++) { + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { int testbit = 1 << i; if (jack->type & testbit) - input_report_switch(jack->input_dev, jack_types[i], + input_report_switch(jack->input_dev, + jack_switch_types[i], status & testbit); } -- cgit v1.2.3-18-g5258 From ebb812cb8df48e299b3d4ab75cbb0042384ef70d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Mar 2010 18:07:12 +0000 Subject: ALSA: Add support for key reporting via the jack interface Some devices provide support for detection of a small number of buttons on their jacks. One common implementation provides a single button, implemented by shorting the microphone to ground and detected along with microphone presence detection by detecting varying current draws on the microphone bias signal. Provide support for up to three buttons via the jack interface. These default to reporting BTN_n but an API is provided to allow these to be remapped to other keys by the machine driver where it knows what the keys are. More keys can be added with ease if required. This is only intended to support simple accessory button designs. If the interface is limiting then either creating a child device for the accessory or accessing the input device in the jack directly is recommended. Signed-off-by: Mark Brown --- include/sound/jack.h | 8 +++++++ sound/core/jack.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/include/sound/jack.h b/include/sound/jack.h index f236e426a70..d90b9fa3270 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -42,6 +42,11 @@ enum snd_jack_types { SND_JACK_MECHANICAL = 0x0008, /* If detected separately */ SND_JACK_VIDEOOUT = 0x0010, SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT, + + /* Kept separate from switches to facilitate implementation */ + SND_JACK_BTN_0 = 0x4000, + SND_JACK_BTN_1 = 0x2000, + SND_JACK_BTN_2 = 0x1000, }; struct snd_jack { @@ -50,6 +55,7 @@ struct snd_jack { int type; const char *id; char name[100]; + unsigned int key[3]; /* Keep in sync with definitions above */ void *private_data; void (*private_free)(struct snd_jack *); }; @@ -59,6 +65,8 @@ struct snd_jack { int snd_jack_new(struct snd_card *card, const char *id, int type, struct snd_jack **jack); void snd_jack_set_parent(struct snd_jack *jack, struct device *parent); +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype); void snd_jack_report(struct snd_jack *jack, int status); diff --git a/sound/core/jack.c b/sound/core/jack.c index f6f091f2b38..3813e7b04d0 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -55,7 +55,7 @@ static int snd_jack_dev_register(struct snd_device *device) { struct snd_jack *jack = device->device_data; struct snd_card *card = device->card; - int err; + int err, i; snprintf(jack->name, sizeof(jack->name), "%s %s", card->shortname, jack->id); @@ -65,6 +65,19 @@ static int snd_jack_dev_register(struct snd_device *device) if (!jack->input_dev->dev.parent) jack->input_dev->dev.parent = snd_card_get_device_link(card); + /* Add capabilities for any keys that are enabled */ + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (!(jack->type & testbit)) + continue; + + if (!jack->key[i]) + jack->key[i] = BTN_0 + i; + + input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); + } + err = input_register_device(jack->input_dev); if (err == 0) jack->registered = 1; @@ -150,6 +163,43 @@ void snd_jack_set_parent(struct snd_jack *jack, struct device *parent) } EXPORT_SYMBOL(snd_jack_set_parent); +/** + * snd_jack_set_key - Set a key mapping on a jack + * + * @jack: The jack to configure + * @type: Jack report type for this key + * @keytype: Input layer key type to be reported + * + * Map a SND_JACK_BTN_ button type to an input layer key, allowing + * reporting of keys on accessories via the jack abstraction. If no + * mapping is provided but keys are enabled in the jack type then + * BTN_n numeric buttons will be reported. + * + * Note that this is intended to be use by simple devices with small + * numbers of keys that can be reported. It is also possible to + * access the input device directly - devices with complex input + * capabilities on accessories should consider doing this rather than + * using this abstraction. + * + * This function may only be called prior to registration of the jack. + */ +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype) +{ + int key = fls(SND_JACK_BTN_0) - fls(type); + + WARN_ON(jack->registered); + + if (!keytype || key >= ARRAY_SIZE(jack->key)) + return -EINVAL; + + jack->type |= type; + jack->key[key] = keytype; + + return 0; +} +EXPORT_SYMBOL(snd_jack_set_key); + /** * snd_jack_report - Report the current status of a jack * @@ -163,6 +213,14 @@ void snd_jack_report(struct snd_jack *jack, int status) if (!jack) return; + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (jack->type & testbit) + input_report_key(jack->input_dev, jack->key[i], + status & testbit); + } + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { int testbit = 1 << i; if (jack->type & testbit) -- cgit v1.2.3-18-g5258 From 85dfcdffc227717fd218b9a86b507d9b7a3c321d Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Thu, 18 Mar 2010 16:17:00 +0800 Subject: ASoC: soc-cache: add i2c read entry for 8_8 mode Signed-off-by: Cliff Cai Signed-off-by: Barry Song Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-cache.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index bf593a834f5..91db7afb121 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -225,6 +225,40 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, } } +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec, + unsigned int r) +{ + struct i2c_msg xfer[2]; + u8 reg = r; + u8 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 1; + xfer[1].buf = &data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return data; +} +#else +#define snd_soc_8_8_read_i2c NULL +#endif + #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, unsigned int r) @@ -466,6 +500,7 @@ static struct { { .addr_bits = 8, .data_bits = 8, .write = snd_soc_8_8_write, .read = snd_soc_8_8_read, + .i2c_read = snd_soc_8_8_read_i2c, }, { .addr_bits = 8, .data_bits = 16, -- cgit v1.2.3-18-g5258 From f4bee1bb0083869b1b7d73c6eda7703a7aae0506 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Thu, 18 Mar 2010 16:17:01 +0800 Subject: ASoC: soc-cache: let reg be AND'ed by 0xff instead of data buffer for 8_8 mode The registers for AD193X are defined as 0x800-0x810 for spi which uses 16_8 mode, for i2c to support AD1937, we will use 8_8 mode, only the low byte of 0x800-0x810 is valid. The patch will not destory other codecs, but make soc cache interface more useful. Signed-off-by: Barry Song Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-cache.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 91db7afb121..9dfe9a58a31 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -159,7 +159,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, BUG_ON(codec->volatile_register); - data[0] = reg & 0xff; + reg &= 0xff; + data[0] = reg; data[1] = value & 0xff; if (reg < codec->reg_cache_size) @@ -180,6 +181,7 @@ static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, unsigned int reg) { u8 *cache = codec->reg_cache; + reg &= 0xff; if (reg >= codec->reg_cache_size) return -1; return cache[reg]; -- cgit v1.2.3-18-g5258 From 6a1c7b7e2ea3ca5f6ae73154c4b72755d262c9e0 Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Wed, 17 Mar 2010 17:42:29 -0500 Subject: OMAP4: PMIC: Rename twl6030_codec as twl6040_codec Correction for chips: twl6030 is Phoenix Power chip twl6040 is Phoenix Audio chip Signed-off-by: Margarita Olaya Cabrera Acked-by: Liam Girdwood Acked-by: Samuel Ortiz Signed-off-by: Mark Brown --- drivers/mfd/twl-core.c | 4 ++-- include/linux/i2c/twl.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 562cd4935e1..720e099e506 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -109,7 +109,7 @@ #endif #if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\ - defined(CONFIG_SND_SOC_TWL6030) || defined(CONFIG_SND_SOC_TWL6030_MODULE) + defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE) #define twl_has_codec() true #else #define twl_has_codec() false @@ -708,7 +708,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) /* Phoenix*/ if (twl_has_codec() && pdata->codec && twl_class_is_6030()) { sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; - child = add_child(sub_chip_id, "twl6030_codec", + child = add_child(sub_chip_id, "twl6040_codec", pdata->codec, sizeof(*pdata->codec), false, 0, 0); if (IS_ERR(child)) diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h index fb6784e86d5..ebd90ce58ca 100644 --- a/include/linux/i2c/twl.h +++ b/include/linux/i2c/twl.h @@ -569,9 +569,9 @@ struct twl4030_codec_data { struct twl4030_codec_audio_data *audio; struct twl4030_codec_vibra_data *vibra; - /* twl6030 */ - int audpwron_gpio; /* audio power-on gpio */ - int naudint_irq; /* audio interrupt */ + /* twl6040 */ + int audpwron_gpio; /* audio power-on gpio */ + int naudint_irq; /* audio interrupt */ }; struct twl4030_platform_data { -- cgit v1.2.3-18-g5258 From 6ff86a3f33e84c430aeb8ff3b7f68b1c052ec1e9 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Fri, 19 Mar 2010 11:14:36 +0100 Subject: ALSA: hda - Take internal mic as Front Mic Add new check for MIC. Do the internal DMIC as the Front MIC. It could solve the default record source index issue. [Fix the check properly using the bitmask by tiwai] Signed-off-by: Kailang Yang Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 0e76ac2b2ac..d72bcff8d27 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -4218,7 +4218,8 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, break; case AC_JACK_MIC_IN: { int preferred, alt; - if (loc == AC_JACK_LOC_FRONT) { + if (loc == AC_JACK_LOC_FRONT || + (loc & 0x30) == AC_JACK_LOC_INTERNAL) { preferred = AUTO_PIN_FRONT_MIC; alt = AUTO_PIN_MIC; } else { -- cgit v1.2.3-18-g5258 From da00c24493bf6ae3772dfe7343dca033ebc75955 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Fri, 19 Mar 2010 11:23:45 +0100 Subject: ALSA: hda - Add parse customize define function for Realtek codecs Added alc_auto_parse_customize_define() to parse the Realtek-specific attributes from SKU. Also enable beep controls only when the proper attribute bit is set. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 112 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 16 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 4ec57633af8..245e1afa589 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -275,6 +275,18 @@ struct alc_mic_route { #define MUX_IDX_UNDEF ((unsigned char)-1) +struct alc_customize_define { + unsigned int sku_cfg; + unsigned char port_connectivity; + unsigned char check_sum; + unsigned char customization; + unsigned char external_amp; + unsigned int enable_pcbeep:1; + unsigned int platform_type:1; + unsigned int swap:1; + unsigned int override:1; +}; + struct alc_spec { /* codec parameterization */ struct snd_kcontrol_new *mixers[5]; /* mixer arrays */ @@ -332,6 +344,7 @@ struct alc_spec { /* dynamic controls, init_verbs and input_mux */ struct auto_pin_cfg autocfg; + struct alc_customize_define cdefine; struct snd_array kctls; struct hda_input_mux private_imux[3]; hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; @@ -1247,6 +1260,62 @@ static void alc_init_auto_mic(struct hda_codec *codec) spec->unsol_event = alc_sku_unsol_event; } +static int alc_auto_parse_customize_define(struct hda_codec *codec) +{ + unsigned int ass, tmp, i; + unsigned nid; + struct alc_spec *spec = codec->spec; + + ass = codec->subsystem_id & 0xffff; + if (ass != codec->bus->pci->subsystem_device && (ass & 1)) + goto do_sku; + + nid = 0x1d; + if (codec->vendor_id == 0x10ec0260) + nid = 0x17; + ass = snd_hda_codec_get_pincfg(codec, nid); + + if (!(ass & 1)) { + printk(KERN_INFO "hda_codec: %s: SKU not ready 0x%08x\n", + codec->chip_name, ass); + return -1; + } + + /* check sum */ + tmp = 0; + for (i = 1; i < 16; i++) { + if ((ass >> i) & 1) + tmp++; + } + if (((ass >> 16) & 0xf) != tmp) + return -1; + + spec->cdefine.port_connectivity = ass >> 30; + spec->cdefine.enable_pcbeep = (ass & 0x100000) >> 20; + spec->cdefine.check_sum = (ass >> 16) & 0xf; + spec->cdefine.customization = ass >> 8; +do_sku: + spec->cdefine.sku_cfg = ass; + spec->cdefine.external_amp = (ass & 0x38) >> 3; + spec->cdefine.platform_type = (ass & 0x4) >> 2; + spec->cdefine.swap = (ass & 0x2) >> 1; + spec->cdefine.override = ass & 0x1; + + snd_printd("SKU: Nid=0x%x sku_cfg=0x%08x\n", + nid, spec->cdefine.sku_cfg); + snd_printd("SKU: port_connectivity=0x%x\n", + spec->cdefine.port_connectivity); + snd_printd("SKU: enable_pcbeep=0x%x\n", spec->cdefine.enable_pcbeep); + snd_printd("SKU: check_sum=0x%08x\n", spec->cdefine.check_sum); + snd_printd("SKU: customization=0x%08x\n", spec->cdefine.customization); + snd_printd("SKU: external_amp=0x%x\n", spec->cdefine.external_amp); + snd_printd("SKU: platform_type=0x%x\n", spec->cdefine.platform_type); + snd_printd("SKU: swap=0x%x\n", spec->cdefine.swap); + snd_printd("SKU: override=0x%x\n", spec->cdefine.override); + + return 0; +} + /* check subsystem ID and set up device-specific initialization; * return 1 if initialized, 0 if invalid SSID */ @@ -3779,7 +3848,6 @@ static struct hda_codec_ops alc_patch_ops = { .reboot_notify = alc_shutup, }; - /* * Test configuration for debugging * @@ -10267,6 +10335,8 @@ static int patch_alc882(struct hda_codec *codec) codec->spec = spec; + alc_auto_parse_customize_define(codec); + switch (codec->vendor_id) { case 0x10ec0882: case 0x10ec0885: @@ -10362,7 +10432,9 @@ static int patch_alc882(struct hda_codec *codec) } set_capture_mixer(codec); - set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + + if (spec->cdefine.enable_pcbeep) + set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); spec->vmaster_nid = 0x0c; @@ -12146,6 +12218,7 @@ static int patch_alc262(struct hda_codec *codec) snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PROC_COEF, tmp | 0x80); } #endif + alc_auto_parse_customize_define(codec); alc_fix_pll_init(codec, 0x20, 0x0a, 10); @@ -12224,7 +12297,7 @@ static int patch_alc262(struct hda_codec *codec) } if (!spec->cap_mixer && !spec->no_analog) set_capture_mixer(codec); - if (!spec->no_analog) + if (!spec->no_analog && spec->cdefine.enable_pcbeep) set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); spec->vmaster_nid = 0x0c; @@ -14094,6 +14167,8 @@ static int patch_alc269(struct hda_codec *codec) codec->spec = spec; + alc_auto_parse_customize_define(codec); + alc_fix_pll_init(codec, 0x20, 0x04, 15); if ((alc_read_coef_idx(codec, 0) & 0x00f0) == 0x0010){ @@ -14164,7 +14239,8 @@ static int patch_alc269(struct hda_codec *codec) if (!spec->cap_mixer) set_capture_mixer(codec); - set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT); + if (spec->cdefine.enable_pcbeep) + set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT); spec->vmaster_nid = 0x02; @@ -18314,6 +18390,8 @@ static int patch_alc662(struct hda_codec *codec) codec->spec = spec; + alc_auto_parse_customize_define(codec); + alc_fix_pll_init(codec, 0x20, 0x04, 15); if (alc_read_coef_idx(codec, 0)==0x8020){ @@ -18373,18 +18451,20 @@ static int patch_alc662(struct hda_codec *codec) if (!spec->cap_mixer) set_capture_mixer(codec); - switch (codec->vendor_id) { - case 0x10ec0662: - set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); - break; - case 0x10ec0272: - case 0x10ec0663: - case 0x10ec0665: - set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT); - break; - case 0x10ec0273: - set_beep_amp(spec, 0x0b, 0x03, HDA_INPUT); - break; + if (spec->cdefine.enable_pcbeep) { + switch (codec->vendor_id) { + case 0x10ec0662: + set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT); + break; + case 0x10ec0272: + case 0x10ec0663: + case 0x10ec0665: + set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT); + break; + case 0x10ec0273: + set_beep_amp(spec, 0x0b, 0x03, HDA_INPUT); + break; + } } spec->vmaster_nid = 0x02; -- cgit v1.2.3-18-g5258 From c027ddcd01c852dfa5880862e47f022d7d9402f5 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Fri, 19 Mar 2010 11:33:06 +0100 Subject: ALSA: hda - Add alc_codec_rename() helper Added alc_codec_rename() helper for renaming codec->chip_name. Added Acer-specific codec naming for ALC269/662. [Clean-up and refactoring by tiwai] Signed-off-by: Kailang Yang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 245e1afa589..3d37e2161a5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -3848,6 +3848,18 @@ static struct hda_codec_ops alc_patch_ops = { .reboot_notify = alc_shutup, }; +/* replace the codec chip_name with the given string */ +static int alc_codec_rename(struct hda_codec *codec, const char *name) +{ + kfree(codec->chip_name); + codec->chip_name = kstrdup(name, GFP_KERNEL); + if (!codec->chip_name) { + alc_free(codec); + return -ENOMEM; + } + return 0; +} + /* * Test configuration for debugging * @@ -14169,17 +14181,15 @@ static int patch_alc269(struct hda_codec *codec) alc_auto_parse_customize_define(codec); - alc_fix_pll_init(codec, 0x20, 0x04, 15); - if ((alc_read_coef_idx(codec, 0) & 0x00f0) == 0x0010){ - kfree(codec->chip_name); - codec->chip_name = kstrdup("ALC259", GFP_KERNEL); - if (!codec->chip_name) { - alc_free(codec); - return -ENOMEM; - } + if (codec->bus->pci->subsystem_vendor == 0x1025 && + spec->cdefine.platform_type == 1) + alc_codec_rename(codec, "ALC271X"); + else + alc_codec_rename(codec, "ALC259"); is_alc269vb = 1; - } + } else + alc_fix_pll_init(codec, 0x20, 0x04, 15); board_config = snd_hda_check_board_config(codec, ALC269_MODEL_LAST, alc269_models, @@ -18394,14 +18404,12 @@ static int patch_alc662(struct hda_codec *codec) alc_fix_pll_init(codec, 0x20, 0x04, 15); - if (alc_read_coef_idx(codec, 0)==0x8020){ - kfree(codec->chip_name); - codec->chip_name = kstrdup("ALC661", GFP_KERNEL); - if (!codec->chip_name) { - alc_free(codec); - return -ENOMEM; - } - } + if (alc_read_coef_idx(codec, 0) == 0x8020) + alc_codec_rename(codec, "ALC661"); + else if ((alc_read_coef_idx(codec, 0) & (1 << 14)) && + codec->bus->pci->subsystem_vendor == 0x1025 && + spec->cdefine.platform_type == 1) + alc_codec_rename(codec, "ALC272X"); board_config = snd_hda_check_board_config(codec, ALC662_MODEL_LAST, alc662_models, -- cgit v1.2.3-18-g5258 From 9fc71e8f586efc99472337a8b089a4915877b4b8 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Thu, 18 Mar 2010 19:45:41 -0600 Subject: ASoC: McPDM: Use tabs for indentation Indentation in initial support for McPDM driver was converted to spaces. Use tabs to comply with open source coding-style. Signed-off-by: Jorge Eduardo Candelaria Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/omap/mcpdm.c | 548 ++++++++++++++++++++++++------------------------- 1 file changed, 274 insertions(+), 274 deletions(-) diff --git a/sound/soc/omap/mcpdm.c b/sound/soc/omap/mcpdm.c index ad8df6cfae8..479d3b6306d 100644 --- a/sound/soc/omap/mcpdm.c +++ b/sound/soc/omap/mcpdm.c @@ -1,5 +1,5 @@ /* - * mcpdm.c -- McPDM interface driver + * mcpdm.c -- McPDM interface driver * * Author: Jorge Eduardo Candelaria * Copyright (C) 2009 - Texas Instruments, Inc. @@ -38,46 +38,46 @@ static struct omap_mcpdm *mcpdm; static inline void omap_mcpdm_write(u16 reg, u32 val) { - __raw_writel(val, mcpdm->io_base + reg); + __raw_writel(val, mcpdm->io_base + reg); } static inline int omap_mcpdm_read(u16 reg) { - return __raw_readl(mcpdm->io_base + reg); + return __raw_readl(mcpdm->io_base + reg); } static void omap_mcpdm_reg_dump(void) { - dev_dbg(mcpdm->dev, "***********************\n"); - dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQSTATUS_RAW)); - dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQSTATUS)); - dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQENABLE_SET)); - dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQENABLE_CLR)); - dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQWAKE_EN)); - dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAENABLE_SET)); - dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAENABLE_CLR)); - dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAWAKEEN)); - dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", - omap_mcpdm_read(MCPDM_CTRL)); - dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", - omap_mcpdm_read(MCPDM_DN_DATA)); - dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", - omap_mcpdm_read(MCPDM_UP_DATA)); - dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", - omap_mcpdm_read(MCPDM_FIFO_CTRL_DN)); - dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", - omap_mcpdm_read(MCPDM_FIFO_CTRL_UP)); - dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n", - omap_mcpdm_read(MCPDM_DN_OFFSET)); - dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQSTATUS_RAW)); + dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQSTATUS)); + dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQENABLE_SET)); + dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQENABLE_CLR)); + dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQWAKE_EN)); + dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAENABLE_SET)); + dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAENABLE_CLR)); + dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAWAKEEN)); + dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", + omap_mcpdm_read(MCPDM_CTRL)); + dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", + omap_mcpdm_read(MCPDM_DN_DATA)); + dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", + omap_mcpdm_read(MCPDM_UP_DATA)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", + omap_mcpdm_read(MCPDM_FIFO_CTRL_DN)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", + omap_mcpdm_read(MCPDM_FIFO_CTRL_UP)); + dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n", + omap_mcpdm_read(MCPDM_DN_OFFSET)); + dev_dbg(mcpdm->dev, "***********************\n"); } /* @@ -86,26 +86,26 @@ static void omap_mcpdm_reg_dump(void) */ static void omap_mcpdm_reset_capture(int reset) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (reset) - ctrl |= SW_UP_RST; - else - ctrl &= ~SW_UP_RST; + if (reset) + ctrl |= SW_UP_RST; + else + ctrl &= ~SW_UP_RST; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } static void omap_mcpdm_reset_playback(int reset) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (reset) - ctrl |= SW_DN_RST; - else - ctrl &= ~SW_DN_RST; + if (reset) + ctrl |= SW_DN_RST; + else + ctrl &= ~SW_DN_RST; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -114,14 +114,14 @@ static void omap_mcpdm_reset_playback(int reset) */ void omap_mcpdm_start(int stream) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (stream) - ctrl |= mcpdm->up_channels; - else - ctrl |= mcpdm->dn_channels; + if (stream) + ctrl |= mcpdm->up_channels; + else + ctrl |= mcpdm->dn_channels; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -130,14 +130,14 @@ void omap_mcpdm_start(int stream) */ void omap_mcpdm_stop(int stream) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (stream) - ctrl &= ~mcpdm->up_channels; - else - ctrl &= ~mcpdm->dn_channels; + if (stream) + ctrl &= ~mcpdm->up_channels; + else + ctrl &= ~mcpdm->dn_channels; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -146,38 +146,38 @@ void omap_mcpdm_stop(int stream) */ int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink) { - int irq_mask = 0; - int ctrl; + int irq_mask = 0; + int ctrl; - if (!uplink) - return -EINVAL; + if (!uplink) + return -EINVAL; - mcpdm->uplink = uplink; + mcpdm->uplink = uplink; - /* Enable irq request generation */ - irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); + /* Enable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); - /* Configure uplink threshold */ - if (uplink->threshold > UP_THRES_MAX) - uplink->threshold = UP_THRES_MAX; + /* Configure uplink threshold */ + if (uplink->threshold > UP_THRES_MAX) + uplink->threshold = UP_THRES_MAX; - omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold); + omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold); - /* Configure DMA controller */ - omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE); + /* Configure DMA controller */ + omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE); - /* Set pdm out format */ - ctrl = omap_mcpdm_read(MCPDM_CTRL); - ctrl &= ~PDMOUTFORMAT; - ctrl |= uplink->format & PDMOUTFORMAT; + /* Set pdm out format */ + ctrl = omap_mcpdm_read(MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= uplink->format & PDMOUTFORMAT; - /* Uplink channels */ - mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK); + /* Uplink channels */ + mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK); - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); - return 0; + return 0; } /* @@ -186,38 +186,38 @@ int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink) */ int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink) { - int irq_mask = 0; - int ctrl; + int irq_mask = 0; + int ctrl; - if (!downlink) - return -EINVAL; + if (!downlink) + return -EINVAL; - mcpdm->downlink = downlink; + mcpdm->downlink = downlink; - /* Enable irq request generation */ - irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); + /* Enable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); - /* Configure uplink threshold */ - if (downlink->threshold > DN_THRES_MAX) - downlink->threshold = DN_THRES_MAX; + /* Configure uplink threshold */ + if (downlink->threshold > DN_THRES_MAX) + downlink->threshold = DN_THRES_MAX; - omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold); + omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold); - /* Enable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE); + /* Enable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE); - /* Set pdm out format */ - ctrl = omap_mcpdm_read(MCPDM_CTRL); - ctrl &= ~PDMOUTFORMAT; - ctrl |= downlink->format & PDMOUTFORMAT; + /* Set pdm out format */ + ctrl = omap_mcpdm_read(MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= downlink->format & PDMOUTFORMAT; - /* Downlink channels */ - mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK); + /* Downlink channels */ + mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK); - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); - return 0; + return 0; } /* @@ -226,24 +226,24 @@ int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink) */ int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink) { - int irq_mask = 0; + int irq_mask = 0; - if (!uplink) - return -EINVAL; + if (!uplink) + return -EINVAL; - /* Disable irq request generation */ - irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); + /* Disable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); - /* Disable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE); + /* Disable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE); - /* Clear Downlink channels */ - mcpdm->up_channels = 0; + /* Clear Downlink channels */ + mcpdm->up_channels = 0; - mcpdm->uplink = NULL; + mcpdm->uplink = NULL; - return 0; + return 0; } /* @@ -252,124 +252,124 @@ int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink) */ int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink) { - int irq_mask = 0; + int irq_mask = 0; - if (!downlink) - return -EINVAL; + if (!downlink) + return -EINVAL; - /* Disable irq request generation */ - irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); + /* Disable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); - /* Disable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE); + /* Disable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE); - /* clear Downlink channels */ - mcpdm->dn_channels = 0; + /* clear Downlink channels */ + mcpdm->dn_channels = 0; - mcpdm->downlink = NULL; + mcpdm->downlink = NULL; - return 0; + return 0; } static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) { - struct omap_mcpdm *mcpdm_irq = dev_id; - int irq_status; - - irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS); - - /* Acknowledge irq event */ - omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status); - - if (irq & MCPDM_DN_IRQ_FULL) { - dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); - omap_mcpdm_reset_playback(1); - omap_mcpdm_playback_open(mcpdm_irq->downlink); - omap_mcpdm_reset_playback(0); - } - - if (irq & MCPDM_DN_IRQ_EMPTY) { - dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); - omap_mcpdm_reset_playback(1); - omap_mcpdm_playback_open(mcpdm_irq->downlink); - omap_mcpdm_reset_playback(0); - } - - if (irq & MCPDM_DN_IRQ) { - dev_dbg(mcpdm_irq->dev, "DN write request\n"); - } - - if (irq & MCPDM_UP_IRQ_FULL) { - dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); - omap_mcpdm_reset_capture(1); - omap_mcpdm_capture_open(mcpdm_irq->uplink); - omap_mcpdm_reset_capture(0); - } - - if (irq & MCPDM_UP_IRQ_EMPTY) { - dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); - omap_mcpdm_reset_capture(1); - omap_mcpdm_capture_open(mcpdm_irq->uplink); - omap_mcpdm_reset_capture(0); - } - - if (irq & MCPDM_UP_IRQ) { - dev_dbg(mcpdm_irq->dev, "UP write request\n"); - } - - return IRQ_HANDLED; + struct omap_mcpdm *mcpdm_irq = dev_id; + int irq_status; + + irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS); + + /* Acknowledge irq event */ + omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status); + + if (irq & MCPDM_DN_IRQ_FULL) { + dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(1); + omap_mcpdm_playback_open(mcpdm_irq->downlink); + omap_mcpdm_reset_playback(0); + } + + if (irq & MCPDM_DN_IRQ_EMPTY) { + dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(1); + omap_mcpdm_playback_open(mcpdm_irq->downlink); + omap_mcpdm_reset_playback(0); + } + + if (irq & MCPDM_DN_IRQ) { + dev_dbg(mcpdm_irq->dev, "DN write request\n"); + } + + if (irq & MCPDM_UP_IRQ_FULL) { + dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(1); + omap_mcpdm_capture_open(mcpdm_irq->uplink); + omap_mcpdm_reset_capture(0); + } + + if (irq & MCPDM_UP_IRQ_EMPTY) { + dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(1); + omap_mcpdm_capture_open(mcpdm_irq->uplink); + omap_mcpdm_reset_capture(0); + } + + if (irq & MCPDM_UP_IRQ) { + dev_dbg(mcpdm_irq->dev, "UP write request\n"); + } + + return IRQ_HANDLED; } int omap_mcpdm_request(void) { - int ret; + int ret; - clk_enable(mcpdm->clk); + clk_enable(mcpdm->clk); - spin_lock(&mcpdm->lock); + spin_lock(&mcpdm->lock); - if (!mcpdm->free) { - dev_err(mcpdm->dev, "McPDM interface is in use\n"); - spin_unlock(&mcpdm->lock); - ret = -EBUSY; - goto err; - } - mcpdm->free = 0; + if (!mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is in use\n"); + spin_unlock(&mcpdm->lock); + ret = -EBUSY; + goto err; + } + mcpdm->free = 0; - spin_unlock(&mcpdm->lock); + spin_unlock(&mcpdm->lock); - /* Disable lines while request is ongoing */ - omap_mcpdm_write(MCPDM_CTRL, 0x00); + /* Disable lines while request is ongoing */ + omap_mcpdm_write(MCPDM_CTRL, 0x00); - ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, - 0, "McPDM", (void *)mcpdm); - if (ret) { - dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n"); - goto err; - } + ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, + 0, "McPDM", (void *)mcpdm); + if (ret) { + dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n"); + goto err; + } - return 0; + return 0; err: - clk_disable(mcpdm->clk); - return ret; + clk_disable(mcpdm->clk); + return ret; } void omap_mcpdm_free(void) { - spin_lock(&mcpdm->lock); - if (mcpdm->free) { - dev_err(mcpdm->dev, "McPDM interface is already free\n"); - spin_unlock(&mcpdm->lock); - return; - } - mcpdm->free = 1; - spin_unlock(&mcpdm->lock); - - clk_disable(mcpdm->clk); - - free_irq(mcpdm->irq, (void *)mcpdm); + spin_lock(&mcpdm->lock); + if (mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is already free\n"); + spin_unlock(&mcpdm->lock); + return; + } + mcpdm->free = 1; + spin_unlock(&mcpdm->lock); + + clk_disable(mcpdm->clk); + + free_irq(mcpdm->irq, (void *)mcpdm); } /* Enable/disable DC offset cancelation for the analog @@ -377,108 +377,108 @@ void omap_mcpdm_free(void) */ int omap_mcpdm_set_offset(int offset1, int offset2) { - int offset; + int offset; - if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX)) - return -EINVAL; + if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX)) + return -EINVAL; - offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2); + offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2); - /* offset cancellation for channel 1 */ - if (offset1) - offset |= DN_OFST_RX1_EN; - else - offset &= ~DN_OFST_RX1_EN; + /* offset cancellation for channel 1 */ + if (offset1) + offset |= DN_OFST_RX1_EN; + else + offset &= ~DN_OFST_RX1_EN; - /* offset cancellation for channel 2 */ - if (offset2) - offset |= DN_OFST_RX2_EN; - else - offset &= ~DN_OFST_RX2_EN; + /* offset cancellation for channel 2 */ + if (offset2) + offset |= DN_OFST_RX2_EN; + else + offset &= ~DN_OFST_RX2_EN; - omap_mcpdm_write(MCPDM_DN_OFFSET, offset); + omap_mcpdm_write(MCPDM_DN_OFFSET, offset); - return 0; + return 0; } static int __devinit omap_mcpdm_probe(struct platform_device *pdev) { - struct resource *res; - int ret = 0; - - mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); - if (!mcpdm) { - ret = -ENOMEM; - goto exit; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "no resource\n"); - goto err_resource; - } - - spin_lock_init(&mcpdm->lock); - mcpdm->free = 1; - mcpdm->io_base = ioremap(res->start, resource_size(res)); - if (!mcpdm->io_base) { - ret = -ENOMEM; - goto err_resource; - } - - mcpdm->irq = platform_get_irq(pdev, 0); - - mcpdm->clk = clk_get(&pdev->dev, "pdm_ck"); - if (IS_ERR(mcpdm->clk)) { - ret = PTR_ERR(mcpdm->clk); - dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret); - goto err_clk; - } - - mcpdm->dev = &pdev->dev; - platform_set_drvdata(pdev, mcpdm); - - return 0; + struct resource *res; + int ret = 0; + + mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); + if (!mcpdm) { + ret = -ENOMEM; + goto exit; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no resource\n"); + goto err_resource; + } + + spin_lock_init(&mcpdm->lock); + mcpdm->free = 1; + mcpdm->io_base = ioremap(res->start, resource_size(res)); + if (!mcpdm->io_base) { + ret = -ENOMEM; + goto err_resource; + } + + mcpdm->irq = platform_get_irq(pdev, 0); + + mcpdm->clk = clk_get(&pdev->dev, "pdm_ck"); + if (IS_ERR(mcpdm->clk)) { + ret = PTR_ERR(mcpdm->clk); + dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret); + goto err_clk; + } + + mcpdm->dev = &pdev->dev; + platform_set_drvdata(pdev, mcpdm); + + return 0; err_clk: - iounmap(mcpdm->io_base); + iounmap(mcpdm->io_base); err_resource: - kfree(mcpdm); + kfree(mcpdm); exit: - return ret; + return ret; } static int __devexit omap_mcpdm_remove(struct platform_device *pdev) { - struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev); + struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev); - platform_set_drvdata(pdev, NULL); + platform_set_drvdata(pdev, NULL); - clk_put(mcpdm_ptr->clk); + clk_put(mcpdm_ptr->clk); - iounmap(mcpdm_ptr->io_base); + iounmap(mcpdm_ptr->io_base); - mcpdm_ptr->clk = NULL; - mcpdm_ptr->free = 0; - mcpdm_ptr->dev = NULL; + mcpdm_ptr->clk = NULL; + mcpdm_ptr->free = 0; + mcpdm_ptr->dev = NULL; - kfree(mcpdm_ptr); + kfree(mcpdm_ptr); - return 0; + return 0; } static struct platform_driver omap_mcpdm_driver = { - .probe = omap_mcpdm_probe, - .remove = __devexit_p(omap_mcpdm_remove), - .driver = { - .name = "omap-mcpdm", - }, + .probe = omap_mcpdm_probe, + .remove = __devexit_p(omap_mcpdm_remove), + .driver = { + .name = "omap-mcpdm", + }, }; static struct platform_device *omap_mcpdm_device; static int __init omap_mcpdm_init(void) { - return platform_driver_register(&omap_mcpdm_driver); + return platform_driver_register(&omap_mcpdm_driver); } arch_initcall(omap_mcpdm_init); -- cgit v1.2.3-18-g5258 From 299a151f53ed9272af616076305d3ee762c2eba0 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 19 Mar 2010 12:27:31 +0200 Subject: ASoC: omap-mcbsp: Add support for Left Justified format Basic support for Left Justified coding for OMAP McBSP. Signed-off-by: Peter Ujfalusi Acked-by: Jarkko Nikula Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 2952fb046ed..0199e95a5d6 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -331,7 +331,8 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK; wpf = channels = params_channels(params); - if (channels == 2 && format == SND_SOC_DAIFMT_I2S) { + if (channels == 2 && (format == SND_SOC_DAIFMT_I2S || + format == SND_SOC_DAIFMT_LEFT_J)) { /* Use dual-phase frames */ regs->rcr2 |= RPHASE; regs->xcr2 |= XPHASE; @@ -376,6 +377,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, /* Set FS period and length in terms of bit clock periods */ switch (format) { case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: regs->srgr2 |= FPER(framesize - 1); regs->srgr1 |= FWID((framesize >> 1) - 1); break; @@ -427,6 +429,14 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_LEFT_J: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + regs->spcr1 |= RJUST(2); + /* Invert FS polarity configuration */ + temp_fmt ^= SND_SOC_DAIFMT_NB_IF; + break; case SND_SOC_DAIFMT_DSP_A: /* 1-bit data delay */ regs->rcr2 |= RDATDLY(1); -- cgit v1.2.3-18-g5258 From 27648b2f1c464f8827cc01ba4eb21580b5402947 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 16 Mar 2010 16:09:47 +0000 Subject: ASoC: Correct typoed Mic2 connections on 1133-EV1 board Signed-off-by: Mark Brown --- sound/soc/imx/wm1133-ev1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c index b75fcde85e8..0af987e9422 100644 --- a/sound/soc/imx/wm1133-ev1.c +++ b/sound/soc/imx/wm1133-ev1.c @@ -181,8 +181,8 @@ static const struct snd_soc_dapm_route wm1133_ev1_map[] = { /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ { "IN1RN", NULL, "Mic Bias" }, - { "IN1RP", NULL, "Mic1 Jack" }, - { "Mic Bias", NULL, "Mic1 Jack" }, + { "IN1RP", NULL, "Mic2 Jack" }, + { "Mic Bias", NULL, "Mic2 Jack" }, /* Line in Jack --> AUX (L+R) */ { "IN3R", NULL, "Line In Jack" }, -- cgit v1.2.3-18-g5258 From 8ecbabd977dc64d2d8c9980420a3d72db06fe551 Mon Sep 17 00:00:00 2001 From: Misael Lopez Cruz Date: Fri, 19 Mar 2010 11:25:51 +0000 Subject: ASoC: TWL6040: Add twl6040 codec driver Initial version of TWL6040 codec driver. The TWL6040 codec uses a proprietary PDM-based digital audio interface. Audio paths supported are: - Input: Main Mic, Sub Mic, Headset Mic, Auxiliary-FM Left/Right - Output: Headset Left/Right, Handsfree Left/Right TWL6040 codec supports power-up/down manual and automatic sequence. Manual sequence is done through a specific register writes sequence. Automatic sequence is done when the codec is powered-up through the external AUDPWRON line. The completion of the sequence is signaled through the audio interrupt. TWL6040 codec sysclk can be provided by: low-power or high performance PLL: - The low-power PLL takes a low-frequency input at 32,768 Hz and generates an approximate of 17.64 or 19.2 MHz (for 44.1 KHz and 48 KHz respectively) - The high-performance PLL generates an exact 19.2 MHz clock signal from high-frequency input at 12/19.2/26/38.4 MHz. Low-power playback mode is a special scenario where only headset path (headset DAC and driver) is active. For the particular case of headset path, PLL being used defines the headset power mode: low-power, high-performance. Signed-off-by: Misael Lopez Cruz Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/twl6040.c | 1227 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/twl6040.h | 141 +++++ 4 files changed, 1374 insertions(+) create mode 100755 sound/soc/codecs/twl6040.c create mode 100644 sound/soc/codecs/twl6040.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 16c47edb826..398cbb0e79f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -35,6 +35,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C select SND_SOC_TWL4030 if TWL4030_CORE + select SND_SOC_TWL6040 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C select SND_SOC_WM2000 if I2C @@ -168,6 +169,9 @@ config SND_SOC_TWL4030 select TWL4030_CODEC tristate +config SND_SOC_TWL6040 + tristate + config SND_SOC_UDA134X tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 69817778b2b..98bd10c8fd3 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -22,6 +22,7 @@ snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-twl4030-objs := twl4030.o +snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8350-objs := wm8350.o @@ -85,6 +86,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o +obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c new file mode 100755 index 00000000000..0e4dce7de05 --- /dev/null +++ b/sound/soc/codecs/twl6040.c @@ -0,0 +1,1227 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "twl6040.h" + +#define TWL6040_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +/* codec private data */ +struct twl6040_data { + struct snd_soc_codec codec; + int audpwron; + int naudint; + int codec_powered; + int pll; + int non_lp; + unsigned int sysclk; + struct snd_pcm_hw_constraint_list *sysclk_constraints; + struct completion ready; +}; + +/* + * twl6040 register cache & default register settings + */ +static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = { + 0x00, /* not used 0x00 */ + 0x4B, /* TWL6040_ASICID (ro) 0x01 */ + 0x00, /* TWL6040_ASICREV (ro) 0x02 */ + 0x00, /* TWL6040_INTID 0x03 */ + 0x00, /* TWL6040_INTMR 0x04 */ + 0x00, /* TWL6040_NCPCTRL 0x05 */ + 0x00, /* TWL6040_LDOCTL 0x06 */ + 0x60, /* TWL6040_HPPLLCTL 0x07 */ + 0x00, /* TWL6040_LPPLLCTL 0x08 */ + 0x4A, /* TWL6040_LPPLLDIV 0x09 */ + 0x00, /* TWL6040_AMICBCTL 0x0A */ + 0x00, /* TWL6040_DMICBCTL 0x0B */ + 0x18, /* TWL6040_MICLCTL 0x0C - No input selected on Left Mic */ + 0x18, /* TWL6040_MICRCTL 0x0D - No input selected on Right Mic */ + 0x00, /* TWL6040_MICGAIN 0x0E */ + 0x1B, /* TWL6040_LINEGAIN 0x0F */ + 0x00, /* TWL6040_HSLCTL 0x10 */ + 0x00, /* TWL6040_HSRCTL 0x11 */ + 0x00, /* TWL6040_HSGAIN 0x12 */ + 0x00, /* TWL6040_EARCTL 0x13 */ + 0x00, /* TWL6040_HFLCTL 0x14 */ + 0x00, /* TWL6040_HFLGAIN 0x15 */ + 0x00, /* TWL6040_HFRCTL 0x16 */ + 0x00, /* TWL6040_HFRGAIN 0x17 */ + 0x00, /* TWL6040_VIBCTLL 0x18 */ + 0x00, /* TWL6040_VIBDATL 0x19 */ + 0x00, /* TWL6040_VIBCTLR 0x1A */ + 0x00, /* TWL6040_VIBDATR 0x1B */ + 0x00, /* TWL6040_HKCTL1 0x1C */ + 0x00, /* TWL6040_HKCTL2 0x1D */ + 0x00, /* TWL6040_GPOCTL 0x1E */ + 0x00, /* TWL6040_ALB 0x1F */ + 0x00, /* TWL6040_DLB 0x20 */ + 0x00, /* not used 0x21 */ + 0x00, /* not used 0x22 */ + 0x00, /* not used 0x23 */ + 0x00, /* not used 0x24 */ + 0x00, /* not used 0x25 */ + 0x00, /* not used 0x26 */ + 0x00, /* not used 0x27 */ + 0x00, /* TWL6040_TRIM1 0x28 */ + 0x00, /* TWL6040_TRIM2 0x29 */ + 0x00, /* TWL6040_TRIM3 0x2A */ + 0x00, /* TWL6040_HSOTRIM 0x2B */ + 0x00, /* TWL6040_HFOTRIM 0x2C */ + 0x09, /* TWL6040_ACCCTL 0x2D */ + 0x00, /* TWL6040_STATUS (ro) 0x2E */ +}; + +/* + * twl6040 vio/gnd registers: + * registers under vio/gnd supply can be accessed + * before the power-up sequence, after NRESPWRON goes high + */ +static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = { + TWL6040_REG_ASICID, + TWL6040_REG_ASICREV, + TWL6040_REG_INTID, + TWL6040_REG_INTMR, + TWL6040_REG_NCPCTL, + TWL6040_REG_LDOCTL, + TWL6040_REG_AMICBCTL, + TWL6040_REG_DMICBCTL, + TWL6040_REG_HKCTL1, + TWL6040_REG_HKCTL2, + TWL6040_REG_GPOCTL, + TWL6040_REG_TRIM1, + TWL6040_REG_TRIM2, + TWL6040_REG_TRIM3, + TWL6040_REG_HSOTRIM, + TWL6040_REG_HFOTRIM, + TWL6040_REG_ACCCTL, + TWL6040_REG_STATUS, +}; + +/* + * twl6040 vdd/vss registers: + * registers under vdd/vss supplies can only be accessed + * after the power-up sequence + */ +static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = { + TWL6040_REG_HPPLLCTL, + TWL6040_REG_LPPLLCTL, + TWL6040_REG_LPPLLDIV, + TWL6040_REG_MICLCTL, + TWL6040_REG_MICRCTL, + TWL6040_REG_MICGAIN, + TWL6040_REG_LINEGAIN, + TWL6040_REG_HSLCTL, + TWL6040_REG_HSRCTL, + TWL6040_REG_HSGAIN, + TWL6040_REG_EARCTL, + TWL6040_REG_HFLCTL, + TWL6040_REG_HFLGAIN, + TWL6040_REG_HFRCTL, + TWL6040_REG_HFRGAIN, + TWL6040_REG_VIBCTLL, + TWL6040_REG_VIBDATL, + TWL6040_REG_VIBCTLR, + TWL6040_REG_VIBDATR, + TWL6040_REG_ALB, + TWL6040_REG_DLB, +}; + +/* + * read twl6040 register cache + */ +static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + return cache[reg]; +} + +/* + * write twl6040 register cache + */ +static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + + if (reg >= TWL6040_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * read from twl6040 hardware register + */ +static int twl6040_read_reg_volatile(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value; + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg); + twl6040_write_reg_cache(codec, reg, value); + + return value; +} + +/* + * write to the twl6040 register space + */ +static int twl6040_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + twl6040_write_reg_cache(codec, reg, value); + return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); +} + +static void twl6040_init_vio_regs(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + int reg, i; + + /* allow registers to be accessed by i2c */ + twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]); + + for (i = 0; i < TWL6040_VIOREGNUM; i++) { + reg = twl6040_vio_reg[i]; + /* skip read-only registers (ASICID, ASICREV, STATUS) */ + switch (reg) { + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_STATUS: + continue; + default: + break; + } + twl6040_write(codec, reg, cache[reg]); + } +} + +static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + int reg, i; + + for (i = 0; i < TWL6040_VDDREGNUM; i++) { + reg = twl6040_vdd_reg[i]; + twl6040_write(codec, reg, cache[reg]); + } +} + +/* twl6040 codec manual power-up sequence */ +static void twl6040_power_up(struct snd_soc_codec *codec) +{ + u8 ncpctl, ldoctl, lppllctl, accctl; + + ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL); + ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL); + + /* enable reference system */ + ldoctl |= TWL6040_REFENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + msleep(10); + /* enable internal oscillator */ + ldoctl |= TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(10); + /* enable high-side ldo */ + ldoctl |= TWL6040_HSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* enable negative charge pump */ + ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN; + twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl); + udelay(488); + /* enable low-side ldo */ + ldoctl |= TWL6040_LSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* enable low-power pll */ + lppllctl |= TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + /* reset state machine */ + accctl |= TWL6040_RESETSPLIT; + twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); + mdelay(5); + accctl &= ~TWL6040_RESETSPLIT; + twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); + /* disable internal oscillator */ + ldoctl &= ~TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); +} + +/* twl6040 codec manual power-down sequence */ +static void twl6040_power_down(struct snd_soc_codec *codec) +{ + u8 ncpctl, ldoctl, lppllctl, accctl; + + ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL); + ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL); + + /* enable internal oscillator */ + ldoctl |= TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(10); + /* disable low-power pll */ + lppllctl &= ~TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + /* disable low-side ldo */ + ldoctl &= ~TWL6040_LSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* disable negative charge pump */ + ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN); + twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl); + udelay(488); + /* disable high-side ldo */ + ldoctl &= ~TWL6040_HSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* disable internal oscillator */ + ldoctl &= ~TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + /* disable reference system */ + ldoctl &= ~TWL6040_REFENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + msleep(10); +} + +/* set headset dac and driver power mode */ +static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) +{ + int hslctl, hsrctl; + int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL; + + hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); + + if (high_perf) { + hslctl &= ~mask; + hsrctl &= ~mask; + } else { + hslctl |= mask; + hsrctl |= mask; + } + + twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl); + twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl); + + return 0; +} + +static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct twl6040_data *priv = codec->private_data; + + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->non_lp++; + else + priv->non_lp--; + + return 0; +} + +/* audio interrupt handler */ +static irqreturn_t twl6040_naudint_handler(int irq, void *data) +{ + struct snd_soc_codec *codec = data; + struct twl6040_data *priv = codec->private_data; + u8 intid; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); + + switch (intid) { + case TWL6040_THINT: + dev_alert(codec->dev, "die temp over-limit detection\n"); + break; + case TWL6040_PLUGINT: + case TWL6040_UNPLUGINT: + case TWL6040_HOOKINT: + break; + case TWL6040_HFINT: + dev_alert(codec->dev, "hf drivers over current detection\n"); + break; + case TWL6040_VIBINT: + dev_alert(codec->dev, "vib drivers over current detection\n"); + break; + case TWL6040_READYINT: + complete(&priv->ready); + break; + default: + dev_err(codec->dev, "unknown audio interrupt %d\n", intid); + break; + } + + return IRQ_HANDLED; +} + +/* + * MICATT volume control: + * from -6 to 0 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); + +/* + * MICGAIN volume control: + * from 6 to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); + +/* + * HSGAIN volume control: + * from -30 to 0 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0); + +/* + * HFGAIN volume control: + * from -52 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0); + +/* Left analog microphone selection */ +static const char *twl6040_amicl_texts[] = + {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"}; + +/* Right analog microphone selection */ +static const char *twl6040_amicr_texts[] = + {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"}; + +static const struct soc_enum twl6040_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 3, twl6040_amicl_texts), + SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts), +}; + +static const struct snd_kcontrol_new amicl_control = + SOC_DAPM_ENUM("Route", twl6040_enum[0]); + +static const struct snd_kcontrol_new amicr_control = + SOC_DAPM_ENUM("Route", twl6040_enum[1]); + +/* Headset DAC playback switches */ +static const struct snd_kcontrol_new hsdacl_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 5, 1, 0); + +static const struct snd_kcontrol_new hsdacr_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 5, 1, 0); + +/* Handsfree DAC playback switches */ +static const struct snd_kcontrol_new hfdacl_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 2, 1, 0); + +static const struct snd_kcontrol_new hfdacr_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0); + +/* Headset driver switches */ +static const struct snd_kcontrol_new hsl_driver_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 2, 1, 0); + +static const struct snd_kcontrol_new hsr_driver_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 2, 1, 0); + +/* Handsfree driver switches */ +static const struct snd_kcontrol_new hfl_driver_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 4, 1, 0); + +static const struct snd_kcontrol_new hfr_driver_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 4, 1, 0); + +static const struct snd_kcontrol_new twl6040_snd_controls[] = { + /* Capture gains */ + SOC_DOUBLE_TLV("Capture Preamplifier Volume", + TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv), + SOC_DOUBLE_TLV("Capture Volume", + TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv), + + /* Playback gains */ + SOC_DOUBLE_TLV("Headset Playback Volume", + TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), + SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), + +}; + +static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MAINMIC"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("SUBMIC"), + SND_SOC_DAPM_INPUT("AFML"), + SND_SOC_DAPM_INPUT("AFMR"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + + /* Analog input muxes for the capture amplifiers */ + SND_SOC_DAPM_MUX("Analog Left Capture Route", + SND_SOC_NOPM, 0, 0, &amicl_control), + SND_SOC_DAPM_MUX("Analog Right Capture Route", + SND_SOC_NOPM, 0, 0, &amicr_control), + + /* Analog capture PGAs */ + SND_SOC_DAPM_PGA("MicAmpL", + TWL6040_REG_MICLCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("MicAmpR", + TWL6040_REG_MICRCTL, 0, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture", + TWL6040_REG_MICLCTL, 2, 0), + SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture", + TWL6040_REG_MICRCTL, 2, 0), + + /* Microphone bias */ + SND_SOC_DAPM_MICBIAS("Headset Mic Bias", + TWL6040_REG_AMICBCTL, 0, 0), + SND_SOC_DAPM_MICBIAS("Main Mic Bias", + TWL6040_REG_AMICBCTL, 4, 0), + SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias", + TWL6040_REG_DMICBCTL, 0, 0), + SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias", + TWL6040_REG_DMICBCTL, 4, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("HSDAC Left", "Headset Playback", + TWL6040_REG_HSLCTL, 0, 0), + SND_SOC_DAPM_DAC("HSDAC Right", "Headset Playback", + TWL6040_REG_HSRCTL, 0, 0), + SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback", + TWL6040_REG_HFLCTL, 0, 0, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback", + TWL6040_REG_HFRCTL, 0, 0, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Analog playback switches */ + SND_SOC_DAPM_SWITCH("HSDAC Left Playback", + SND_SOC_NOPM, 0, 0, &hsdacl_switch_controls), + SND_SOC_DAPM_SWITCH("HSDAC Right Playback", + SND_SOC_NOPM, 0, 0, &hsdacr_switch_controls), + SND_SOC_DAPM_SWITCH("HFDAC Left Playback", + SND_SOC_NOPM, 0, 0, &hfdacl_switch_controls), + SND_SOC_DAPM_SWITCH("HFDAC Right Playback", + SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls), + + SND_SOC_DAPM_SWITCH("Headset Left Driver", + SND_SOC_NOPM, 0, 0, &hsl_driver_switch_controls), + SND_SOC_DAPM_SWITCH("Headset Right Driver", + SND_SOC_NOPM, 0, 0, &hsr_driver_switch_controls), + SND_SOC_DAPM_SWITCH_E("Handsfree Left Driver", + SND_SOC_NOPM, 0, 0, &hfl_driver_switch_controls, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH_E("Handsfree Right Driver", + SND_SOC_NOPM, 0, 0, &hfr_driver_switch_controls, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Analog playback PGAs */ + SND_SOC_DAPM_PGA("HFDAC Left PGA", + TWL6040_REG_HFLCTL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HFDAC Right PGA", + TWL6040_REG_HFRCTL, 1, 0, NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Capture path */ + {"Analog Left Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Left Capture Route", "Main Mic", "MAINMIC"}, + {"Analog Left Capture Route", "Aux/FM Left", "AFML"}, + + {"Analog Right Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Right Capture Route", "Sub Mic", "SUBMIC"}, + {"Analog Right Capture Route", "Aux/FM Right", "AFMR"}, + + {"MicAmpL", NULL, "Analog Left Capture Route"}, + {"MicAmpR", NULL, "Analog Right Capture Route"}, + + {"ADC Left", NULL, "MicAmpL"}, + {"ADC Right", NULL, "MicAmpR"}, + + /* Headset playback path */ + {"HSDAC Left Playback", "Switch", "HSDAC Left"}, + {"HSDAC Right Playback", "Switch", "HSDAC Right"}, + + {"Headset Left Driver", "Switch", "HSDAC Left Playback"}, + {"Headset Right Driver", "Switch", "HSDAC Right Playback"}, + + {"HSOL", NULL, "Headset Left Driver"}, + {"HSOR", NULL, "Headset Right Driver"}, + + /* Handsfree playback path */ + {"HFDAC Left Playback", "Switch", "HFDAC Left"}, + {"HFDAC Right Playback", "Switch", "HFDAC Right"}, + + {"HFDAC Left PGA", NULL, "HFDAC Left Playback"}, + {"HFDAC Right PGA", NULL, "HFDAC Right Playback"}, + + {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"}, + {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"}, + + {"HFL", NULL, "Handsfree Left Driver"}, + {"HFR", NULL, "Handsfree Right Driver"}, +}; + +static int twl6040_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, twl6040_dapm_widgets, + ARRAY_SIZE(twl6040_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int twl6040_power_up_completion(struct snd_soc_codec *codec, + int naudint) +{ + struct twl6040_data *priv = codec->private_data; + int time_left; + u8 intid; + + time_left = wait_for_completion_timeout(&priv->ready, + msecs_to_jiffies(48)); + + if (!time_left) { + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, + TWL6040_REG_INTID); + if (!(intid & TWL6040_READYINT)) { + dev_err(codec->dev, "timeout waiting for READYINT\n"); + return -ETIMEDOUT; + } + } + + priv->codec_powered = 1; + + return 0; +} + +static int twl6040_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct twl6040_data *priv = codec->private_data; + int audpwron = priv->audpwron; + int naudint = priv->naudint; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (priv->codec_powered) + break; + + if (gpio_is_valid(audpwron)) { + /* use AUDPWRON line */ + gpio_set_value(audpwron, 1); + + /* wait for power-up completion */ + ret = twl6040_power_up_completion(codec, naudint); + if (ret) + return ret; + + /* sync registers updated during power-up sequence */ + twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL); + } else { + /* use manual power-up sequence */ + twl6040_power_up(codec); + priv->codec_powered = 1; + } + + /* initialize vdd/vss registers with reg_cache */ + twl6040_init_vdd_regs(codec); + break; + case SND_SOC_BIAS_OFF: + if (!priv->codec_powered) + break; + + if (gpio_is_valid(audpwron)) { + /* use AUDPWRON line */ + gpio_set_value(audpwron, 0); + + /* power-down sequence latency */ + udelay(500); + + /* sync registers updated during power-down sequence */ + twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL); + twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL, + 0x00); + } else { + /* use manual power-down sequence */ + twl6040_power_down(codec); + } + + priv->codec_powered = 0; + break; + } + + codec->bias_level = level; + + return 0; +} + +/* set of rates for each pll: low-power and high-performance */ + +static unsigned int lp_rates[] = { + 88200, + 96000, +}; + +static struct snd_pcm_hw_constraint_list lp_constraints = { + .count = ARRAY_SIZE(lp_rates), + .list = lp_rates, +}; + +static unsigned int hp_rates[] = { + 96000, +}; + +static struct snd_pcm_hw_constraint_list hp_constraints = { + .count = ARRAY_SIZE(hp_rates), + .list = hp_rates, +}; + +static int twl6040_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 twl6040_data *priv = codec->private_data; + + if (!priv->sysclk) { + dev_err(codec->dev, + "no mclk configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + /* + * capture is not supported at 17.64 MHz, + * it's reserved for headset low-power playback scenario + */ + if ((priv->sysclk == 17640000) && substream->stream) { + dev_err(codec->dev, + "capture mode is not supported at %dHz\n", + priv->sysclk); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + priv->sysclk_constraints); + + return 0; +} + +static int twl6040_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct twl6040_data *priv = codec->private_data; + u8 lppllctl; + int rate; + + /* nothing to do for high-perf pll, it supports only 48 kHz */ + if (priv->pll == TWL6040_HPPLL_ID) + return 0; + + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + + rate = params_rate(params); + switch (rate) { + case 88200: + lppllctl |= TWL6040_LPLLFIN; + priv->sysclk = 17640000; + break; + case 96000: + lppllctl &= ~TWL6040_LPLLFIN; + priv->sysclk = 19200000; + break; + default: + dev_err(codec->dev, "unsupported rate %d\n", rate); + return -EINVAL; + } + + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + return 0; +} + +static int twl6040_trigger(struct snd_pcm_substream *substream, + int cmd, 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 twl6040_data *priv = codec->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* + * low-power playback mode is restricted + * for headset path only + */ + if ((priv->sysclk == 17640000) && priv->non_lp) { + dev_err(codec->dev, + "some enabled paths aren't supported at %dHz\n", + priv->sysclk); + return -EPERM; + } + break; + default: + break; + } + + return 0; +} + +static int twl6040_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 twl6040_data *priv = codec->private_data; + u8 hppllctl, lppllctl; + + hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + + switch (clk_id) { + case TWL6040_SYSCLK_SEL_LPPLL: + switch (freq) { + case 32768: + /* headset dac and driver must be in low-power mode */ + headset_power_mode(codec, 0); + + /* clk32k input requires low-power pll */ + lppllctl |= TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + mdelay(5); + lppllctl &= ~TWL6040_HPLLSEL; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + hppllctl &= ~TWL6040_HPLLENA; + twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); + break; + default: + dev_err(codec->dev, "unknown mclk freq %d\n", freq); + return -EINVAL; + } + + /* lppll divider */ + switch (priv->sysclk) { + case 17640000: + lppllctl |= TWL6040_LPLLFIN; + break; + case 19200000: + lppllctl &= ~TWL6040_LPLLFIN; + break; + default: + /* sysclk not yet configured */ + lppllctl &= ~TWL6040_LPLLFIN; + priv->sysclk = 19200000; + break; + } + + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + priv->pll = TWL6040_LPPLL_ID; + priv->sysclk_constraints = &lp_constraints; + break; + case TWL6040_SYSCLK_SEL_HPPLL: + hppllctl &= ~TWL6040_MCLK_MSK; + + switch (freq) { + case 12000000: + /* mclk input, pll enabled */ + hppllctl |= TWL6040_MCLK_12000KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLENA; + break; + case 19200000: + /* mclk input, pll disabled */ + hppllctl |= TWL6040_MCLK_19200KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLBP; + break; + case 26000000: + /* mclk input, pll enabled */ + hppllctl |= TWL6040_MCLK_26000KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLENA; + break; + case 38400000: + /* clk slicer, pll disabled */ + hppllctl |= TWL6040_MCLK_38400KHZ | + TWL6040_HPLLSQRENA | + TWL6040_HPLLBP; + break; + default: + dev_err(codec->dev, "unknown mclk freq %d\n", freq); + return -EINVAL; + } + + /* headset dac and driver must be in high-performance mode */ + headset_power_mode(codec, 1); + + twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); + udelay(500); + lppllctl |= TWL6040_HPLLSEL; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + lppllctl &= ~TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + /* high-performance pll can provide only 19.2 MHz */ + priv->pll = TWL6040_HPPLL_ID; + priv->sysclk = 19200000; + priv->sysclk_constraints = &hp_constraints; + break; + default: + dev_err(codec->dev, "unknown clk_id %d\n", clk_id); + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops twl6040_dai_ops = { + .startup = twl6040_startup, + .hw_params = twl6040_hw_params, + .trigger = twl6040_trigger, + .set_sysclk = twl6040_set_dai_sysclk, +}; + +struct snd_soc_dai twl6040_dai = { + .name = "twl6040", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}; +EXPORT_SYMBOL_GPL(twl6040_dai); + +#ifdef CONFIG_PM +static int twl6040_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; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int twl6040_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + twl6040_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} +#else +#define twl6040_suspend NULL +#define twl6040_resume NULL +#endif + +static struct snd_soc_codec *twl6040_codec; + +static int twl6040_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + BUG_ON(!twl6040_codec); + + codec = twl6040_codec; + socdev->card->codec = codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + return ret; + } + + snd_soc_add_controls(codec, twl6040_snd_controls, + ARRAY_SIZE(twl6040_snd_controls)); + twl6040_add_widgets(codec); + + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; +} + +static int twl6040_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_twl6040 = { + .probe = twl6040_probe, + .remove = twl6040_remove, + .suspend = twl6040_suspend, + .resume = twl6040_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_twl6040); + +static int __devinit twl6040_codec_probe(struct platform_device *pdev) +{ + struct twl4030_codec_data *twl_codec = pdev->dev.platform_data; + struct snd_soc_codec *codec; + struct twl6040_data *priv; + int audpwron, naudint; + int ret = 0; + + priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + if (twl_codec) { + audpwron = twl_codec->audpwron_gpio; + naudint = twl_codec->naudint_irq; + } else { + audpwron = -EINVAL; + naudint = 0; + } + + priv->audpwron = audpwron; + priv->naudint = naudint; + + codec = &priv->codec; + codec->dev = &pdev->dev; + twl6040_dai.dev = &pdev->dev; + + codec->name = "twl6040"; + codec->owner = THIS_MODULE; + codec->read = twl6040_read_reg_cache; + codec->write = twl6040_write; + codec->set_bias_level = twl6040_set_bias_level; + codec->private_data = priv; + codec->dai = &twl6040_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(twl6040_reg); + codec->reg_cache = kmemdup(twl6040_reg, sizeof(twl6040_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + init_completion(&priv->ready); + + if (gpio_is_valid(audpwron)) { + ret = gpio_request(audpwron, "audpwron"); + if (ret) + goto gpio1_err; + + ret = gpio_direction_output(audpwron, 0); + if (ret) + goto gpio2_err; + + priv->codec_powered = 0; + } + + if (naudint) { + /* audio interrupt */ + ret = request_threaded_irq(naudint, NULL, + twl6040_naudint_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "twl6040_codec", codec); + if (ret) + goto gpio2_err; + } else { + if (gpio_is_valid(audpwron)) { + /* enable only codec ready interrupt */ + twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, + ~TWL6040_READYMSK & TWL6040_ALLINT_MSK); + } else { + /* no interrupts at all */ + twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, + TWL6040_ALLINT_MSK); + } + } + + /* init vio registers */ + twl6040_init_vio_regs(codec); + + /* power on device */ + ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto irq_err; + + ret = snd_soc_register_codec(codec); + if (ret) + goto reg_err; + + twl6040_codec = codec; + + ret = snd_soc_register_dai(&twl6040_dai); + if (ret) + goto dai_err; + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); + twl6040_codec = NULL; +reg_err: + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); +irq_err: + if (naudint) + free_irq(naudint, codec); +gpio2_err: + if (gpio_is_valid(audpwron)) + gpio_free(audpwron); +gpio1_err: + kfree(codec->reg_cache); +cache_err: + kfree(priv); + return ret; +} + +static int __devexit twl6040_codec_remove(struct platform_device *pdev) +{ + struct twl6040_data *priv = twl6040_codec->private_data; + int audpwron = priv->audpwron; + int naudint = priv->naudint; + + if (gpio_is_valid(audpwron)) + gpio_free(audpwron); + + if (naudint) + free_irq(naudint, twl6040_codec); + + snd_soc_unregister_dai(&twl6040_dai); + snd_soc_unregister_codec(twl6040_codec); + + kfree(twl6040_codec); + twl6040_codec = NULL; + + return 0; +} + +static struct platform_driver twl6040_codec_driver = { + .driver = { + .name = "twl6040_codec", + .owner = THIS_MODULE, + }, + .probe = twl6040_codec_probe, + .remove = __devexit_p(twl6040_codec_remove), +}; + +static int __init twl6040_codec_init(void) +{ + return platform_driver_register(&twl6040_codec_driver); +} +module_init(twl6040_codec_init); + +static void __exit twl6040_codec_exit(void) +{ + platform_driver_unregister(&twl6040_codec_driver); +} +module_exit(twl6040_codec_exit); + +MODULE_DESCRIPTION("ASoC TWL6040 codec driver"); +MODULE_AUTHOR("Misael Lopez Cruz"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h new file mode 100644 index 00000000000..c472070a1da --- /dev/null +++ b/sound/soc/codecs/twl6040.h @@ -0,0 +1,141 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TWL6040_H__ +#define __TWL6040_H__ + +#define TWL6040_REG_ASICID 0x01 +#define TWL6040_REG_ASICREV 0x02 +#define TWL6040_REG_INTID 0x03 +#define TWL6040_REG_INTMR 0x04 +#define TWL6040_REG_NCPCTL 0x05 +#define TWL6040_REG_LDOCTL 0x06 +#define TWL6040_REG_HPPLLCTL 0x07 +#define TWL6040_REG_LPPLLCTL 0x08 +#define TWL6040_REG_LPPLLDIV 0x09 +#define TWL6040_REG_AMICBCTL 0x0A +#define TWL6040_REG_DMICBCTL 0x0B +#define TWL6040_REG_MICLCTL 0x0C +#define TWL6040_REG_MICRCTL 0x0D +#define TWL6040_REG_MICGAIN 0x0E +#define TWL6040_REG_LINEGAIN 0x0F +#define TWL6040_REG_HSLCTL 0x10 +#define TWL6040_REG_HSRCTL 0x11 +#define TWL6040_REG_HSGAIN 0x12 +#define TWL6040_REG_EARCTL 0x13 +#define TWL6040_REG_HFLCTL 0x14 +#define TWL6040_REG_HFLGAIN 0x15 +#define TWL6040_REG_HFRCTL 0x16 +#define TWL6040_REG_HFRGAIN 0x17 +#define TWL6040_REG_VIBCTLL 0x18 +#define TWL6040_REG_VIBDATL 0x19 +#define TWL6040_REG_VIBCTLR 0x1A +#define TWL6040_REG_VIBDATR 0x1B +#define TWL6040_REG_HKCTL1 0x1C +#define TWL6040_REG_HKCTL2 0x1D +#define TWL6040_REG_GPOCTL 0x1E +#define TWL6040_REG_ALB 0x1F +#define TWL6040_REG_DLB 0x20 +#define TWL6040_REG_TRIM1 0x28 +#define TWL6040_REG_TRIM2 0x29 +#define TWL6040_REG_TRIM3 0x2A +#define TWL6040_REG_HSOTRIM 0x2B +#define TWL6040_REG_HFOTRIM 0x2C +#define TWL6040_REG_ACCCTL 0x2D +#define TWL6040_REG_STATUS 0x2E + +#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1) + +#define TWL6040_VIOREGNUM 18 +#define TWL6040_VDDREGNUM 21 + +/* INTID (0x03) fields */ + +#define TWL6040_THINT 0x01 +#define TWL6040_PLUGINT 0x02 +#define TWL6040_UNPLUGINT 0x04 +#define TWL6040_HOOKINT 0x08 +#define TWL6040_HFINT 0x10 +#define TWL6040_VIBINT 0x20 +#define TWL6040_READYINT 0x40 + +/* INTMR (0x04) fields */ + +#define TWL6040_READYMSK 0x40 +#define TWL6040_ALLINT_MSK 0x7B + +/* NCPCTL (0x05) fields */ + +#define TWL6040_NCPENA 0x01 +#define TWL6040_NCPOPEN 0x40 + +/* LDOCTL (0x06) fields */ + +#define TWL6040_LSLDOENA 0x01 +#define TWL6040_HSLDOENA 0x04 +#define TWL6040_REFENA 0x40 +#define TWL6040_OSCENA 0x80 + +/* HPPLLCTL (0x07) fields */ + +#define TWL6040_HPLLENA 0x01 +#define TWL6040_HPLLRST 0x02 +#define TWL6040_HPLLBP 0x04 +#define TWL6040_HPLLSQRENA 0x08 +#define TWL6040_HPLLSQRBP 0x10 +#define TWL6040_MCLK_12000KHZ (0 << 5) +#define TWL6040_MCLK_19200KHZ (1 << 5) +#define TWL6040_MCLK_26000KHZ (2 << 5) +#define TWL6040_MCLK_38400KHZ (3 << 5) +#define TWL6040_MCLK_MSK 0x60 + +/* LPPLLCTL (0x08) fields */ + +#define TWL6040_LPLLENA 0x01 +#define TWL6040_LPLLRST 0x02 +#define TWL6040_LPLLSEL 0x04 +#define TWL6040_LPLLFIN 0x08 +#define TWL6040_HPLLSEL 0x10 + +/* HSLCTL (0x10) fields */ + +#define TWL6040_HSDACMODEL 0x02 +#define TWL6040_HSDRVMODEL 0x08 + +/* HSRCTL (0x11) fields */ + +#define TWL6040_HSDACMODER 0x02 +#define TWL6040_HSDRVMODER 0x08 + +/* ACCCTL (0x2D) fields */ + +#define TWL6040_RESETSPLIT 0x04 + +#define TWL6040_SYSCLK_SEL_LPPLL 1 +#define TWL6040_SYSCLK_SEL_HPPLL 2 + +#define TWL6040_HPPLL_ID 1 +#define TWL6040_LPPLL_ID 2 + +extern struct snd_soc_dai twl6040_dai; +extern struct snd_soc_codec_device soc_codec_dev_twl6040; + +#endif /* End of __TWL6040_H__ */ -- cgit v1.2.3-18-g5258 From a1533d94c6b930ef4263941adfd1e1132ca95627 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Fri, 19 Mar 2010 14:48:33 +0800 Subject: ASoC: rename ad1938 to ad193x and add support for ad1936/7/8/9 Signed-off-by: Barry Song Signed-off-by: Yi Li Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 +- sound/soc/codecs/Makefile | 4 +- sound/soc/codecs/ad1938.c | 521 ------------------------------------------ sound/soc/codecs/ad1938.h | 100 --------- sound/soc/codecs/ad193x.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ad193x.h | 83 +++++++ 6 files changed, 647 insertions(+), 625 deletions(-) delete mode 100644 sound/soc/codecs/ad1938.c delete mode 100644 sound/soc/codecs/ad1938.h create mode 100644 sound/soc/codecs/ad193x.c create mode 100644 sound/soc/codecs/ad193x.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 398cbb0e79f..bc0ab47e156 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -13,7 +13,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS select SND_SOC_AD1836 if SPI_MASTER - select SND_SOC_AD1938 if SPI_MASTER + select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_ADS117X select SND_SOC_AD73311 if I2C @@ -92,7 +92,7 @@ config SND_SOC_AC97_CODEC config SND_SOC_AD1836 tristate -config SND_SOC_AD1938 +config SND_SOC_AD193X tristate config SND_SOC_AD1980 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 98bd10c8fd3..33790416735 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,6 @@ snd-soc-ac97-objs := ac97.o snd-soc-ad1836-objs := ad1836.o -snd-soc-ad1938-objs := ad1938.o +snd-soc-ad193x-objs := ad193x.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ads117x-objs := ads117x.o @@ -64,7 +64,7 @@ snd-soc-wm2000-objs := wm2000.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o -obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o +obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c deleted file mode 100644 index c233810d463..00000000000 --- a/sound/soc/codecs/ad1938.c +++ /dev/null @@ -1,521 +0,0 @@ -/* - * File: sound/soc/codecs/ad1938.c - * Author: Barry Song - * - * Created: June 04 2009 - * Description: Driver for AD1938 sound chip - * - * Modified: - * Copyright 2009 Analog Devices Inc. - * - * Bugs: Enter bugs at http://blackfin.uclinux.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. - * - * 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, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ad1938.h" - -/* codec private data */ -struct ad1938_priv { - struct snd_soc_codec codec; - u8 reg_cache[AD1938_NUM_REGS]; -}; - -/* ad1938 register cache & default register settings */ -static const u8 ad1938_reg[AD1938_NUM_REGS] = { - 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, -}; - -static struct snd_soc_codec *ad1938_codec; -struct snd_soc_codec_device soc_codec_dev_ad1938; -static int ad1938_register(struct ad1938_priv *ad1938); -static void ad1938_unregister(struct ad1938_priv *ad1938); - -/* - * AD1938 volume/mute/de-emphasis etc. controls - */ -static const char *ad1938_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; - -static const struct soc_enum ad1938_deemp_enum = - SOC_ENUM_SINGLE(AD1938_DAC_CTRL2, 1, 4, ad1938_deemp); - -static const struct snd_kcontrol_new ad1938_snd_controls[] = { - /* DAC volume control */ - SOC_DOUBLE_R("DAC1 Volume", AD1938_DAC_L1_VOL, - AD1938_DAC_R1_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC2 Volume", AD1938_DAC_L2_VOL, - AD1938_DAC_R2_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC3 Volume", AD1938_DAC_L3_VOL, - AD1938_DAC_R3_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC4 Volume", AD1938_DAC_L4_VOL, - AD1938_DAC_R4_VOL, 0, 0xFF, 1), - - /* ADC switch control */ - SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, - AD1938_ADCR1_MUTE, 1, 1), - SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, - AD1938_ADCR2_MUTE, 1, 1), - - /* DAC switch control */ - SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, - AD1938_DACR1_MUTE, 1, 1), - SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, - AD1938_DACR2_MUTE, 1, 1), - SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, - AD1938_DACR3_MUTE, 1, 1), - SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, - AD1938_DACR4_MUTE, 1, 1), - - /* ADC high-pass filter */ - SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0, - AD1938_ADC_HIGHPASS_FILTER, 1, 0), - - /* DAC de-emphasis */ - SOC_ENUM("Playback Deemphasis", ad1938_deemp_enum), -}; - -static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = { - SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1), - SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_SUPPLY("PLL_PWR", AD1938_PLL_CLK_CTRL0, 0, 1, NULL, 0), - SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0), - SND_SOC_DAPM_OUTPUT("DAC1OUT"), - SND_SOC_DAPM_OUTPUT("DAC2OUT"), - SND_SOC_DAPM_OUTPUT("DAC3OUT"), - SND_SOC_DAPM_OUTPUT("DAC4OUT"), - SND_SOC_DAPM_INPUT("ADC1IN"), - SND_SOC_DAPM_INPUT("ADC2IN"), -}; - -static const struct snd_soc_dapm_route audio_paths[] = { - { "DAC", NULL, "PLL_PWR" }, - { "ADC", NULL, "PLL_PWR" }, - { "DAC", NULL, "ADC_PWR" }, - { "ADC", NULL, "ADC_PWR" }, - { "DAC1OUT", "DAC1 Switch", "DAC" }, - { "DAC2OUT", "DAC2 Switch", "DAC" }, - { "DAC3OUT", "DAC3 Switch", "DAC" }, - { "DAC4OUT", "DAC4 Switch", "DAC" }, - { "ADC", "ADC1 Switch", "ADC1IN" }, - { "ADC", "ADC2 Switch", "ADC2IN" }, -}; - -/* - * DAI ops entries - */ - -static int ad1938_mute(struct snd_soc_dai *dai, int mute) -{ - struct snd_soc_codec *codec = dai->codec; - int reg; - - reg = snd_soc_read(codec, AD1938_DAC_CTRL2); - reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & - (~AD1938_DAC_MASTER_MUTE); - snd_soc_write(codec, AD1938_DAC_CTRL2, reg); - - return 0; -} - -static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, - unsigned int rx_mask, int slots, int width) -{ - struct snd_soc_codec *codec = dai->codec; - int dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1); - int adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2); - - dac_reg &= ~AD1938_DAC_CHAN_MASK; - adc_reg &= ~AD1938_ADC_CHAN_MASK; - - switch (slots) { - case 2: - dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 4: - dac_reg |= AD1938_DAC_4_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_4_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 8: - dac_reg |= AD1938_DAC_8_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_8_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 16: - dac_reg |= AD1938_DAC_16_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_16_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - default: - return -EINVAL; - } - - snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg); - snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg); - - return 0; -} - -static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai, - unsigned int fmt) -{ - struct snd_soc_codec *codec = codec_dai->codec; - int adc_reg, dac_reg; - - adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2); - dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1); - - /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S - * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) - */ - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - adc_reg &= ~AD1938_ADC_SERFMT_MASK; - adc_reg |= AD1938_ADC_SERFMT_TDM; - break; - case SND_SOC_DAIFMT_DSP_A: - adc_reg &= ~AD1938_ADC_SERFMT_MASK; - adc_reg |= AD1938_ADC_SERFMT_AUX; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ - adc_reg &= ~AD1938_ADC_LEFT_HIGH; - adc_reg &= ~AD1938_ADC_BCLK_INV; - dac_reg &= ~AD1938_DAC_LEFT_HIGH; - dac_reg &= ~AD1938_DAC_BCLK_INV; - break; - case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ - adc_reg |= AD1938_ADC_LEFT_HIGH; - adc_reg &= ~AD1938_ADC_BCLK_INV; - dac_reg |= AD1938_DAC_LEFT_HIGH; - dac_reg &= ~AD1938_DAC_BCLK_INV; - break; - case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ - adc_reg &= ~AD1938_ADC_LEFT_HIGH; - adc_reg |= AD1938_ADC_BCLK_INV; - dac_reg &= ~AD1938_DAC_LEFT_HIGH; - dac_reg |= AD1938_DAC_BCLK_INV; - break; - - case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ - adc_reg |= AD1938_ADC_LEFT_HIGH; - adc_reg |= AD1938_ADC_BCLK_INV; - dac_reg |= AD1938_DAC_LEFT_HIGH; - dac_reg |= AD1938_DAC_BCLK_INV; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ - adc_reg |= AD1938_ADC_LCR_MASTER; - adc_reg |= AD1938_ADC_BCLK_MASTER; - dac_reg |= AD1938_DAC_LCR_MASTER; - dac_reg |= AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ - adc_reg |= AD1938_ADC_LCR_MASTER; - adc_reg &= ~AD1938_ADC_BCLK_MASTER; - dac_reg |= AD1938_DAC_LCR_MASTER; - dac_reg &= ~AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ - adc_reg &= ~AD1938_ADC_LCR_MASTER; - adc_reg |= AD1938_ADC_BCLK_MASTER; - dac_reg &= ~AD1938_DAC_LCR_MASTER; - dac_reg |= AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ - adc_reg &= ~AD1938_ADC_LCR_MASTER; - adc_reg &= ~AD1938_ADC_BCLK_MASTER; - dac_reg &= ~AD1938_DAC_LCR_MASTER; - dac_reg &= ~AD1938_DAC_BCLK_MASTER; - break; - default: - return -EINVAL; - } - - snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg); - snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg); - - return 0; -} - -static int ad1938_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - int word_len = 0, reg = 0; - - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->card->codec; - - /* bit size */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - word_len = 3; - break; - case SNDRV_PCM_FORMAT_S20_3LE: - word_len = 1; - break; - case SNDRV_PCM_FORMAT_S24_LE: - case SNDRV_PCM_FORMAT_S32_LE: - word_len = 0; - break; - } - - reg = snd_soc_read(codec, AD1938_DAC_CTRL2); - reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len; - snd_soc_write(codec, AD1938_DAC_CTRL2, reg); - - reg = snd_soc_read(codec, AD1938_ADC_CTRL1); - reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len; - snd_soc_write(codec, AD1938_ADC_CTRL1, reg); - - return 0; -} - -static int __devinit ad1938_spi_probe(struct spi_device *spi) -{ - struct snd_soc_codec *codec; - struct ad1938_priv *ad1938; - - ad1938 = kzalloc(sizeof(struct ad1938_priv), GFP_KERNEL); - if (ad1938 == NULL) - return -ENOMEM; - - codec = &ad1938->codec; - codec->control_data = spi; - codec->dev = &spi->dev; - - dev_set_drvdata(&spi->dev, ad1938); - - return ad1938_register(ad1938); -} - -static int __devexit ad1938_spi_remove(struct spi_device *spi) -{ - struct ad1938_priv *ad1938 = dev_get_drvdata(&spi->dev); - - ad1938_unregister(ad1938); - return 0; -} - -static struct spi_driver ad1938_spi_driver = { - .driver = { - .name = "ad1938", - .owner = THIS_MODULE, - }, - .probe = ad1938_spi_probe, - .remove = __devexit_p(ad1938_spi_remove), -}; - -static struct snd_soc_dai_ops ad1938_dai_ops = { - .hw_params = ad1938_hw_params, - .digital_mute = ad1938_mute, - .set_tdm_slot = ad1938_set_tdm_slot, - .set_fmt = ad1938_set_dai_fmt, -}; - -/* codec DAI instance */ -struct snd_soc_dai ad1938_dai = { - .name = "AD1938", - .playback = { - .stream_name = "Playback", - .channels_min = 2, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .ops = &ad1938_dai_ops, -}; -EXPORT_SYMBOL_GPL(ad1938_dai); - -static int ad1938_register(struct ad1938_priv *ad1938) -{ - int ret; - struct snd_soc_codec *codec = &ad1938->codec; - - if (ad1938_codec) { - dev_err(codec->dev, "Another ad1938 is registered\n"); - return -EINVAL; - } - - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ad1938; - codec->reg_cache = ad1938->reg_cache; - codec->reg_cache_size = AD1938_NUM_REGS; - codec->name = "AD1938"; - codec->owner = THIS_MODULE; - codec->dai = &ad1938_dai; - codec->num_dai = 1; - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - - ad1938_dai.dev = codec->dev; - ad1938_codec = codec; - - memcpy(codec->reg_cache, ad1938_reg, AD1938_NUM_REGS); - - ret = snd_soc_codec_set_cache_io(codec, 16, 8, SND_SOC_SPI); - if (ret < 0) { - dev_err(codec->dev, "failed to set cache I/O: %d\n", - ret); - kfree(ad1938); - return ret; - } - - /* default setting for ad1938 */ - - /* unmute dac channels */ - snd_soc_write(codec, AD1938_DAC_CHNL_MUTE, 0x0); - /* de-emphasis: 48kHz, powedown dac */ - snd_soc_write(codec, AD1938_DAC_CTRL2, 0x1A); - /* powerdown dac, dac in tdm mode */ - snd_soc_write(codec, AD1938_DAC_CTRL0, 0x41); - /* high-pass filter enable */ - snd_soc_write(codec, AD1938_ADC_CTRL0, 0x3); - /* sata delay=1, adc aux mode */ - snd_soc_write(codec, AD1938_ADC_CTRL1, 0x43); - /* pll input: mclki/xi */ - snd_soc_write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); - snd_soc_write(codec, AD1938_PLL_CLK_CTRL1, 0x04); - - ret = snd_soc_register_codec(codec); - if (ret != 0) { - dev_err(codec->dev, "Failed to register codec: %d\n", ret); - kfree(ad1938); - return ret; - } - - ret = snd_soc_register_dai(&ad1938_dai); - if (ret != 0) { - dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - kfree(ad1938); - return ret; - } - - return 0; -} - -static void ad1938_unregister(struct ad1938_priv *ad1938) -{ - snd_soc_unregister_dai(&ad1938_dai); - snd_soc_unregister_codec(&ad1938->codec); - kfree(ad1938); - ad1938_codec = NULL; -} - -static int ad1938_probe(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec; - int ret = 0; - - if (ad1938_codec == NULL) { - dev_err(&pdev->dev, "Codec device not registered\n"); - return -ENODEV; - } - - socdev->card->codec = ad1938_codec; - codec = ad1938_codec; - - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - dev_err(codec->dev, "failed to create pcms: %d\n", ret); - goto pcm_err; - } - - snd_soc_add_controls(codec, ad1938_snd_controls, - ARRAY_SIZE(ad1938_snd_controls)); - snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, - ARRAY_SIZE(ad1938_dapm_widgets)); - snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); - - -pcm_err: - return ret; -} - -/* power down chip */ -static int ad1938_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_ad1938 = { - .probe = ad1938_probe, - .remove = ad1938_remove, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938); - -static int __init ad1938_init(void) -{ - int ret; - - ret = spi_register_driver(&ad1938_spi_driver); - if (ret != 0) { - printk(KERN_ERR "Failed to register ad1938 SPI driver: %d\n", - ret); - } - - return ret; -} -module_init(ad1938_init); - -static void __exit ad1938_exit(void) -{ - spi_unregister_driver(&ad1938_spi_driver); -} -module_exit(ad1938_exit); - -MODULE_DESCRIPTION("ASoC ad1938 driver"); -MODULE_AUTHOR("Barry Song "); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1938.h b/sound/soc/codecs/ad1938.h deleted file mode 100644 index fe3c48cd2d5..00000000000 --- a/sound/soc/codecs/ad1938.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * File: sound/soc/codecs/ad1836.h - * Based on: - * Author: Barry Song - * - * Created: May 25, 2009 - * Description: definitions for AD1938 registers - * - * Modified: - * - * Bugs: Enter bugs at http://blackfin.uclinux.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. - * - * 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, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef __AD1938_H__ -#define __AD1938_H__ - -#define AD1938_PLL_CLK_CTRL0 0 -#define AD1938_PLL_POWERDOWN 0x01 -#define AD1938_PLL_CLK_CTRL1 1 -#define AD1938_DAC_CTRL0 2 -#define AD1938_DAC_POWERDOWN 0x01 -#define AD1938_DAC_SERFMT_MASK 0xC0 -#define AD1938_DAC_SERFMT_STEREO (0 << 6) -#define AD1938_DAC_SERFMT_TDM (1 << 6) -#define AD1938_DAC_CTRL1 3 -#define AD1938_DAC_2_CHANNELS 0 -#define AD1938_DAC_4_CHANNELS 1 -#define AD1938_DAC_8_CHANNELS 2 -#define AD1938_DAC_16_CHANNELS 3 -#define AD1938_DAC_CHAN_SHFT 1 -#define AD1938_DAC_CHAN_MASK (3 << AD1938_DAC_CHAN_SHFT) -#define AD1938_DAC_LCR_MASTER (1 << 4) -#define AD1938_DAC_BCLK_MASTER (1 << 5) -#define AD1938_DAC_LEFT_HIGH (1 << 3) -#define AD1938_DAC_BCLK_INV (1 << 7) -#define AD1938_DAC_CTRL2 4 -#define AD1938_DAC_WORD_LEN_MASK 0xC -#define AD1938_DAC_MASTER_MUTE 1 -#define AD1938_DAC_CHNL_MUTE 5 -#define AD1938_DACL1_MUTE 0 -#define AD1938_DACR1_MUTE 1 -#define AD1938_DACL2_MUTE 2 -#define AD1938_DACR2_MUTE 3 -#define AD1938_DACL3_MUTE 4 -#define AD1938_DACR3_MUTE 5 -#define AD1938_DACL4_MUTE 6 -#define AD1938_DACR4_MUTE 7 -#define AD1938_DAC_L1_VOL 6 -#define AD1938_DAC_R1_VOL 7 -#define AD1938_DAC_L2_VOL 8 -#define AD1938_DAC_R2_VOL 9 -#define AD1938_DAC_L3_VOL 10 -#define AD1938_DAC_R3_VOL 11 -#define AD1938_DAC_L4_VOL 12 -#define AD1938_DAC_R4_VOL 13 -#define AD1938_ADC_CTRL0 14 -#define AD1938_ADC_POWERDOWN 0x01 -#define AD1938_ADC_HIGHPASS_FILTER 1 -#define AD1938_ADCL1_MUTE 2 -#define AD1938_ADCR1_MUTE 3 -#define AD1938_ADCL2_MUTE 4 -#define AD1938_ADCR2_MUTE 5 -#define AD1938_ADC_CTRL1 15 -#define AD1938_ADC_SERFMT_MASK 0x60 -#define AD1938_ADC_SERFMT_STEREO (0 << 5) -#define AD1938_ADC_SERFMT_TDM (1 << 2) -#define AD1938_ADC_SERFMT_AUX (2 << 5) -#define AD1938_ADC_WORD_LEN_MASK 0x3 -#define AD1938_ADC_CTRL2 16 -#define AD1938_ADC_2_CHANNELS 0 -#define AD1938_ADC_4_CHANNELS 1 -#define AD1938_ADC_8_CHANNELS 2 -#define AD1938_ADC_16_CHANNELS 3 -#define AD1938_ADC_CHAN_SHFT 4 -#define AD1938_ADC_CHAN_MASK (3 << AD1938_ADC_CHAN_SHFT) -#define AD1938_ADC_LCR_MASTER (1 << 3) -#define AD1938_ADC_BCLK_MASTER (1 << 6) -#define AD1938_ADC_LEFT_HIGH (1 << 2) -#define AD1938_ADC_BCLK_INV (1 << 1) - -#define AD1938_NUM_REGS 17 - -extern struct snd_soc_dai ad1938_dai; -extern struct snd_soc_codec_device soc_codec_dev_ad1938; -#endif diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c new file mode 100644 index 00000000000..d7426990b66 --- /dev/null +++ b/sound/soc/codecs/ad193x.c @@ -0,0 +1,560 @@ +/* + * AD193X Audio Codec driver supporting AD1936/7/8/9 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ad193x.h" + +/* codec private data */ +struct ad193x_priv { + struct snd_soc_codec codec; + u8 reg_cache[AD193X_NUM_REGS]; +}; + +/* ad193x register cache & default register settings */ +static const u8 ad193x_reg[AD193X_NUM_REGS] = { + 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, +}; + +static struct snd_soc_codec *ad193x_codec; +struct snd_soc_codec_device soc_codec_dev_ad193x; +static int ad193x_register(struct ad193x_priv *ad193x, int bus_type); +static void ad193x_unregister(struct ad193x_priv *ad193x); + +/* + * AD193X volume/mute/de-emphasis etc. controls + */ +static const char *ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; + +static const struct soc_enum ad193x_deemp_enum = + SOC_ENUM_SINGLE(AD193X_DAC_CTRL2, 1, 4, ad193x_deemp); + +static const struct snd_kcontrol_new ad193x_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL, + AD193X_DAC_R1_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL, + AD193X_DAC_R2_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL, + AD193X_DAC_R3_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL, + AD193X_DAC_R4_VOL, 0, 0xFF, 1), + + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE, + AD193X_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE, + AD193X_ADCR2_MUTE, 1, 1), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE, + AD193X_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE, + AD193X_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE, + AD193X_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE, + AD193X_DACR4_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0, + AD193X_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "PLL_PWR" }, + { "ADC", NULL, "PLL_PWR" }, + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, + { "DAC1OUT", "DAC1 Switch", "DAC" }, + { "DAC2OUT", "DAC2 Switch", "DAC" }, + { "DAC3OUT", "DAC3 Switch", "DAC" }, + { "DAC4OUT", "DAC4 Switch", "DAC" }, + { "ADC", "ADC1 Switch", "ADC1IN" }, + { "ADC", "ADC2 Switch", "ADC2IN" }, +}; + +/* + * DAI ops entries + */ + +static int ad193x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); + reg = (mute > 0) ? reg | AD193X_DAC_MASTER_MUTE : reg & + (~AD193X_DAC_MASTER_MUTE); + snd_soc_write(codec, AD193X_DAC_CTRL2, reg); + + return 0; +} + +static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct snd_soc_codec *codec = dai->codec; + int dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1); + int adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2); + + dac_reg &= ~AD193X_DAC_CHAN_MASK; + adc_reg &= ~AD193X_ADC_CHAN_MASK; + + switch (slots) { + case 2: + dac_reg |= AD193X_DAC_2_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_2_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 4: + dac_reg |= AD193X_DAC_4_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_4_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 8: + dac_reg |= AD193X_DAC_8_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_8_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 16: + dac_reg |= AD193X_DAC_16_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_16_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg); + snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg); + + return 0; +} + +static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int adc_reg, dac_reg; + + adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2); + dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1); + + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S + * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adc_reg &= ~AD193X_ADC_SERFMT_MASK; + adc_reg |= AD193X_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_reg &= ~AD193X_ADC_SERFMT_MASK; + adc_reg |= AD193X_ADC_SERFMT_AUX; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + adc_reg &= ~AD193X_ADC_LEFT_HIGH; + adc_reg &= ~AD193X_ADC_BCLK_INV; + dac_reg &= ~AD193X_DAC_LEFT_HIGH; + dac_reg &= ~AD193X_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ + adc_reg |= AD193X_ADC_LEFT_HIGH; + adc_reg &= ~AD193X_ADC_BCLK_INV; + dac_reg |= AD193X_DAC_LEFT_HIGH; + dac_reg &= ~AD193X_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ + adc_reg &= ~AD193X_ADC_LEFT_HIGH; + adc_reg |= AD193X_ADC_BCLK_INV; + dac_reg &= ~AD193X_DAC_LEFT_HIGH; + dac_reg |= AD193X_DAC_BCLK_INV; + break; + + case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ + adc_reg |= AD193X_ADC_LEFT_HIGH; + adc_reg |= AD193X_ADC_BCLK_INV; + dac_reg |= AD193X_DAC_LEFT_HIGH; + dac_reg |= AD193X_DAC_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + adc_reg |= AD193X_ADC_LCR_MASTER; + adc_reg |= AD193X_ADC_BCLK_MASTER; + dac_reg |= AD193X_DAC_LCR_MASTER; + dac_reg |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + adc_reg |= AD193X_ADC_LCR_MASTER; + adc_reg &= ~AD193X_ADC_BCLK_MASTER; + dac_reg |= AD193X_DAC_LCR_MASTER; + dac_reg &= ~AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + adc_reg &= ~AD193X_ADC_LCR_MASTER; + adc_reg |= AD193X_ADC_BCLK_MASTER; + dac_reg &= ~AD193X_DAC_LCR_MASTER; + dac_reg |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + adc_reg &= ~AD193X_ADC_LCR_MASTER; + adc_reg &= ~AD193X_ADC_BCLK_MASTER; + dac_reg &= ~AD193X_DAC_LCR_MASTER; + dac_reg &= ~AD193X_DAC_BCLK_MASTER; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg); + snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg); + + return 0; +} + +static int ad193x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0, reg = 0; + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_len = 0; + break; + } + + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); + reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; + snd_soc_write(codec, AD193X_DAC_CTRL2, reg); + + reg = snd_soc_read(codec, AD193X_ADC_CTRL1); + reg = (reg & (~AD193X_ADC_WORD_LEN_MASK)) | word_len; + snd_soc_write(codec, AD193X_ADC_CTRL1, reg); + + return 0; +} + +int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) +{ + struct snd_soc_codec *codec; + struct ad193x_priv *ad193x; + + ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL); + if (ad193x == NULL) + return -ENOMEM; + + codec = &ad193x->codec; + codec->control_data = ctrl_data; + codec->dev = dev; + + dev_set_drvdata(dev, ad193x); + + return ad193x_register(ad193x, bus_type); +} +EXPORT_SYMBOL_GPL(ad193x_bus_probe); + +int ad193x_bus_remove(struct device *dev) +{ + struct ad193x_priv *ad193x = dev_get_drvdata(dev); + + ad193x_unregister(ad193x); + return 0; +} +EXPORT_SYMBOL_GPL(ad193x_bus_remove); + +static struct snd_soc_dai_ops ad193x_dai_ops = { + .hw_params = ad193x_hw_params, + .digital_mute = ad193x_mute, + .set_tdm_slot = ad193x_set_tdm_slot, + .set_fmt = ad193x_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad193x_dai = { + .name = "AD193X", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad193x_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad193x_dai); + +static int ad193x_register(struct ad193x_priv *ad193x, int bus_type) +{ + int ret; + struct snd_soc_codec *codec = &ad193x->codec; + + if (ad193x_codec) { + dev_err(codec->dev, "Another ad193x is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + codec->private_data = ad193x; + codec->reg_cache = ad193x->reg_cache; + codec->reg_cache_size = AD193X_NUM_REGS; + codec->name = "AD193X"; + codec->owner = THIS_MODULE; + codec->dai = &ad193x_dai; + codec->num_dai = 1; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ad193x_dai.dev = codec->dev; + ad193x_codec = codec; + + memcpy(codec->reg_cache, ad193x_reg, AD193X_NUM_REGS); + + if (bus_type == SND_SOC_I2C) + ret = snd_soc_codec_set_cache_io(codec, 8, 8, bus_type); + else + ret = snd_soc_codec_set_cache_io(codec, 16, 8, bus_type); + if (ret < 0) { + dev_err(codec->dev, "failed to set cache I/O: %d\n", + ret); + kfree(ad193x); + return ret; + } + + /* default setting for ad193x */ + + /* unmute dac channels */ + snd_soc_write(codec, AD193X_DAC_CHNL_MUTE, 0x0); + /* de-emphasis: 48kHz, powedown dac */ + snd_soc_write(codec, AD193X_DAC_CTRL2, 0x1A); + /* powerdown dac, dac in tdm mode */ + snd_soc_write(codec, AD193X_DAC_CTRL0, 0x41); + /* high-pass filter enable */ + snd_soc_write(codec, AD193X_ADC_CTRL0, 0x3); + /* sata delay=1, adc aux mode */ + snd_soc_write(codec, AD193X_ADC_CTRL1, 0x43); + /* pll input: mclki/xi */ + snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ + snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + kfree(ad193x); + return ret; + } + + ret = snd_soc_register_dai(&ad193x_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + kfree(ad193x); + return ret; + } + + return 0; +} + +static void ad193x_unregister(struct ad193x_priv *ad193x) +{ + snd_soc_unregister_dai(&ad193x_dai); + snd_soc_unregister_codec(&ad193x->codec); + kfree(ad193x); + ad193x_codec = NULL; +} + +static int ad193x_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad193x_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad193x_codec; + codec = ad193x_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, ad193x_snd_controls, + ARRAY_SIZE(ad193x_snd_controls)); + snd_soc_dapm_new_controls(codec, ad193x_dapm_widgets, + ARRAY_SIZE(ad193x_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + +pcm_err: + return ret; +} + +/* power down chip */ +static int ad193x_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_ad193x = { + .probe = ad193x_probe, + .remove = ad193x_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad193x); + +#if defined(CONFIG_SPI_MASTER) +static int __devinit ad193x_spi_probe(struct spi_device *spi) +{ + return ad193x_bus_probe(&spi->dev, spi, SND_SOC_SPI); +} + +static int __devexit ad193x_spi_remove(struct spi_device *spi) +{ + return ad193x_bus_remove(&spi->dev); +} + +static struct spi_driver ad193x_spi_driver = { + .driver = { + .name = "ad193x", + .owner = THIS_MODULE, + }, + .probe = ad193x_spi_probe, + .remove = __devexit_p(ad193x_spi_remove), +}; +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id ad193x_id[] = { + { "ad1936", 0 }, + { "ad1937", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad193x_id); + +static int __devinit ad193x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return ad193x_bus_probe(&client->dev, client, SND_SOC_I2C); +} + +static int __devexit ad193x_i2c_remove(struct i2c_client *client) +{ + return ad193x_bus_remove(&client->dev); +} + +static struct i2c_driver ad193x_i2c_driver = { + .driver = { + .name = "ad193x", + }, + .probe = ad193x_i2c_probe, + .remove = __devexit_p(ad193x_i2c_remove), + .id_table = ad193x_id, +}; +#endif + +static int __init ad193x_modinit(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&ad193x_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register AD193X I2C driver: %d\n", + ret); + } +#endif + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&ad193x_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register AD193X SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(ad193x_modinit); + +static void __exit ad193x_modexit(void) +{ +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&ad193x_spi_driver); +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ad193x_i2c_driver); +#endif +} +module_exit(ad193x_modexit); + +MODULE_DESCRIPTION("ASoC ad193x driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h new file mode 100644 index 00000000000..b240d1d67c2 --- /dev/null +++ b/sound/soc/codecs/ad193x.h @@ -0,0 +1,83 @@ +/* + * AD193X Audio Codec driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __AD193X_H__ +#define __AD193X_H__ + +#define AD193X_PLL_CLK_CTRL0 0x800 +#define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_CLK_CTRL1 0x801 +#define AD193X_DAC_CTRL0 0x802 +#define AD193X_DAC_POWERDOWN 0x01 +#define AD193X_DAC_SERFMT_MASK 0xC0 +#define AD193X_DAC_SERFMT_STEREO (0 << 6) +#define AD193X_DAC_SERFMT_TDM (1 << 6) +#define AD193X_DAC_CTRL1 0x803 +#define AD193X_DAC_2_CHANNELS 0 +#define AD193X_DAC_4_CHANNELS 1 +#define AD193X_DAC_8_CHANNELS 2 +#define AD193X_DAC_16_CHANNELS 3 +#define AD193X_DAC_CHAN_SHFT 1 +#define AD193X_DAC_CHAN_MASK (3 << AD193X_DAC_CHAN_SHFT) +#define AD193X_DAC_LCR_MASTER (1 << 4) +#define AD193X_DAC_BCLK_MASTER (1 << 5) +#define AD193X_DAC_LEFT_HIGH (1 << 3) +#define AD193X_DAC_BCLK_INV (1 << 7) +#define AD193X_DAC_CTRL2 0x804 +#define AD193X_DAC_WORD_LEN_MASK 0xC +#define AD193X_DAC_MASTER_MUTE 1 +#define AD193X_DAC_CHNL_MUTE 0x805 +#define AD193X_DACL1_MUTE 0 +#define AD193X_DACR1_MUTE 1 +#define AD193X_DACL2_MUTE 2 +#define AD193X_DACR2_MUTE 3 +#define AD193X_DACL3_MUTE 4 +#define AD193X_DACR3_MUTE 5 +#define AD193X_DACL4_MUTE 6 +#define AD193X_DACR4_MUTE 7 +#define AD193X_DAC_L1_VOL 0x806 +#define AD193X_DAC_R1_VOL 0x807 +#define AD193X_DAC_L2_VOL 0x808 +#define AD193X_DAC_R2_VOL 0x809 +#define AD193X_DAC_L3_VOL 0x80a +#define AD193X_DAC_R3_VOL 0x80b +#define AD193X_DAC_L4_VOL 0x80c +#define AD193X_DAC_R4_VOL 0x80d +#define AD193X_ADC_CTRL0 0x80e +#define AD193X_ADC_POWERDOWN 0x01 +#define AD193X_ADC_HIGHPASS_FILTER 1 +#define AD193X_ADCL1_MUTE 2 +#define AD193X_ADCR1_MUTE 3 +#define AD193X_ADCL2_MUTE 4 +#define AD193X_ADCR2_MUTE 5 +#define AD193X_ADC_CTRL1 0x80f +#define AD193X_ADC_SERFMT_MASK 0x60 +#define AD193X_ADC_SERFMT_STEREO (0 << 5) +#define AD193X_ADC_SERFMT_TDM (1 << 2) +#define AD193X_ADC_SERFMT_AUX (2 << 5) +#define AD193X_ADC_WORD_LEN_MASK 0x3 +#define AD193X_ADC_CTRL2 0x810 +#define AD193X_ADC_2_CHANNELS 0 +#define AD193X_ADC_4_CHANNELS 1 +#define AD193X_ADC_8_CHANNELS 2 +#define AD193X_ADC_16_CHANNELS 3 +#define AD193X_ADC_CHAN_SHFT 4 +#define AD193X_ADC_CHAN_MASK (3 << AD193X_ADC_CHAN_SHFT) +#define AD193X_ADC_LCR_MASTER (1 << 3) +#define AD193X_ADC_BCLK_MASTER (1 << 6) +#define AD193X_ADC_LEFT_HIGH (1 << 2) +#define AD193X_ADC_BCLK_INV (1 << 1) + +#define AD193X_NUM_REGS 17 + +extern struct snd_soc_dai ad193x_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad193x; +extern int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type); +extern int ad193x_bus_remove(struct device *dev); + +#endif -- cgit v1.2.3-18-g5258 From cffce322be94ecdd3bbb74dfd3f3834c51b4232a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 19 Mar 2010 12:22:03 +0000 Subject: ASoC: Unexport AD193x bus probe/remove functions The export is not needed since the per-bus code lives in the same module. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/ad193x.c | 6 ++---- sound/soc/codecs/ad193x.h | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index d7426990b66..d034464e26f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -285,7 +285,7 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, return 0; } -int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) +static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) { struct snd_soc_codec *codec; struct ad193x_priv *ad193x; @@ -302,16 +302,14 @@ int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) return ad193x_register(ad193x, bus_type); } -EXPORT_SYMBOL_GPL(ad193x_bus_probe); -int ad193x_bus_remove(struct device *dev) +static int ad193x_bus_remove(struct device *dev) { struct ad193x_priv *ad193x = dev_get_drvdata(dev); ad193x_unregister(ad193x); return 0; } -EXPORT_SYMBOL_GPL(ad193x_bus_remove); static struct snd_soc_dai_ops ad193x_dai_ops = { .hw_params = ad193x_hw_params, diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h index b240d1d67c2..a03c880d52f 100644 --- a/sound/soc/codecs/ad193x.h +++ b/sound/soc/codecs/ad193x.h @@ -77,7 +77,5 @@ extern struct snd_soc_dai ad193x_dai; extern struct snd_soc_codec_device soc_codec_dev_ad193x; -extern int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type); -extern int ad193x_bus_remove(struct device *dev); #endif -- cgit v1.2.3-18-g5258 From 698c3756660f7afd6fa5051f667a259a953eea77 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Fri, 19 Mar 2010 11:18:40 +0800 Subject: ASoC: change bf5xx-ad1938 machine driver to bf5xx-ad193x machine driver Signed-off-by: Barry Song <21cnbao@gmail.com> Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/blackfin/Kconfig | 9 ++- sound/soc/blackfin/Makefile | 4 +- sound/soc/blackfin/bf5xx-ad1938.c | 149 -------------------------------------- sound/soc/blackfin/bf5xx-ad193x.c | 149 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 155 deletions(-) delete mode 100644 sound/soc/blackfin/bf5xx-ad1938.c create mode 100644 sound/soc/blackfin/bf5xx-ad193x.c diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 97f1a251e44..8ef25025f3d 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -49,13 +49,14 @@ config SND_BF5XX_SOC_AD1836 help Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. -config SND_BF5XX_SOC_AD1938 - tristate "SoC AD1938 Audio support for Blackfin" +config SND_BF5XX_SOC_AD193X + tristate "SoC AD193X Audio support for Blackfin" depends on SND_BF5XX_TDM select SND_BF5XX_SOC_TDM - select SND_SOC_AD1938 + select SND_SOC_AD193X help - Say Y if you want to add support for AD1938 codec on Blackfin. + Say Y if you want to add support for AD193X codec on Blackfin. + This driver supports AD1936, AD1937, AD1938 and AD1939. config SND_BF5XX_AC97 tristate "SoC AC97 Audio for the ADI BF5xx chip" diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 87e30423912..49af3f32aec 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -20,10 +20,10 @@ snd-ad1836-objs := bf5xx-ad1836.o snd-ad1980-objs := bf5xx-ad1980.o snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o -snd-ad1938-objs := bf5xx-ad1938.o +snd-ad193x-objs := bf5xx-ad193x.o obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o -obj-$(CONFIG_SND_BF5XX_SOC_AD1938) += snd-ad1938.o +obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad1938.c deleted file mode 100644 index 2ef1e5013b8..00000000000 --- a/sound/soc/blackfin/bf5xx-ad1938.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * File: sound/soc/blackfin/bf5xx-ad1938.c - * Author: Barry Song - * - * Created: Thur June 4 2009 - * Description: Board driver for ad1938 sound chip - * - * Bugs: Enter bugs at http://blackfin.uclinux.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. - * - * 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, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "../codecs/ad1938.h" -#include "bf5xx-sport.h" - -#include "bf5xx-tdm-pcm.h" -#include "bf5xx-tdm.h" - -static struct snd_soc_card bf5xx_ad1938; - -static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - - cpu_dai->private_data = sport_handle; - return 0; -} - -static int bf5xx_ad1938_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->dai->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - unsigned int channel_map[] = {0, 1, 2, 3, 4, 5, 6, 7}; - int ret = 0; - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set codec DAI slots, 8 channels, all channels are enabled */ - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32); - if (ret < 0) - return ret; - - /* set cpu DAI channel mapping */ - ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map), - channel_map, ARRAY_SIZE(channel_map), channel_map); - if (ret < 0) - return ret; - - return 0; -} - -static struct snd_soc_ops bf5xx_ad1938_ops = { - .startup = bf5xx_ad1938_startup, - .hw_params = bf5xx_ad1938_hw_params, -}; - -static struct snd_soc_dai_link bf5xx_ad1938_dai = { - .name = "ad1938", - .stream_name = "AD1938", - .cpu_dai = &bf5xx_tdm_dai, - .codec_dai = &ad1938_dai, - .ops = &bf5xx_ad1938_ops, -}; - -static struct snd_soc_card bf5xx_ad1938 = { - .name = "bf5xx_ad1938", - .platform = &bf5xx_tdm_soc_platform, - .dai_link = &bf5xx_ad1938_dai, - .num_links = 1, -}; - -static struct snd_soc_device bf5xx_ad1938_snd_devdata = { - .card = &bf5xx_ad1938, - .codec_dev = &soc_codec_dev_ad1938, -}; - -static struct platform_device *bfxx_ad1938_snd_device; - -static int __init bf5xx_ad1938_init(void) -{ - int ret; - - bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1); - if (!bfxx_ad1938_snd_device) - return -ENOMEM; - - platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata); - bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev; - ret = platform_device_add(bfxx_ad1938_snd_device); - - if (ret) - platform_device_put(bfxx_ad1938_snd_device); - - return ret; -} - -static void __exit bf5xx_ad1938_exit(void) -{ - platform_device_unregister(bfxx_ad1938_snd_device); -} - -module_init(bf5xx_ad1938_init); -module_exit(bf5xx_ad1938_exit); - -/* Module information */ -MODULE_AUTHOR("Barry Song"); -MODULE_DESCRIPTION("ALSA SoC AD1938 board driver"); -MODULE_LICENSE("GPL"); - diff --git a/sound/soc/blackfin/bf5xx-ad193x.c b/sound/soc/blackfin/bf5xx-ad193x.c new file mode 100644 index 00000000000..b8c9060cfd8 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad193x.c @@ -0,0 +1,149 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad193x.c + * Author: Barry Song + * + * Created: Thur June 4 2009 + * Description: Board driver for ad193x sound chip + * + * Bugs: Enter bugs at http://blackfin.uclinux.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. + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/ad193x.h" +#include "bf5xx-sport.h" + +#include "bf5xx-tdm-pcm.h" +#include "bf5xx-tdm.h" + +static struct snd_soc_card bf5xx_ad193x; + +static int bf5xx_ad193x_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ad193x_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->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + unsigned int channel_map[] = {0, 1, 2, 3, 4, 5, 6, 7}; + int ret = 0; + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI slots, 8 channels, all channels are enabled */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32); + if (ret < 0) + return ret; + + /* set cpu DAI channel mapping */ + ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map), + channel_map, ARRAY_SIZE(channel_map), channel_map); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops bf5xx_ad193x_ops = { + .startup = bf5xx_ad193x_startup, + .hw_params = bf5xx_ad193x_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ad193x_dai = { + .name = "ad193x", + .stream_name = "AD193X", + .cpu_dai = &bf5xx_tdm_dai, + .codec_dai = &ad193x_dai, + .ops = &bf5xx_ad193x_ops, +}; + +static struct snd_soc_card bf5xx_ad193x = { + .name = "bf5xx_ad193x", + .platform = &bf5xx_tdm_soc_platform, + .dai_link = &bf5xx_ad193x_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ad193x_snd_devdata = { + .card = &bf5xx_ad193x, + .codec_dev = &soc_codec_dev_ad193x, +}; + +static struct platform_device *bfxx_ad193x_snd_device; + +static int __init bf5xx_ad193x_init(void) +{ + int ret; + + bfxx_ad193x_snd_device = platform_device_alloc("soc-audio", -1); + if (!bfxx_ad193x_snd_device) + return -ENOMEM; + + platform_set_drvdata(bfxx_ad193x_snd_device, &bf5xx_ad193x_snd_devdata); + bf5xx_ad193x_snd_devdata.dev = &bfxx_ad193x_snd_device->dev; + ret = platform_device_add(bfxx_ad193x_snd_device); + + if (ret) + platform_device_put(bfxx_ad193x_snd_device); + + return ret; +} + +static void __exit bf5xx_ad193x_exit(void) +{ + platform_device_unregister(bfxx_ad193x_snd_device); +} + +module_init(bf5xx_ad193x_init); +module_exit(bf5xx_ad193x_exit); + +/* Module information */ +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("ALSA SoC AD193X board driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3-18-g5258 From 093208f5d03980d7216b706e3c54432d0f299e26 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 16 Mar 2010 16:11:14 +0000 Subject: ASoC: Hook up microphone jack detection on 1133-EV1 board Note that since all the microphones share a bias there is a single jack exported for all three, even though there are two physical connectors plus the soldered down silicon mic. Note also that the SiMic is always present by default. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/imx/wm1133-ev1.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c index 0af987e9422..cec8bae7ac1 100644 --- a/sound/soc/imx/wm1133-ev1.c +++ b/sound/soc/imx/wm1133-ev1.c @@ -203,6 +203,13 @@ static struct snd_soc_jack_pin hp_jack_pins[] = { { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, }; +static struct snd_soc_jack mic_jack; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE }, + { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE }, +}; + static int wm1133_ev1_init(struct snd_soc_codec *codec) { struct snd_soc_card *card = codec->socdev->card; @@ -219,6 +226,14 @@ static int wm1133_ev1_init(struct snd_soc_codec *codec) hp_jack_pins); wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); + /* Microphone jack detection */ + snd_soc_jack_new(card, "Microphone", + SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack); + snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins), + mic_jack_pins); + wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, + SND_JACK_BTN_0); + return 0; } -- cgit v1.2.3-18-g5258 From fd23b7dee5e4d369f620979cb120f53629389355 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 19 Mar 2010 14:52:55 +0000 Subject: ASoC: move dma_data from snd_soc_dai to snd_soc_pcm_stream This fixes a memory corruption when ASoC devices are used in full-duplex mode. Specifically for pxa-ssp code, where this pointer is dynamically allocated for each direction and destroyed upon each stream start. All other platforms are fixed blindly, I couldn't even compile-test them. Sorry for any breakage I may have caused. Reported-by: Sven Neumann Reported-by: Michael Hirsch Signed-off-by: Daniel Mack Acked-by: Peter Ujfalusi Acked-by: Jarkko Nikula Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 18 +++++++++++++++++- include/sound/soc.h | 1 + sound/soc/atmel/atmel-pcm.c | 2 +- sound/soc/atmel/atmel_ssc_dai.c | 6 +++--- sound/soc/davinci/davinci-i2s.c | 3 ++- sound/soc/davinci/davinci-mcasp.c | 3 ++- sound/soc/davinci/davinci-pcm.c | 4 +++- sound/soc/davinci/davinci-vcif.c | 3 ++- sound/soc/imx/imx-pcm-dma-mx2.c | 8 ++++++-- sound/soc/imx/imx-ssi.c | 7 +++++-- sound/soc/omap/omap-mcbsp.c | 4 +++- sound/soc/omap/omap-mcpdm.c | 3 ++- sound/soc/omap/omap-pcm.c | 4 +++- sound/soc/pxa/pxa-ssp.c | 23 +++++++++++----------- sound/soc/pxa/pxa2xx-ac97.c | 17 ++++++++++++----- sound/soc/pxa/pxa2xx-i2s.c | 7 +++++-- sound/soc/pxa/pxa2xx-pcm.c | 4 +++- sound/soc/s3c24xx/s3c-ac97.c | 21 +++++++++++--------- sound/soc/s3c24xx/s3c-dma.c | 4 +++- sound/soc/s3c24xx/s3c-i2s-v2.c | 13 ++++++++----- sound/soc/s3c24xx/s3c-pcm.c | 7 +++++-- sound/soc/s3c24xx/s3c2412-i2s.c | 7 +++++-- sound/soc/s3c24xx/s3c24xx-i2s.c | 19 ++++++++++--------- sound/soc/s6000/s6000-i2s.c | 3 ++- sound/soc/s6000/s6000-pcm.c | 40 ++++++++++++++++++++++++++++----------- 25 files changed, 156 insertions(+), 75 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 6cf76a41501..377693a1438 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -224,7 +224,6 @@ struct snd_soc_dai { struct snd_soc_codec *codec; unsigned int active; unsigned char pop_wait:1; - void *dma_data; /* DAI private data */ void *private_data; @@ -235,4 +234,21 @@ struct snd_soc_dai { struct list_head list; }; +static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai, + const struct snd_pcm_substream *ss) +{ + return (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + dai->playback.dma_data : dai->capture.dma_data; +} + +static inline void snd_soc_dai_set_dma_data(struct snd_soc_dai *dai, + const struct snd_pcm_substream *ss, + void *data) +{ + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback.dma_data = data; + else + dai->capture.dma_data = data; +} + #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index dbfec16015d..b8bac6ae624 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -376,6 +376,7 @@ struct snd_soc_pcm_stream { unsigned int channels_min; /* min channels */ unsigned int channels_max; /* max channels */ unsigned int active; /* num of active users of the stream */ + void *dma_data; /* used by platform code */ }; /* SoC audio ops */ diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index fdb25537212..f6b3cc04b34 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -180,7 +180,7 @@ static int atmel_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); - prtd->params = rtd->dai->cpu_dai->dma_data; + prtd->params = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); prtd->params->dma_intr_handler = atmel_pcm_dma_irq; prtd->dma_buffer = runtime->dma_addr; diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index e588e63f18d..0b59806905d 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -363,12 +363,12 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, ssc_p->dma_params[dir] = dma_params; /* - * The cpu_dai->dma_data field is only used to communicate the - * appropriate DMA parameters to the pcm driver hw_params() + * The snd_soc_pcm_stream->dma_data field is only used to communicate + * the appropriate DMA parameters to the pcm driver hw_params() * function. It should not be used for other purposes * as it is common to all substreams. */ - rtd->dai->cpu_dai->dma_data = dma_params; + snd_soc_dai_set_dma_data(rtd->dai->cpu_dai, substream, dma_params); channels = params_channels(params); diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 6362ca05506..4aad7ecc90a 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -585,7 +585,8 @@ static int davinci_i2s_probe(struct platform_device *pdev) dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start; davinci_i2s_dai.private_data = dev; - davinci_i2s_dai.dma_data = dev->dma_params; + davinci_i2s_dai.capture.dma_data = dev->dma_params; + davinci_i2s_dai.playback.dma_data = dev->dma_params; ret = snd_soc_register_dai(&davinci_i2s_dai); if (ret != 0) goto err_free_mem; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index ab6518d86f1..c056bfbe034 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -917,7 +917,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dma_data->channel = res->start; davinci_mcasp_dai[pdata->op_mode].private_data = dev; - davinci_mcasp_dai[pdata->op_mode].dma_data = dev->dma_params; + davinci_mcasp_dai[pdata->op_mode].capture.dma_data = dev->dma_params; + davinci_mcasp_dai[pdata->op_mode].playback.dma_data = dev->dma_params; davinci_mcasp_dai[pdata->op_mode].dev = &pdev->dev; ret = snd_soc_register_dai(&davinci_mcasp_dai[pdata->op_mode]); diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 80c7fdf2f52..2dc406f42fe 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -649,8 +649,10 @@ static int davinci_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_hardware *ppcm; int ret = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_pcm_dma_params *pa = rtd->dai->cpu_dai->dma_data; + struct davinci_pcm_dma_params *pa; struct davinci_pcm_dma_params *params; + + pa = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); if (!pa) return -ENODEV; params = &pa[substream->stream]; diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c index 03f3feb0fe2..54b91e1768c 100644 --- a/sound/soc/davinci/davinci-vcif.c +++ b/sound/soc/davinci/davinci-vcif.c @@ -222,7 +222,8 @@ static int davinci_vcif_probe(struct platform_device *pdev) davinci_vc->davinci_vcif.dma_rx_addr; davinci_vcif_dai.dev = &pdev->dev; - davinci_vcif_dai.dma_data = davinci_vcif_dev->dma_params; + davinci_vcif_dai.capture.dma_data = davinci_vcif_dev->dma_params; + davinci_vcif_dai.playback.dma_data = davinci_vcif_dev->dma_params; davinci_vcif_dai.private_data = davinci_vcif_dev; ret = snd_soc_register_dai(&davinci_vcif_dai); diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c index 19452e44afd..c78c000e2af 100644 --- a/sound/soc/imx/imx-pcm-dma-mx2.c +++ b/sound/soc/imx/imx-pcm-dma-mx2.c @@ -83,11 +83,13 @@ static void snd_imx_dma_err_callback(int channel, void *data, int err) static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data; + struct imx_pcm_dma_params *dma_params; struct snd_pcm_runtime *runtime = substream->runtime; struct imx_pcm_runtime_data *iprtd = runtime->private_data; int ret; + dma_params = snd_soc_get_dma_data(rtd->dai->cpu_dai, substream); + iprtd->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH); if (iprtd->dma < 0) { pr_err("Failed to claim the audio DMA\n"); @@ -192,10 +194,12 @@ static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data; + struct imx_pcm_dma_params *dma_params; struct imx_pcm_runtime_data *iprtd = runtime->private_data; int err; + dma_params = snd_soc_get_dma_data(rtd->dai->cpu_dai, substream); + iprtd->substream = substream; iprtd->buf = (unsigned int *)substream->dma_buffer.area; iprtd->period_cnt = 0; diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c index 56f46a75d29..28e55c7b14b 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/imx/imx-ssi.c @@ -234,17 +234,20 @@ static int imx_ssi_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct imx_ssi *ssi = cpu_dai->private_data; + struct imx_pcm_dma_params *dma_data; u32 reg, sccr; /* Tx/Rx config */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { reg = SSI_STCCR; - cpu_dai->dma_data = &ssi->dma_params_tx; + dma_data = &ssi->dma_params_tx; } else { reg = SSI_SRCCR; - cpu_dai->dma_data = &ssi->dma_params_rx; + dma_data = &ssi->dma_params_rx; } + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; /* DAI data (word) size */ diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 0199e95a5d6..2d33a89f147 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -322,7 +322,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode; omap_mcbsp_dai_dma_params[id][substream->stream].data_type = OMAP_DMA_DATA_TYPE_S16; - cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream]; + + snd_soc_dai_set_dma_data(cpu_dai, substream, + &omap_mcbsp_dai_dma_params[id][substream->stream]); if (mcbsp_data->configured) { /* McBSP already configured by another stream */ diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index 25f19e4728b..b7f4f7e015f 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -150,7 +150,8 @@ static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, int stream = substream->stream; int channels, err, link_mask = 0; - cpu_dai->dma_data = &omap_mcpdm_dai_dma_params[stream]; + snd_soc_dai_set_dma_data(cpu_dai, substream, + &omap_mcpdm_dai_dma_params[stream]); channels = params_channels(params); switch (channels) { diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 825db385f01..39538c0f81f 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -100,9 +100,11 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct omap_runtime_data *prtd = runtime->private_data; - struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data; + struct omap_pcm_dma_data *dma_data; int err = 0; + dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + /* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma_data) diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index e69397f40f7..5d65a00e4bc 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -103,10 +103,9 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, ssp_disable(&priv->dev); } - if (cpu_dai->dma_data) { - kfree(cpu_dai->dma_data); - cpu_dai->dma_data = NULL; - } + kfree(snd_soc_dai_get_dma_data(cpu_dai, substream)); + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + return ret; } @@ -122,10 +121,8 @@ static void pxa_ssp_shutdown(struct snd_pcm_substream *substream, clk_disable(priv->dev.ssp->clk); } - if (cpu_dai->dma_data) { - kfree(cpu_dai->dma_data); - cpu_dai->dma_data = NULL; - } + kfree(snd_soc_dai_get_dma_data(cpu_dai, substream)); + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); } #ifdef CONFIG_PM @@ -538,19 +535,23 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, u32 sspsp; int width = snd_pcm_format_physical_width(params_format(params)); int ttsa = ssp_read_reg(ssp, SSTSA) & 0xf; + struct pxa2xx_pcm_dma_params *dma_data; + + dma_data = snd_soc_dai_get_dma_data(dai, substream); /* generate correct DMA params */ - if (cpu_dai->dma_data) - kfree(cpu_dai->dma_data); + kfree(dma_data); /* Network mode with one active slot (ttsa == 1) can be used * to force 16-bit frame width on the wire (for S16_LE), even * with two channels. Use 16-bit DMA transfers for this case. */ - cpu_dai->dma_data = ssp_get_dma_params(ssp, + dma_data = ssp_get_dma_params(ssp, ((chn == 2) && (ttsa != 1)) || (width == 32), substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_dma_data(dai, substream, dma_data); + /* we can only change the settings if the port is not in use */ if (ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) return 0; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index e9ae7b3a7e0..d314115e3dd 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -122,11 +122,14 @@ static int pxa2xx_ac97_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct pxa2xx_pcm_dma_params *dma_data; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->dma_data = &pxa2xx_ac97_pcm_stereo_out; + dma_data = &pxa2xx_ac97_pcm_stereo_out; else - cpu_dai->dma_data = &pxa2xx_ac97_pcm_stereo_in; + dma_data = &pxa2xx_ac97_pcm_stereo_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); return 0; } @@ -137,11 +140,14 @@ static int pxa2xx_ac97_hw_aux_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct pxa2xx_pcm_dma_params *dma_data; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->dma_data = &pxa2xx_ac97_pcm_aux_mono_out; + dma_data = &pxa2xx_ac97_pcm_aux_mono_out; else - cpu_dai->dma_data = &pxa2xx_ac97_pcm_aux_mono_in; + dma_data = &pxa2xx_ac97_pcm_aux_mono_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); return 0; } @@ -156,7 +162,8 @@ static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; else - cpu_dai->dma_data = &pxa2xx_ac97_pcm_mic_mono_in; + snd_soc_dai_set_dma_data(cpu_dai, substream, + &pxa2xx_ac97_pcm_mic_mono_in); return 0; } diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 6b8f655d1ad..c1a5275721e 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -164,6 +164,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct pxa2xx_pcm_dma_params *dma_data; BUG_ON(IS_ERR(clk_i2s)); clk_enable(clk_i2s); @@ -171,9 +172,11 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, pxa_i2s_wait(); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_out; + dma_data = &pxa2xx_i2s_pcm_stereo_out; else - cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_in; + dma_data = &pxa2xx_i2s_pcm_stereo_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); /* is port used by another stream */ if (!(SACR0 & SACR0_ENB)) { diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index d38e39575f5..adc7e6f15f9 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -25,9 +25,11 @@ static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct pxa2xx_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct pxa2xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; + struct pxa2xx_pcm_dma_params *dma; int ret; + dma = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); + /* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma) diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c index ee8ed9d7e70..ecf4fd04ae9 100644 --- a/sound/soc/s3c24xx/s3c-ac97.c +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -224,11 +224,14 @@ static int s3c_ac97_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct s3c_dma_params *dma_data; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->dma_data = &s3c_ac97_pcm_out; + dma_data = &s3c_ac97_pcm_out; else - cpu_dai->dma_data = &s3c_ac97_pcm_in; + dma_data = &s3c_ac97_pcm_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); return 0; } @@ -238,8 +241,8 @@ static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd, { u32 ac_glbctrl; struct snd_soc_pcm_runtime *rtd = substream->private_data; - int channel = ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->channel; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) @@ -265,7 +268,7 @@ static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd, writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); - s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); return 0; } @@ -280,7 +283,7 @@ static int s3c_ac97_hw_mic_params(struct snd_pcm_substream *substream, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; else - cpu_dai->dma_data = &s3c_ac97_mic_in; + snd_soc_dai_set_dma_data(cpu_dai, substream, &s3c_ac97_mic_in); return 0; } @@ -290,8 +293,8 @@ static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream, { u32 ac_glbctrl; struct snd_soc_pcm_runtime *rtd = substream->private_data; - int channel = ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->channel; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK; @@ -311,7 +314,7 @@ static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream, writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); - s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); return 0; } diff --git a/sound/soc/s3c24xx/s3c-dma.c b/sound/soc/s3c24xx/s3c-dma.c index 7725e26d6c9..1b61c23ff30 100644 --- a/sound/soc/s3c24xx/s3c-dma.c +++ b/sound/soc/s3c24xx/s3c-dma.c @@ -145,10 +145,12 @@ static int s3c_dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct s3c24xx_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct s3c_dma_params *dma = rtd->dai->cpu_dai->dma_data; unsigned long totbytes = params_buffer_bytes(params); + struct s3c_dma_params *dma = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); int ret = 0; + pr_debug("Entered %s\n", __func__); /* return if this is a bufferless transfer e.g. diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index c3fcb63cbf2..865f93143bf 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -333,14 +333,17 @@ static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_link *dai = rtd->dai; struct s3c_i2sv2_info *i2s = to_info(dai->cpu_dai); + struct s3c_dma_params *dma_data; u32 iismod; pr_debug("Entered %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - dai->cpu_dai->dma_data = i2s->dma_playback; + dma_data = i2s->dma_playback; else - dai->cpu_dai->dma_data = i2s->dma_capture; + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(dai->cpu_dai, substream, dma_data); /* Working copies of register */ iismod = readl(i2s->regs + S3C2412_IISMOD); @@ -372,8 +375,8 @@ static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); unsigned long irqs; int ret = 0; - int channel = ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->channel; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); pr_debug("Entered %s\n", __func__); @@ -409,7 +412,7 @@ static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, * of the auto reload mechanism of S3C24XX. * This call won't bother S3C64XX. */ - s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); break; diff --git a/sound/soc/s3c24xx/s3c-pcm.c b/sound/soc/s3c24xx/s3c-pcm.c index a98f40c3cd2..326f0a9e7e3 100644 --- a/sound/soc/s3c24xx/s3c-pcm.c +++ b/sound/soc/s3c24xx/s3c-pcm.c @@ -178,6 +178,7 @@ static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_link *dai = rtd->dai; struct s3c_pcm_info *pcm = to_info(dai->cpu_dai); + struct s3c_dma_params *dma_data; void __iomem *regs = pcm->regs; struct clk *clk; int sclk_div, sync_div; @@ -187,9 +188,11 @@ static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, dev_dbg(pcm->dev, "Entered %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - dai->cpu_dai->dma_data = pcm->dma_playback; + dma_data = pcm->dma_playback; else - dai->cpu_dai->dma_data = pcm->dma_capture; + dma_data = pcm->dma_capture; + + snd_soc_dai_set_dma_data(dai->cpu_dai, substream, dma_data); /* Strictly check for sample size */ switch (params_format(params)) { diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index a5b21f63261..f3148f98b41 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -151,14 +151,17 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + struct s3c_dma_params *dma_data; u32 iismod; pr_debug("Entered %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->dma_data = i2s->dma_playback; + dma_data = i2s->dma_playback; else - cpu_dai->dma_data = i2s->dma_capture; + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index 0bc5950b9f0..c3ac890a398 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -242,14 +242,17 @@ static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_dma_params *dma_data; u32 iismod; pr_debug("Entered %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; + dma_data = &s3c24xx_i2s_pcm_stereo_out; else - rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in; + dma_data = &s3c24xx_i2s_pcm_stereo_in; + + snd_soc_dai_set_dma_data(rtd->dai->cpu_dai, substream, dma_data); /* Working copies of register */ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); @@ -258,13 +261,11 @@ static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: iismod &= ~S3C2410_IISMOD_16BIT; - ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->dma_size = 1; + dma_data->dma_size = 1; break; case SNDRV_PCM_FORMAT_S16_LE: iismod |= S3C2410_IISMOD_16BIT; - ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->dma_size = 2; + dma_data->dma_size = 2; break; default: return -EINVAL; @@ -280,8 +281,8 @@ static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, { int ret = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; - int channel = ((struct s3c_dma_params *) - rtd->dai->cpu_dai->dma_data)->channel; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream); pr_debug("Entered %s\n", __func__); @@ -300,7 +301,7 @@ static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, else s3c24xx_snd_txctrl(1); - s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c index c5cda187eca..fa23854c5f3 100644 --- a/sound/soc/s6000/s6000-i2s.c +++ b/sound/soc/s6000/s6000-i2s.c @@ -518,7 +518,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev) s6000_i2s_dai.dev = &pdev->dev; s6000_i2s_dai.private_data = dev; - s6000_i2s_dai.dma_data = &dev->dma_params; + s6000_i2s_dai.capture.dma_data = &dev->dma_params; + s6000_i2s_dai.playback.dma_data = &dev->dma_params; dev->sifbase = sifmem->start; dev->scbbase = mmio; diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c index 1d61109e09f..9c7f7f00ceb 100644 --- a/sound/soc/s6000/s6000-pcm.c +++ b/sound/soc/s6000/s6000-pcm.c @@ -58,13 +58,15 @@ static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct s6000_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; int channel; unsigned int period_size; unsigned int dma_offset; dma_addr_t dma_pos; dma_addr_t src, dst; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + period_size = snd_pcm_lib_period_bytes(substream); dma_offset = prtd->period * period_size; dma_pos = runtime->dma_addr + dma_offset; @@ -101,7 +103,8 @@ static irqreturn_t s6000_pcm_irq(int irq, void *data) { struct snd_pcm *pcm = data; struct snd_soc_pcm_runtime *runtime = pcm->private_data; - struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *params = + snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); struct s6000_runtime_data *prtd; unsigned int has_xrun; int i, ret = IRQ_NONE; @@ -172,11 +175,13 @@ static int s6000_pcm_start(struct snd_pcm_substream *substream) { struct s6000_runtime_data *prtd = substream->runtime->private_data; struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; unsigned long flags; int srcinc; u32 dma; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + spin_lock_irqsave(&prtd->lock, flags); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { @@ -212,10 +217,12 @@ static int s6000_pcm_stop(struct snd_pcm_substream *substream) { struct s6000_runtime_data *prtd = substream->runtime->private_data; struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; unsigned long flags; u32 channel; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) channel = par->dma_out; else @@ -236,9 +243,11 @@ static int s6000_pcm_stop(struct snd_pcm_substream *substream) static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; int ret; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + ret = par->trigger(substream, cmd, 0); if (ret < 0) return ret; @@ -275,13 +284,15 @@ static int s6000_pcm_prepare(struct snd_pcm_substream *substream) static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; struct snd_pcm_runtime *runtime = substream->runtime; struct s6000_runtime_data *prtd = runtime->private_data; unsigned long flags; unsigned int offset; dma_addr_t count; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + spin_lock_irqsave(&prtd->lock, flags); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -305,11 +316,12 @@ static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream) static int s6000_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; struct snd_pcm_runtime *runtime = substream->runtime; struct s6000_runtime_data *prtd; int ret; + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware); ret = snd_pcm_hw_constraint_step(runtime, 0, @@ -364,7 +376,7 @@ static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par; int ret; ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); @@ -373,6 +385,8 @@ static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, return ret; } + par = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + if (par->same_rate) { spin_lock(&par->lock); if (par->rate == -1 || @@ -392,7 +406,8 @@ static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, static int s6000_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *par = + snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); spin_lock(&par->lock); par->in_use &= ~(1 << substream->stream); @@ -417,7 +432,8 @@ static struct snd_pcm_ops s6000_pcm_ops = { static void s6000_pcm_free(struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *runtime = pcm->private_data; - struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *params = + snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); free_irq(params->irq, pcm); snd_pcm_lib_preallocate_free_for_all(pcm); @@ -429,9 +445,11 @@ static int s6000_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *runtime = pcm->private_data; - struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + struct s6000_pcm_dma_params *params; int res; + params = snd_soc_dai_get_dma_data(soc_runtime->dai->cpu_dai, substream); + if (!card->dev->dma_mask) card->dev->dma_mask = &s6000_pcm_dmamask; if (!card->dev->coherent_dma_mask) -- cgit v1.2.3-18-g5258 From b2dfa62c525fbe4d89790b6fa90af7c3f1c052ae Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 18 Mar 2010 08:23:33 +0100 Subject: ASoC: remove a card from the list, if instantiation failed If instantiation of a card failed, we still have to remove it from the card list on unregistration. This fixes an Oops on Migo-R, triggering, when after a failed firmware load attempt the driver modules are removed and re-inserted again. Signed-off-by: Guennadi Liakhovetski Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index eb73aab78b9..1ff553bbc06 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1283,26 +1283,25 @@ static int soc_remove(struct platform_device *pdev) struct snd_soc_platform *platform = card->platform; struct snd_soc_codec_device *codec_dev = socdev->codec_dev; - if (!card->instantiated) - return 0; + if (card->instantiated) { + run_delayed_work(&card->delayed_work); - run_delayed_work(&card->delayed_work); + if (platform->remove) + platform->remove(pdev); - if (platform->remove) - platform->remove(pdev); + if (codec_dev->remove) + codec_dev->remove(pdev); - if (codec_dev->remove) - codec_dev->remove(pdev); + for (i = 0; i < card->num_links; i++) { + struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + if (cpu_dai->remove) + cpu_dai->remove(pdev, cpu_dai); + } - for (i = 0; i < card->num_links; i++) { - struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; - if (cpu_dai->remove) - cpu_dai->remove(pdev, cpu_dai); + if (card->remove) + card->remove(pdev); } - if (card->remove) - card->remove(pdev); - snd_soc_unregister_card(card); return 0; -- cgit v1.2.3-18-g5258 From 6da7a2aa899f75116e1a62cef78c358ada9878b7 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Mon, 22 Mar 2010 13:13:37 +0100 Subject: ALSA: usb/caiaq: Add support for Traktor Kontrol X1 This device does not have audio controllers and backlit buttons only. Input data is handled over a dedicated USB endpoint. All functions are supported by the driver now. Signed-off-by: Daniel Mack Cc: Dmitry Torokhov Signed-off-by: Takashi Iwai --- sound/usb/Kconfig | 1 + sound/usb/caiaq/control.c | 99 ++++++++++++++++++++++------ sound/usb/caiaq/device.c | 8 ++- sound/usb/caiaq/device.h | 24 ++++--- sound/usb/caiaq/input.c | 162 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 249 insertions(+), 45 deletions(-) diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index c570ae3e6d5..c4dcbadd83a 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -65,6 +65,7 @@ config SND_USB_CAIAQ * Native Instruments Audio 8 DJ * Native Instruments Guitar Rig Session I/O * Native Instruments Guitar Rig mobile + * Native Instruments Traktor Kontrol X1 To compile this driver as a module, choose M here: the module will be called snd-usb-caiaq. diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c index 537102ba6b9..36ed703a741 100644 --- a/sound/usb/caiaq/control.c +++ b/sound/usb/caiaq/control.c @@ -35,33 +35,41 @@ static int control_info(struct snd_kcontrol *kcontrol, struct snd_usb_caiaqdev *dev = caiaqdev(chip->card); int pos = kcontrol->private_value; int is_intval = pos & CNT_INTVAL; - unsigned int id = dev->chip.usb_id; + int maxval = 63; uinfo->count = 1; pos &= ~CNT_INTVAL; - if (id == USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ) - && (pos == 0)) { - /* current input mode of A8DJ */ - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 2; - return 0; - } + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + if (pos == 0) { + /* current input mode of A8DJ */ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; + } + break; - if (id == USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ) - && (pos == 0)) { - /* current input mode of A4DJ */ - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - return 0; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + if (pos == 0) { + /* current input mode of A4DJ */ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; + } + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + maxval = 127; + break; } if (is_intval) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 64; + uinfo->value.integer.max = maxval; } else { uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; uinfo->value.integer.min = 0; @@ -102,9 +110,10 @@ static int control_put(struct snd_kcontrol *kcontrol, struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); struct snd_usb_caiaqdev *dev = caiaqdev(chip->card); int pos = kcontrol->private_value; + unsigned char cmd = EP1_CMD_WRITE_IO; - if (dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ)) { + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): { /* A4DJ has only one control */ /* do not expose hardware input mode 0 */ dev->control_state[0] = ucontrol->value.integer.value[0] + 1; @@ -113,10 +122,15 @@ static int control_put(struct snd_kcontrol *kcontrol, return 1; } + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + cmd = EP1_CMD_DIMM_LEDS; + break; + } + if (pos & CNT_INTVAL) { dev->control_state[pos & ~CNT_INTVAL] = ucontrol->value.integer.value[0]; - snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, + snd_usb_caiaq_send_command(dev, cmd, dev->control_state, sizeof(dev->control_state)); } else { if (ucontrol->value.integer.value[0]) @@ -124,7 +138,7 @@ static int control_put(struct snd_kcontrol *kcontrol, else dev->control_state[pos / 8] &= ~(1 << (pos % 8)); - snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, + snd_usb_caiaq_send_command(dev, cmd, dev->control_state, sizeof(dev->control_state)); } @@ -273,6 +287,43 @@ static struct caiaq_controller a4dj_controller[] = { { "Current input mode", 0 | CNT_INTVAL } }; +static struct caiaq_controller kontrolx1_controller[] = { + { "LED FX A: ON", 7 | CNT_INTVAL }, + { "LED FX A: 1", 6 | CNT_INTVAL }, + { "LED FX A: 2", 5 | CNT_INTVAL }, + { "LED FX A: 3", 4 | CNT_INTVAL }, + { "LED FX B: ON", 3 | CNT_INTVAL }, + { "LED FX B: 1", 2 | CNT_INTVAL }, + { "LED FX B: 2", 1 | CNT_INTVAL }, + { "LED FX B: 3", 0 | CNT_INTVAL }, + + { "LED Hotcue", 28 | CNT_INTVAL }, + { "LED Shift (white)", 29 | CNT_INTVAL }, + { "LED Shift (green)", 30 | CNT_INTVAL }, + + { "LED Deck A: FX1", 24 | CNT_INTVAL }, + { "LED Deck A: FX2", 25 | CNT_INTVAL }, + { "LED Deck A: IN", 17 | CNT_INTVAL }, + { "LED Deck A: OUT", 16 | CNT_INTVAL }, + { "LED Deck A: < BEAT", 19 | CNT_INTVAL }, + { "LED Deck A: BEAT >", 18 | CNT_INTVAL }, + { "LED Deck A: CUE/ABS", 21 | CNT_INTVAL }, + { "LED Deck A: CUP/REL", 20 | CNT_INTVAL }, + { "LED Deck A: PLAY", 23 | CNT_INTVAL }, + { "LED Deck A: SYNC", 22 | CNT_INTVAL }, + + { "LED Deck B: FX1", 26 | CNT_INTVAL }, + { "LED Deck B: FX2", 27 | CNT_INTVAL }, + { "LED Deck B: IN", 15 | CNT_INTVAL }, + { "LED Deck B: OUT", 14 | CNT_INTVAL }, + { "LED Deck B: < BEAT", 13 | CNT_INTVAL }, + { "LED Deck B: BEAT >", 12 | CNT_INTVAL }, + { "LED Deck B: CUE/ABS", 11 | CNT_INTVAL }, + { "LED Deck B: CUP/REL", 10 | CNT_INTVAL }, + { "LED Deck B: PLAY", 9 | CNT_INTVAL }, + { "LED Deck B: SYNC", 8 | CNT_INTVAL }, +}; + static int __devinit add_controls(struct caiaq_controller *c, int num, struct snd_usb_caiaqdev *dev) { @@ -321,10 +372,16 @@ int __devinit snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *dev) ret = add_controls(a8dj_controller, ARRAY_SIZE(a8dj_controller), dev); break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): ret = add_controls(a4dj_controller, ARRAY_SIZE(a4dj_controller), dev); break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + ret = add_controls(kontrolx1_controller, + ARRAY_SIZE(kontrolx1_controller), dev); + break; } return ret; diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index a3f02dd9744..08ee2545830 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -46,7 +46,8 @@ MODULE_SUPPORTED_DEVICE("{{Native Instruments, RigKontrol2}," "{Native Instruments, Audio 4 DJ}," "{Native Instruments, Audio 8 DJ}," "{Native Instruments, Session I/O}," - "{Native Instruments, GuitarRig mobile}"); + "{Native Instruments, GuitarRig mobile}" + "{Native Instruments, Traktor Kontrol X1}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ @@ -127,6 +128,11 @@ static struct usb_device_id snd_usb_id_table[] = { .idVendor = USB_VID_NATIVEINSTRUMENTS, .idProduct = USB_PID_AUDIO2DJ }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORKONTROLX1 + }, { /* terminator */ } }; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 44e3edf88be..f1117ecc84f 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -5,18 +5,20 @@ #define USB_VID_NATIVEINSTRUMENTS 0x17cc -#define USB_PID_RIGKONTROL2 0x1969 -#define USB_PID_RIGKONTROL3 0x1940 -#define USB_PID_KORECONTROLLER 0x4711 -#define USB_PID_KORECONTROLLER2 0x4712 -#define USB_PID_AK1 0x0815 -#define USB_PID_AUDIO2DJ 0x041c -#define USB_PID_AUDIO4DJ 0x0839 -#define USB_PID_AUDIO8DJ 0x1978 -#define USB_PID_SESSIONIO 0x1915 -#define USB_PID_GUITARRIGMOBILE 0x0d8d +#define USB_PID_RIGKONTROL2 0x1969 +#define USB_PID_RIGKONTROL3 0x1940 +#define USB_PID_KORECONTROLLER 0x4711 +#define USB_PID_KORECONTROLLER2 0x4712 +#define USB_PID_AK1 0x0815 +#define USB_PID_AUDIO2DJ 0x041c +#define USB_PID_AUDIO4DJ 0x0839 +#define USB_PID_AUDIO8DJ 0x1978 +#define USB_PID_SESSIONIO 0x1915 +#define USB_PID_GUITARRIGMOBILE 0x0d8d +#define USB_PID_TRAKTORKONTROLX1 0x2305 #define EP1_BUFSIZE 64 +#define EP4_BUFSIZE 512 #define CAIAQ_USB_STR_LEN 0xff #define MAX_STREAMS 32 @@ -104,6 +106,8 @@ struct snd_usb_caiaqdev { struct input_dev *input_dev; char phys[64]; /* physical device path */ unsigned short keycode[64]; + struct urb *ep4_in_urb; + unsigned char ep4_in_buf[EP4_BUFSIZE]; #endif /* ALSA */ diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index a48d309bd94..27ed0bc651a 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "device.h" @@ -65,6 +66,8 @@ static unsigned short keycode_kore[] = { KEY_BRL_DOT5 }; +#define KONTROLX1_INPUTS 40 + #define DEG90 (range / 2) #define DEG180 (range) #define DEG270 (DEG90 + DEG180) @@ -162,6 +165,17 @@ static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *dev, input_report_abs(input_dev, ABS_Z, (buf[4] << 8) | buf[5]); input_sync(input_dev); break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + input_report_abs(input_dev, ABS_HAT0X, (buf[8] << 8) | buf[9]); + input_report_abs(input_dev, ABS_HAT0Y, (buf[4] << 8) | buf[5]); + input_report_abs(input_dev, ABS_HAT1X, (buf[12] << 8) | buf[13]); + input_report_abs(input_dev, ABS_HAT1Y, (buf[2] << 8) | buf[3]); + input_report_abs(input_dev, ABS_HAT2X, (buf[15] << 8) | buf[15]); + input_report_abs(input_dev, ABS_HAT2Y, (buf[0] << 8) | buf[1]); + input_report_abs(input_dev, ABS_HAT3X, (buf[10] << 8) | buf[11]); + input_report_abs(input_dev, ABS_HAT3Y, (buf[6] << 8) | buf[7]); + input_sync(input_dev); + break; } } @@ -201,7 +215,7 @@ static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *dev, } static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *dev, - char *buf, unsigned int len) + unsigned char *buf, unsigned int len) { struct input_dev *input_dev = dev->input_dev; unsigned short *keycode = input_dev->keycode; @@ -218,15 +232,84 @@ static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *dev, input_report_key(input_dev, keycode[i], buf[i / 8] & (1 << (i % 8))); - if (dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER) || - dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2)) + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): input_report_abs(dev->input_dev, ABS_MISC, 255 - buf[4]); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + /* rotary encoders */ + input_report_abs(dev->input_dev, ABS_X, buf[5] & 0xf); + input_report_abs(dev->input_dev, ABS_Y, buf[5] >> 4); + input_report_abs(dev->input_dev, ABS_Z, buf[6] & 0xf); + input_report_abs(dev->input_dev, ABS_MISC, buf[6] >> 4); + break; + } input_sync(input_dev); } +static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb) +{ + struct snd_usb_caiaqdev *dev = urb->context; + unsigned char *buf = urb->transfer_buffer; + int ret; + + if (urb->status || !dev || urb != dev->ep4_in_urb) + return; + + if (urb->actual_length < 24) + goto requeue; + + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (buf[0] & 0x3) + snd_caiaq_input_read_io(dev, buf + 1, 7); + + if (buf[0] & 0x4) + snd_caiaq_input_read_analog(dev, buf + 8, 16); + + break; + } + +requeue: + dev->ep4_in_urb->actual_length = 0; + ret = usb_submit_urb(dev->ep4_in_urb, GFP_ATOMIC); + if (ret < 0) + log("unable to submit urb. OOM!?\n"); +} + +static int snd_usb_caiaq_input_open(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *dev = input_get_drvdata(idev); + + if (!dev) + return -EINVAL; + + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (usb_submit_urb(dev->ep4_in_urb, GFP_KERNEL) != 0) + return -EIO; + break; + } + + return 0; +} + +static void snd_usb_caiaq_input_close(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *dev = input_get_drvdata(idev); + + if (!dev) + return; + + switch (dev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + usb_kill_urb(dev->ep4_in_urb); + break; + } +} + void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, char *buf, unsigned int len) @@ -251,7 +334,7 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev) { struct usb_device *usb_dev = dev->chip.dev; struct input_dev *input; - int i, ret; + int i, ret = 0; input = input_allocate_device(); if (!input) @@ -265,7 +348,9 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev) usb_to_input_id(usb_dev, &input->id); input->dev.parent = &usb_dev->dev; - switch (dev->chip.usb_id) { + input_set_drvdata(input, dev); + + switch (dev->chip.usb_id) { case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | @@ -325,26 +410,73 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev) input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10); input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1); snd_usb_caiaq_set_auto_msg(dev, 1, 10, 5); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + BUILD_BUG_ON(sizeof(dev->keycode) < KONTROLX1_INPUTS); + for (i = 0; i < KONTROLX1_INPUTS; i++) + dev->keycode[i] = BTN_MISC + i; + input->keycodemax = KONTROLX1_INPUTS; + + /* analog potentiometers */ + input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10); + + /* rotary encoders */ + input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1); + + dev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(dev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + dev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, dev); + + snd_usb_caiaq_set_auto_msg(dev, 1, 10, 5); + break; default: /* no input methods supported on this device */ - input_free_device(input); - return 0; + goto exit_free_idev; } + input->open = snd_usb_caiaq_input_open; + input->close = snd_usb_caiaq_input_close; input->keycode = dev->keycode; input->keycodesize = sizeof(unsigned short); for (i = 0; i < input->keycodemax; i++) __set_bit(dev->keycode[i], input->keybit); ret = input_register_device(input); - if (ret < 0) { - input_free_device(input); - return ret; - } + if (ret < 0) + goto exit_free_idev; dev->input_dev = input; return 0; + +exit_free_idev: + input_free_device(input); + return ret; } void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev) @@ -352,6 +484,10 @@ void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev) if (!dev || !dev->input_dev) return; + usb_kill_urb(dev->ep4_in_urb); + usb_free_urb(dev->ep4_in_urb); + dev->ep4_in_urb = NULL; + input_unregister_device(dev->input_dev); dev->input_dev = NULL; } -- cgit v1.2.3-18-g5258 From 6407d474e6ae6a798fa5ba40b32f508a52de80ff Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 22 Mar 2010 08:55:35 -0700 Subject: ALSA: usb: fix usb build error when PM is not enabled Fix build errors when CONFIG_PM is not enabled: sound/usb/card.c:629: error: 'usb_audio_suspend' undeclared here (not in a function) sound/usb/card.c:630: error: 'usb_audio_resume' undeclared here (not in a function) Signed-off-by: Randy Dunlap Signed-off-by: Takashi Iwai --- sound/usb/card.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/card.c b/sound/usb/card.c index 0bd62a1aa4f..da1346bd485 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -606,6 +606,9 @@ static int usb_audio_resume(struct usb_interface *intf) return 0; } +#else +#define usb_audio_suspend NULL +#define usb_audio_resume NULL #endif /* CONFIG_PM */ static struct usb_device_id usb_audio_ids [] = { -- cgit v1.2.3-18-g5258 From 7fb5622326bf98437e629e94f8710eb12a90b30d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 22 Mar 2010 17:09:47 +0100 Subject: ALSA: hda - Fix uninitialized variable warning in alc_auto_parse_customize_define() Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 3d37e2161a5..3dc2be8f3ca 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1263,7 +1263,7 @@ static void alc_init_auto_mic(struct hda_codec *codec) static int alc_auto_parse_customize_define(struct hda_codec *codec) { unsigned int ass, tmp, i; - unsigned nid; + unsigned nid = 0; struct alc_spec *spec = codec->spec; ass = codec->subsystem_id & 0xffff; -- cgit v1.2.3-18-g5258 From c96907f21f26a1f8c1a1a9096a22500e4d158c4f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 Mar 2010 17:46:37 +0200 Subject: ASoC: TWL4030: PM fix for output amplifiers Gain controls on outputs affect the power consumption when the gain is set to non 0 value. Outputs with amps have one register to configure the routing and the gain: PREDL_CTL (0x25): bit 0: Voice enable bit 1: Audio L1 enable bit 2: Audio L2 enable bit 3: Audio R2 enable bit 4-5: Gain (0x0 - power down, 0x1 - 6dB, 0x2 - 0dB, 0x3 - -6dB) bit 0 - 3: is handled in DAPM domain (DAPM_MIXER) bit 4 - 5: has simple volume control If there is no audio activity (BIAS_STANDBY), and user changes the volume, than the output amplifier will be enabled. If the user changes the routing (but the codec remains in BIAS_STANDBY), than the cached gain value also be written to the register, which enables the amplifier. The existing workaround for this is to have virtual PGAs associated with the outputs, and whit DAPM PMD the gain on the output will be forced to 0 (off) by bypassing the regcache. This failed to disable the amplifiers in several scenario (as mentioned above). Also if the codec is in BIAS_ON state, and user modifies a volume control, which path is actually not enabled, than that amplifier will be enabled as well, but it will be not turned off, since there is no DAPM path, which would make mute it. To prevent amps being enabled, when they are not needed, introduce the following workaround: Track the state of each of this type of output. In twl4030_write only allow actual write, when the given output is enabled, otherwise only update the reg_cache. The PGA event handlers on power up will write the cached value to the chip (restoring gain, routing selection). On power down 0 is written to the register (disabling the amp, and also just in case clearing the routing). Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 72 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 6f5d4af2005..bf59b8a4d1d 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -135,9 +135,11 @@ struct twl4030_priv { unsigned int sysclk; - /* Headset output state handling */ - unsigned int hsl_enabled; - unsigned int hsr_enabled; + /* Output (with associated amp) states */ + u8 hsl_enabled, hsr_enabled; + u8 earpiece_enabled; + u8 predrivel_enabled, predriver_enabled; + u8 carkitl_enabled, carkitr_enabled; }; /* @@ -173,12 +175,47 @@ static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec, static int twl4030_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { + struct twl4030_priv *twl4030 = codec->private_data; + int write_to_reg = 0; + twl4030_write_reg_cache(codec, reg, value); - if (likely(reg < TWL4030_REG_SW_SHADOW)) - return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, - reg); - else - return 0; + if (likely(reg < TWL4030_REG_SW_SHADOW)) { + /* Decide if the given register can be written */ + switch (reg) { + case TWL4030_REG_EAR_CTL: + if (twl4030->earpiece_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PREDL_CTL: + if (twl4030->predrivel_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PREDR_CTL: + if (twl4030->predriver_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PRECKL_CTL: + if (twl4030->carkitl_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PRECKR_CTL: + if (twl4030->carkitr_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_HS_GAIN_SET: + if (twl4030->hsl_enabled || twl4030->hsr_enabled) + write_to_reg = 1; + break; + default: + /* All other register can be written */ + write_to_reg = 1; + break; + } + if (write_to_reg) + return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + value, reg); + } + return 0; } static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) @@ -525,26 +562,26 @@ static int micpath_event(struct snd_soc_dapm_widget *w, * Output PGA builder: * Handle the muting and unmuting of the given output (turning off the * amplifier associated with the output pin) - * On mute bypass the reg_cache and mute the volume - * On unmute: restore the register content + * On mute bypass the reg_cache and write 0 to the register + * On unmute: restore the register content from the reg_cache * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R */ #define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \ static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ struct snd_kcontrol *kcontrol, int event) \ { \ - u8 reg_val; \ + struct twl4030_priv *twl4030 = w->codec->private_data; \ \ switch (event) { \ case SND_SOC_DAPM_POST_PMU: \ + twl4030->pin_name##_enabled = 1; \ twl4030_write(w->codec, reg, \ twl4030_read_reg_cache(w->codec, reg)); \ break; \ case SND_SOC_DAPM_POST_PMD: \ - reg_val = twl4030_read_reg_cache(w->codec, reg); \ - twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ - reg_val & (~mask), \ - reg); \ + twl4030->pin_name##_enabled = 0; \ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ + 0, reg); \ break; \ } \ return 0; \ @@ -664,7 +701,10 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) /* Headset ramp-up according to the TRM */ hs_pop |= TWL4030_VMID_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); - twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); + /* Actually write to the register */ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + hs_gain, + TWL4030_REG_HS_GAIN_SET); hs_pop |= TWL4030_RAMP_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); /* Wait ramp delay time + 1, so the VMID can settle */ -- cgit v1.2.3-18-g5258 From d5021ec9fc32edc6f512c2375923d757e9825f6a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 12:06:30 +0000 Subject: ASoC: Add a notifier for jack status changes Some systems provide both mechanical and electrical detection of jack status changes. On such systems power savings can be achieved by only enabling the electrical detection methods when physical insertion has been detected. Begin supporting such systems by providing a notifier for jack status changes which can be used to trigger any reconfiguration. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/soc.h | 6 ++++++ sound/soc/soc-jack.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index b8bac6ae624..80dfac16272 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -261,6 +262,10 @@ int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask); int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins); +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb); +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb); #ifdef CONFIG_GPIOLIB int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, struct snd_soc_jack_gpio *gpios); @@ -364,6 +369,7 @@ struct snd_soc_jack { struct snd_soc_card *card; struct list_head pins; int status; + struct blocking_notifier_head notifier; }; /* SoC PCM stream information */ diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 3c07a94c2e3..f8fd22cc70b 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -37,6 +37,7 @@ int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, { jack->card = card; INIT_LIST_HEAD(&jack->pins); + BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); return snd_jack_new(card->codec->card, id, type, &jack->jack); } @@ -93,6 +94,9 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) snd_soc_dapm_disable_pin(codec, pin->pin); } + /* Report before the DAPM sync to help users updating micbias status */ + blocking_notifier_call_chain(&jack->notifier, status, NULL); + snd_soc_dapm_sync(codec); snd_jack_report(jack->jack, status); @@ -143,6 +147,40 @@ int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, } EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); +/** + * snd_soc_jack_notifier_register - Register a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to register + * + * Register for notification of the current status of the jack. Note + * that it is not possible to report additional jack events in the + * callback from the notifier, this is intended to support + * applications such as enabling electrical detection only when a + * mechanical detection event has occurred. + */ +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_register(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register); + +/** + * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to unregister + * + * Stop notifying for status changes. + */ +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister); + #ifdef CONFIG_GPIOLIB /* gpio detect */ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) -- cgit v1.2.3-18-g5258 From 5b9e87ccccf77f46c006c2cf0988a66d0f3f310d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 13:36:13 +0000 Subject: ASoC: Allow force enabled pins to be disabled Some systems, such as those with mechanical jack detection, may wish to force enable a pin (typically mic bias) only some of the time. Support such systems by having disable_pin() also coveer force enabled pins. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-dapm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 61128419400..476dbe6daba 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1266,6 +1266,9 @@ static int snd_soc_dapm_set_pin(struct snd_soc_codec *codec, if (!strcmp(w->name, pin)) { pr_debug("dapm: %s: pin %s\n", codec->name, pin); w->connected = status; + /* Allow disabling of forced pins */ + if (status == 0) + w->force = 0; return 0; } } -- cgit v1.2.3-18-g5258 From 2f14430af52fff08ed1987a3cee612e133a6b82f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 13:48:36 +0000 Subject: ASoC: Move WM8350 microphone detection bias managment out of driver Allow machines to control exactly when the bias is turned on and off. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8350.c | 2 -- sound/soc/imx/wm1133-ev1.c | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index ff6c10155ee..fb1005edee7 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1475,8 +1475,6 @@ int wm8350_mic_jack_detect(struct snd_soc_codec *codec, wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, WM8350_MIC_DET_ENA); - snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); - return 0; } EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c index cec8bae7ac1..a6e7d949763 100644 --- a/sound/soc/imx/wm1133-ev1.c +++ b/sound/soc/imx/wm1133-ev1.c @@ -234,6 +234,8 @@ static int wm1133_ev1_init(struct snd_soc_codec *codec) wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, SND_JACK_BTN_0); + snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); + return 0; } -- cgit v1.2.3-18-g5258 From f06bce9c8c54354ea4ff96852bd92e10204c8cfb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 15:30:30 +0000 Subject: ASoC: Allow disabling of WM835x jack detection If no report is specified then disable detection. Note that we don't disable the slow clock, though the power consumption from it should be negligable. That should be reference counted, ideally through DAPM. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8350.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index fb1005edee7..8ac92f5e4f9 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1393,7 +1393,8 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data) * @jack: jack to report detection events on * @report: value to report * - * Enables the headphone jack detection of the WM8350. + * Enables the headphone jack detection of the WM8350. If no report + * is specified then detection is disabled. */ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report) @@ -1422,8 +1423,12 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, return -EINVAL; } - wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); - wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + if (report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + } else { + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, ena); + } /* Sync status */ wm8350_hp_jack_handler(irq + wm8350->irq_base, priv); @@ -1459,7 +1464,8 @@ static irqreturn_t wm8350_mic_handler(int irq, void *data) * @detect_report: value to report when presence detected * @short_report: value to report when microphone short detected * - * Enables the microphone jack detection of the WM8350. + * Enables the microphone jack detection of the WM8350. If both reports + * are specified as zero then detection is disabled. */ int wm8350_mic_jack_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, @@ -1472,8 +1478,14 @@ int wm8350_mic_jack_detect(struct snd_soc_codec *codec, priv->mic.report = detect_report; priv->mic.short_report = short_report; - wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); - wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, WM8350_MIC_DET_ENA); + if (detect_report || short_report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } else { + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } return 0; } -- cgit v1.2.3-18-g5258 From 69266866a5790080d7fe80094b28d670ff8aa765 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 16:37:01 +0000 Subject: ASoC: Allow WM8903 mic detect disable and don't force bias on Don't force enable the microphone bias on WM8903 when doing jack detection, and don't force enable microphone bias. This allows platforms to only enable microphone detection when a jack has been inserted. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8903.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index b5427b47d6f..134b17532f2 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1465,7 +1465,7 @@ int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int det, int shrt) { struct wm8903_priv *wm8903 = codec->private_data; - int irq_mask = 0; + int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT; dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n", det, shrt); @@ -1485,16 +1485,17 @@ int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, WM8903_MICDET_EINT | WM8903_MICSHRT_EINT, irq_mask); - /* Enable mic detection, this may not have been set through - * platform data (eg, if the defaults are OK). */ - snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, - WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); - snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, - WM8903_MICDET_ENA, WM8903_MICDET_ENA); - - /* Force the microphone bias on; this will trigger an initial - * detection. */ - snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); + if (det && shrt) { + /* Enable mic detection, this may not have been set through + * platform data (eg, if the defaults are OK). */ + snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, WM8903_MICDET_ENA); + } else { + snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, 0); + } return 0; } -- cgit v1.2.3-18-g5258 From 306ff3e473a970f88680e8355c0900fcab0357e2 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Mon, 22 Mar 2010 15:12:07 +0100 Subject: ALSA: ua101: remove experimental status Now that the EHCI driver copes with small iso packets without blowing up, take the snd-ua101 driver out of the alpha-test stage. Signed-off-by: Clemens Ladisch Signed-off-by: Jaroslav Kysela --- sound/usb/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index c570ae3e6d5..3273b47b3a6 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -22,8 +22,7 @@ config SND_USB_AUDIO will be called snd-usb-audio. config SND_USB_UA101 - tristate "Edirol UA-101/UA-1000 driver (EXPERIMENTAL)" - depends on EXPERIMENTAL + tristate "Edirol UA-101/UA-1000 driver" select SND_PCM select SND_RAWMIDI help -- cgit v1.2.3-18-g5258 From 778a76e2dbdb896d005849e9e74518d6aba85671 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Mar 2010 22:05:10 +0000 Subject: ASoC: Implement WM8994 DAI tristate support This also adds the first DAI operation for AIF3 so fill out the ID and the ops for that too. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8994.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 29f3771c33a..d01d3091fe8 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3332,6 +3332,36 @@ static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) return 0; } +static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int reg, val, mask; + + switch (codec_dai->id) { + case 1: + reg = WM8994_AIF1_MASTER_SLAVE; + mask = WM8994_AIF1_TRI; + break; + case 2: + reg = WM8994_AIF2_MASTER_SLAVE; + mask = WM8994_AIF2_TRI; + break; + case 3: + reg = WM8994_POWER_MANAGEMENT_6; + mask = WM8994_AIF3_TRI; + break; + default: + return -EINVAL; + } + + if (tristate) + val = mask; + else + val = 0; + + return snd_soc_update_bits(codec, reg, mask, reg); +} + #define WM8994_RATES SNDRV_PCM_RATE_8000_96000 #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ @@ -3343,6 +3373,7 @@ static struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .hw_params = wm8994_hw_params, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, }; static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { @@ -3351,6 +3382,11 @@ static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { .hw_params = wm8994_hw_params, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, +}; + +static struct snd_soc_dai_ops wm8994_aif3_dai_ops = { + .set_tristate = wm8994_set_tristate, }; struct snd_soc_dai wm8994_dai[] = { @@ -3394,6 +3430,7 @@ struct snd_soc_dai wm8994_dai[] = { }, { .name = "WM8994 AIF3", + .id = 3, .playback = { .stream_name = "AIF3 Playback", .channels_min = 2, @@ -3408,6 +3445,7 @@ struct snd_soc_dai wm8994_dai[] = { .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, + .ops = &wm8994_aif3_dai_ops, } }; EXPORT_SYMBOL_GPL(wm8994_dai); -- cgit v1.2.3-18-g5258 From 4b6316b4b16c7fb5d51df43f0371416e054e7102 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 23 Mar 2010 16:27:28 +0900 Subject: ASoC: ak4642: Add pll select support Current ak4642 was not able to select pll. This patch add support it. It still expect PLL base input pin is MCKI. see Table 5 "setting of PLL Mode" of datasheet Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 40 +++++++++++++++++++++++++++++++++------- sound/soc/sh/fsi-ak4642.c | 10 ++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 3ef16bbc8c8..d5bd4cae73a 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -80,12 +80,18 @@ #define AK4642_CACHEREGNUM 0x25 +/* MD_CTL1 */ +#define PLL3 (1 << 7) +#define PLL2 (1 << 6) +#define PLL1 (1 << 5) +#define PLL0 (1 << 4) +#define PLL_MASK (PLL3 | PLL2 | PLL1 | PLL0) + struct snd_soc_codec_device soc_codec_dev_ak4642; /* codec private data */ struct ak4642_priv { struct snd_soc_codec codec; - unsigned int sysclk; }; static struct snd_soc_codec *ak4642_codec; @@ -249,9 +255,32 @@ static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct ak4642_priv *ak4642 = codec->private_data; + u8 pll; + + switch (freq) { + case 11289600: + pll = PLL2; + break; + case 12288000: + pll = PLL2 | PLL0; + break; + case 12000000: + pll = PLL2 | PLL1; + break; + case 24000000: + pll = PLL2 | PLL1 | PLL0; + break; + case 13500000: + pll = PLL3 | PLL2; + break; + case 27000000: + pll = PLL3 | PLL2 | PLL0; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, MD_CTL1, PLL_MASK, pll); - ak4642->sysclk = freq; return 0; } @@ -342,7 +371,6 @@ static int ak4642_init(struct ak4642_priv *ak4642) * * Audio I/F Format: MSB justified (ADC & DAC) * BICK frequency at Master Mode: 64fs - * Input Master Clock Select at PLL Mode: 11.2896MHz * MCKO: Enable * Sampling Frequency: 44.1kHz * @@ -352,10 +380,8 @@ static int ak4642_init(struct ak4642_priv *ak4642) * please fix-me */ ak4642_write(codec, 0x01, 0x08); - ak4642_write(codec, 0x04, 0x4a); ak4642_write(codec, 0x05, 0x27); - ak4642_write(codec, 0x00, 0x40); - ak4642_write(codec, 0x01, 0x0b); + ak4642_write(codec, 0x04, 0x0a); return ret; diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index 5263ab18f82..c0207dc524b 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -22,11 +22,21 @@ #include #include <../sound/soc/codecs/ak4642.h> +static int fsi_ak4642_dai_init(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dai_set_sysclk(&ak4642_dai, 0, 11289600, 0); + + return ret; +} + static struct snd_soc_dai_link fsi_dai_link = { .name = "AK4642", .stream_name = "AK4642", .cpu_dai = &fsi_soc_dai[0], /* fsi */ .codec_dai = &ak4642_dai, + .init = fsi_ak4642_dai_init, .ops = NULL, }; -- cgit v1.2.3-18-g5258 From 0643ce8f42e763635d8d6cf311730a6e84d4be92 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 15 Mar 2010 18:10:50 +0900 Subject: ASoC: ak4642: Add set_fmt function for snd_soc_dai_ops Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 69 ++++++++++++++++++++++++++++++----------------- sound/soc/sh/fsi-ak4642.c | 4 +++ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index d5bd4cae73a..3452bd75273 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -80,6 +80,17 @@ #define AK4642_CACHEREGNUM 0x25 +/* PW_MGMT2 */ +#define HPMTN (1 << 6) +#define PMHPL (1 << 5) +#define PMHPR (1 << 4) +#define MS (1 << 3) /* master/slave select */ +#define MCKO (1 << 1) +#define PMPLL (1 << 0) + +#define PMHP_MASK (PMHPL | PMHPR) +#define PMHP PMHP_MASK + /* MD_CTL1 */ #define PLL3 (1 << 7) #define PLL2 (1 << 6) @@ -87,6 +98,9 @@ #define PLL0 (1 << 4) #define PLL_MASK (PLL3 | PLL2 | PLL1 | PLL0) +#define BCKO_MASK (1 << 3) +#define BCKO_64 BCKO_MASK + struct snd_soc_codec_device soc_codec_dev_ak4642; /* codec private data */ @@ -188,9 +202,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * This operation came from example code of * "ASAHI KASEI AK4642" (japanese) manual p97. - * - * Example code use 0x39, 0x79 value for 0x01 address, - * But we need MCKO (0x02) bit now */ ak4642_write(codec, 0x05, 0x27); ak4642_write(codec, 0x0f, 0x09); @@ -200,8 +211,8 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, ak4642_write(codec, 0x0a, 0x28); ak4642_write(codec, 0x0d, 0x28); ak4642_write(codec, 0x00, 0x64); - ak4642_write(codec, 0x01, 0x3b); /* + MCKO bit */ - ak4642_write(codec, 0x01, 0x7b); /* + MCKO bit */ + snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP); + snd_soc_update_bits(codec, PW_MGMT2, HPMTN, HPMTN); } else { /* * start stereo input @@ -238,8 +249,8 @@ static void ak4642_dai_shutdown(struct snd_pcm_substream *substream, if (is_play) { /* stop headphone output */ - ak4642_write(codec, 0x01, 0x3b); - ak4642_write(codec, 0x01, 0x0b); + snd_soc_update_bits(codec, PW_MGMT2, HPMTN, 0); + snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, 0); ak4642_write(codec, 0x00, 0x40); ak4642_write(codec, 0x0e, 0x11); ak4642_write(codec, 0x0f, 0x08); @@ -284,10 +295,37 @@ static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai, return 0; } +static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u8 data; + u8 bcko; + + data = MCKO | PMPLL; /* use MCKO */ + bcko = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + data |= MS; + bcko = BCKO_64; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, PW_MGMT2, MS, data); + snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko); + + return 0; +} + static struct snd_soc_dai_ops ak4642_dai_ops = { .startup = ak4642_dai_startup, .shutdown = ak4642_dai_shutdown, .set_sysclk = ak4642_dai_set_sysclk, + .set_fmt = ak4642_dai_set_fmt, }; struct snd_soc_dai ak4642_dai = { @@ -366,23 +404,6 @@ static int ak4642_init(struct ak4642_priv *ak4642) goto reg_cache_err; } - /* - * clock setting - * - * Audio I/F Format: MSB justified (ADC & DAC) - * BICK frequency at Master Mode: 64fs - * MCKO: Enable - * Sampling Frequency: 44.1kHz - * - * This operation came from example code of - * "ASAHI KASEI AK4642" (japanese) manual p89. - * - * please fix-me - */ - ak4642_write(codec, 0x01, 0x08); - ak4642_write(codec, 0x05, 0x27); - ak4642_write(codec, 0x04, 0x0a); - return ret; reg_cache_err: diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index c0207dc524b..be018542314 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -26,6 +26,10 @@ static int fsi_ak4642_dai_init(struct snd_soc_codec *codec) { int ret; + ret = snd_soc_dai_set_fmt(&ak4642_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_sysclk(&ak4642_dai, 0, 11289600, 0); return ret; -- cgit v1.2.3-18-g5258 From 1ad747ca9b6f97f895e0a6ccd447b158aeaa568d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 23 Mar 2010 16:27:38 +0900 Subject: ASoC: ak4642: Add enhanced sampling rate Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 68 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 3452bd75273..de1809dc8d9 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -101,6 +101,13 @@ #define BCKO_MASK (1 << 3) #define BCKO_64 BCKO_MASK +/* MD_CTL2 */ +#define FS0 (1 << 0) +#define FS1 (1 << 1) +#define FS2 (1 << 2) +#define FS3 (1 << 5) +#define FS_MASK (FS0 | FS1 | FS2 | FS3) + struct snd_soc_codec_device soc_codec_dev_ak4642; /* codec private data */ @@ -196,14 +203,12 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * PLL, Master Mode * Audio I/F Format :MSB justified (ADC & DAC) - * Sampling Frequency: 44.1kHz - * Digital Volume: −8dB + * Digital Volume: -8dB * Bass Boost Level : Middle * * This operation came from example code of * "ASAHI KASEI AK4642" (japanese) manual p97. */ - ak4642_write(codec, 0x05, 0x27); ak4642_write(codec, 0x0f, 0x09); ak4642_write(codec, 0x0e, 0x19); ak4642_write(codec, 0x09, 0x91); @@ -219,7 +224,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * PLL Master Mode * Audio I/F Format:MSB justified (ADC & DAC) - * Sampling Frequency:44.1kHz * Pre MIC AMP:+20dB * MIC Power On * ALC setting:Refer to Table 35 @@ -228,7 +232,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * This operation came from example code of * "ASAHI KASEI AK4642" (japanese) manual p94. */ - ak4642_write(codec, 0x05, 0x27); ak4642_write(codec, 0x02, 0x05); ak4642_write(codec, 0x06, 0x3c); ak4642_write(codec, 0x08, 0xe1); @@ -321,11 +324,65 @@ static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static int ak4642_dai_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; + u8 rate; + + switch (params_rate(params)) { + case 7350: + rate = FS2; + break; + case 8000: + rate = 0; + break; + case 11025: + rate = FS2 | FS0; + break; + case 12000: + rate = FS0; + break; + case 14700: + rate = FS2 | FS1; + break; + case 16000: + rate = FS1; + break; + case 22050: + rate = FS2 | FS1 | FS0; + break; + case 24000: + rate = FS1 | FS0; + break; + case 29400: + rate = FS3 | FS2 | FS1; + break; + case 32000: + rate = FS3 | FS1; + break; + case 44100: + rate = FS3 | FS2 | FS1 | FS0; + break; + case 48000: + rate = FS3 | FS1 | FS0; + break; + default: + return -EINVAL; + break; + } + snd_soc_update_bits(codec, MD_CTL2, FS_MASK, rate); + + return 0; +} + static struct snd_soc_dai_ops ak4642_dai_ops = { .startup = ak4642_dai_startup, .shutdown = ak4642_dai_shutdown, .set_sysclk = ak4642_dai_set_sysclk, .set_fmt = ak4642_dai_set_fmt, + .hw_params = ak4642_dai_hw_params, }; struct snd_soc_dai ak4642_dai = { @@ -343,6 +400,7 @@ struct snd_soc_dai ak4642_dai = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE }, .ops = &ak4642_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(ak4642_dai); -- cgit v1.2.3-18-g5258 From ae76148114aa2457baa0d340fc506bb7d114cb12 Mon Sep 17 00:00:00 2001 From: Bernhard Urban Date: Tue, 23 Mar 2010 04:12:38 +0100 Subject: ALSA: aureon - Patch for suspend/resume for Terratec Aureon cards. Add proper suspend/resume code for Terratec Aureon cards. Based on ice1724 suspend/resume work of Igor Chernyshev. Fixes bug https://bugtrack.alsa-project.org/alsa-bug/view.php?id=4944 Tested on linux-2.6.32.9 Signed-off-by: Bernhard Urban Signed-off-by: Takashi Iwai --- sound/pci/ice1712/aureon.c | 89 +++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/sound/pci/ice1712/aureon.c b/sound/pci/ice1712/aureon.c index 9e66f6d306f..2f6252266a0 100644 --- a/sound/pci/ice1712/aureon.c +++ b/sound/pci/ice1712/aureon.c @@ -1956,11 +1956,10 @@ static int __devinit aureon_add_controls(struct snd_ice1712 *ice) return 0; } - /* - * initialize the chip + * reset the chip */ -static int __devinit aureon_init(struct snd_ice1712 *ice) +static int aureon_reset(struct snd_ice1712 *ice) { static const unsigned short wm_inits_aureon[] = { /* These come first to reduce init pop noise */ @@ -2047,30 +2046,10 @@ static int __devinit aureon_init(struct snd_ice1712 *ice) 0x0605, /* slave, 24bit, MSB on second OSCLK, SDOUT for right channel when OLRCK is high */ (unsigned short)-1 }; - struct aureon_spec *spec; unsigned int tmp; const unsigned short *p; - int err, i; - - spec = kzalloc(sizeof(*spec), GFP_KERNEL); - if (!spec) - return -ENOMEM; - ice->spec = spec; - - if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) { - ice->num_total_dacs = 6; - ice->num_total_adcs = 2; - } else { - /* aureon 7.1 and prodigy 7.1 */ - ice->num_total_dacs = 8; - ice->num_total_adcs = 2; - } - - /* to remeber the register values of CS8415 */ - ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL); - if (!ice->akm) - return -ENOMEM; - ice->akm_codecs = 1; + int err; + struct aureon_spec *spec = ice->spec; err = aureon_ac97_init(ice); if (err != 0) @@ -2118,6 +2097,61 @@ static int __devinit aureon_init(struct snd_ice1712 *ice) /* initialize PCA9554 pin directions & set default input */ aureon_pca9554_write(ice, PCA9554_DIR, 0x00); aureon_pca9554_write(ice, PCA9554_OUT, 0x00); /* internal AUX */ + return 0; +} + +/* + * suspend/resume + */ +#ifdef CONFIG_PM +static int aureon_resume(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec = ice->spec; + int err, i; + + err = aureon_reset(ice); + if (err != 0) + return err; + + /* workaround for poking volume with alsamixer after resume: + * just set stored volume again */ + for (i = 0; i < ice->num_total_dacs; i++) + wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]); + return 0; +} +#endif + +/* + * initialize the chip + */ +static int __devinit aureon_init(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec; + int i, err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) { + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + } else { + /* aureon 7.1 and prodigy 7.1 */ + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + } + + /* to remeber the register values of CS8415 */ + ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL); + if (!ice->akm) + return -ENOMEM; + ice->akm_codecs = 1; + + err = aureon_reset(ice); + if (err != 0) + return err; spec->master[0] = WM_VOL_MUTE; spec->master[1] = WM_VOL_MUTE; @@ -2126,6 +2160,11 @@ static int __devinit aureon_init(struct snd_ice1712 *ice) wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]); } +#ifdef CONFIG_PM + ice->pm_resume = aureon_resume; + ice->pm_suspend_enabled = 1; +#endif + return 0; } -- cgit v1.2.3-18-g5258 From feb58cffca772097a849b413fb7954f87e9e9e71 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 24 Mar 2010 15:27:24 +0900 Subject: ASoC: fsi: ensures process inside master lock Bit operation for fsi_master should be done inside master lock. But soft-reset/interrupt operation were outside of it. This patch modify this problem. It still allow to INT_ST outside-operation on fsi_interrupt, but it is not problem. Because this register doesn't need the bit operation. Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/sh/fsi.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 993abb730df..db91349b680 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -79,6 +79,12 @@ #define INT_A_IN (1 << 4) #define INT_A_OUT (1 << 0) +/* SOFT_RST */ +#define PBSR (1 << 12) /* Port B Software Reset */ +#define PASR (1 << 8) /* Port A Software Reset */ +#define IR (1 << 4) /* Interrupt Reset */ +#define FSISR (1 << 0) /* Software Reset */ + #define FSI_RATES SNDRV_PCM_RATE_8000_96000 #define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) @@ -372,18 +378,13 @@ static void fsi_irq_init(struct fsi_priv *fsi, int is_play) static void fsi_soft_all_reset(struct fsi_master *master) { - u32 status = fsi_master_read(master, SOFT_RST); - /* port AB reset */ - status &= 0x000000ff; - fsi_master_write(master, SOFT_RST, status); + fsi_master_mask_set(master, SOFT_RST, PASR | PBSR, 0); mdelay(10); /* soft reset */ - status &= 0x000000f0; - fsi_master_write(master, SOFT_RST, status); - status |= 0x00000001; - fsi_master_write(master, SOFT_RST, status); + fsi_master_mask_set(master, SOFT_RST, FSISR, 0); + fsi_master_mask_set(master, SOFT_RST, FSISR, FSISR); mdelay(10); } @@ -558,12 +559,11 @@ static int fsi_data_pop(struct fsi_priv *fsi, int startup) static irqreturn_t fsi_interrupt(int irq, void *data) { struct fsi_master *master = data; - u32 status = fsi_master_read(master, SOFT_RST) & ~0x00000010; u32 int_st = fsi_master_read(master, INT_ST); /* clear irq status */ - fsi_master_write(master, SOFT_RST, status); - fsi_master_write(master, SOFT_RST, status | 0x00000010); + fsi_master_mask_set(master, SOFT_RST, IR, 0); + fsi_master_mask_set(master, SOFT_RST, IR, IR); if (int_st & INT_A_OUT) fsi_data_push(&master->fsia, 0); -- cgit v1.2.3-18-g5258 From 10ea76cc25b6738eb2f7c58b3312e1ebc61f753e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 23 Mar 2010 11:47:54 +0900 Subject: ASoC: fsi: IRQ related process had be united Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/sh/fsi.c | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index db91349b680..7c295df6e85 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -322,7 +322,7 @@ static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play) /************************************************************************ - ctrl function + irq function ************************************************************************/ @@ -344,6 +344,35 @@ static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) fsi_master_mask_set(master, IEMSK, data, 0); } +static u32 fsi_irq_get_status(struct fsi_master *master) +{ + return fsi_master_read(master, INT_ST); +} + +static void fsi_irq_clear_all_status(struct fsi_master *master) +{ + fsi_master_write(master, INT_ST, 0x0000000); +} + +static void fsi_irq_clear_status(struct fsi_priv *fsi) +{ + u32 data = 0; + struct fsi_master *master = fsi_get_master(fsi); + + data |= fsi_port_ab_io_bit(fsi, 0); + data |= fsi_port_ab_io_bit(fsi, 1); + + /* clear interrupt factor */ + fsi_master_mask_set(master, INT_ST, data, 0); +} + +/************************************************************************ + + + ctrl function + + +************************************************************************/ static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) { u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4); @@ -355,25 +384,17 @@ static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) fsi_master_mask_set(master, CLK_RST, val, 0); } -static void fsi_irq_init(struct fsi_priv *fsi, int is_play) +static void fsi_fifo_init(struct fsi_priv *fsi, int is_play) { - u32 data; u32 ctrl; - data = fsi_port_ab_io_bit(fsi, is_play); ctrl = is_play ? DOFF_CTL : DIFF_CTL; - /* set IMSK */ - fsi_irq_disable(fsi, is_play); - /* set interrupt generation factor */ fsi_reg_write(fsi, ctrl, IRQ_HALF); /* clear FIFO */ fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR); - - /* clear interrupt factor */ - fsi_master_mask_set(fsi_get_master(fsi), INT_ST, data, 0); } static void fsi_soft_all_reset(struct fsi_master *master) @@ -559,7 +580,7 @@ static int fsi_data_pop(struct fsi_priv *fsi, int startup) static irqreturn_t fsi_interrupt(int irq, void *data) { struct fsi_master *master = data; - u32 int_st = fsi_master_read(master, INT_ST); + u32 int_st = fsi_irq_get_status(master); /* clear irq status */ fsi_master_mask_set(master, SOFT_RST, IR, 0); @@ -574,7 +595,7 @@ static irqreturn_t fsi_interrupt(int irq, void *data) if (int_st & INT_B_IN) fsi_data_pop(&master->fsib, 0); - fsi_master_write(master, INT_ST, 0x0000000); + fsi_irq_clear_all_status(master); return IRQ_HANDLED; } @@ -699,8 +720,12 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, if (is_master) fsi_clk_ctrl(fsi, 1); - /* irq setting */ - fsi_irq_init(fsi, is_play); + /* irq clear */ + fsi_irq_disable(fsi, is_play); + fsi_irq_clear_status(fsi); + + /* fifo init */ + fsi_fifo_init(fsi, is_play); return ret; } -- cgit v1.2.3-18-g5258 From fca5bca48759c21eddc0667a4582a227d7b0165a Mon Sep 17 00:00:00 2001 From: Felix Homann Date: Thu, 25 Mar 2010 11:29:14 +0100 Subject: ALSA: usbaudio: Add basic support for M-Audio Fast Track Ultra series This adds basic support for M-Audio's Fast Track Ultra series of USB audio interfaces. It is a refactored version of the patch Clemens Ladisch posted some time ago. Neither playback nor capturing work properly at 44100 Hz (don't know why). The other sampling rates work properly. There's no support for the DSP mixer, yet. Signed-off-by: Felix Homann Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 2 ++ sound/usb/quirks-table.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/urb.c | 5 +++++ 3 files changed, 61 insertions(+) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index b1309cdc1ac..7b84b61a078 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -317,6 +317,8 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) break; case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */ case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra 8 */ + case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ /* doesn't set the sample rate attribute, but supports it */ fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; break; diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 81c5f8a312c..91ddef31bcb 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1826,6 +1826,60 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + USB_DEVICE(0x0763, 0x2080), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track Ultra 8", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + /* interface 3 (MIDI) is standard compliant */ + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0763, 0x2081), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track Ultra 8R", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + /* interface 3 (MIDI) is standard compliant */ + { + .ifnum = -1 + } + } + } +}, /* Casio devices */ { diff --git a/sound/usb/urb.c b/sound/usb/urb.c index ad50d439892..e2b7c478424 100644 --- a/sound/usb/urb.c +++ b/sound/usb/urb.c @@ -918,6 +918,11 @@ void snd_usb_init_substream(struct snd_usb_stream *as, case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ subs->ops.retire_sync = retire_playback_sync_urb_hs_emu; break; + case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra 8 */ + case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ + subs->ops.prepare_sync = prepare_playback_sync_urb; + subs->ops.retire_sync = retire_playback_sync_urb; + break; } } -- cgit v1.2.3-18-g5258 From 7177395fdd919e561544a7d1c0ac196098a2ae2d Mon Sep 17 00:00:00 2001 From: Sedji Gaouaou Date: Mon, 1 Mar 2010 12:19:18 +0100 Subject: ALSA: AC97: add AC97 support for AT91. This patch add AC97 support for ATMEL AT91, using the AVR32 code. While AVR is using a DMA, the AT91 chips are using a Peripheral Data Controller. Signed-off-by: Sedji Gaouaou Acked-by: Liam Girdwood Acked-by: Mark Brown Signed-off-by: Takashi Iwai --- sound/atmel/Kconfig | 2 +- sound/atmel/ac97c.c | 339 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 255 insertions(+), 86 deletions(-) diff --git a/sound/atmel/Kconfig b/sound/atmel/Kconfig index 6c228a91940..94de43a096f 100644 --- a/sound/atmel/Kconfig +++ b/sound/atmel/Kconfig @@ -12,7 +12,7 @@ config SND_ATMEL_AC97C tristate "Atmel AC97 Controller (AC97C) driver" select SND_PCM select SND_AC97_CODEC - depends on DW_DMAC && AVR32 + depends on (DW_DMAC && AVR32) || ARCH_AT91 help ALSA sound driver for the Atmel AC97 controller. diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c index 0c0f8771656..3c0a6f4e358 100644 --- a/sound/atmel/ac97c.c +++ b/sound/atmel/ac97c.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,10 @@ #include +#include +#include +#include + #include "ac97c.h" enum { @@ -63,6 +68,7 @@ struct atmel_ac97c { u64 cur_format; unsigned int cur_rate; unsigned long flags; + int playback_period, capture_period; /* Serialize access to opened variable */ spinlock_t lock; void __iomem *regs; @@ -242,10 +248,12 @@ static int atmel_ac97c_playback_hw_params(struct snd_pcm_substream *substream, if (retval < 0) return retval; /* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */ - if (retval == 1) - if (test_and_clear_bit(DMA_TX_READY, &chip->flags)) - dw_dma_cyclic_free(chip->dma.tx_chan); - + if (cpu_is_at32ap7000()) { + /* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */ + if (retval == 1) + if (test_and_clear_bit(DMA_TX_READY, &chip->flags)) + dw_dma_cyclic_free(chip->dma.tx_chan); + } /* Set restrictions to params. */ mutex_lock(&opened_mutex); chip->cur_rate = params_rate(hw_params); @@ -266,9 +274,14 @@ static int atmel_ac97c_capture_hw_params(struct snd_pcm_substream *substream, if (retval < 0) return retval; /* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */ - if (retval == 1) - if (test_and_clear_bit(DMA_RX_READY, &chip->flags)) - dw_dma_cyclic_free(chip->dma.rx_chan); + if (cpu_is_at32ap7000()) { + if (retval < 0) + return retval; + /* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */ + if (retval == 1) + if (test_and_clear_bit(DMA_RX_READY, &chip->flags)) + dw_dma_cyclic_free(chip->dma.rx_chan); + } /* Set restrictions to params. */ mutex_lock(&opened_mutex); @@ -282,16 +295,20 @@ static int atmel_ac97c_capture_hw_params(struct snd_pcm_substream *substream, static int atmel_ac97c_playback_hw_free(struct snd_pcm_substream *substream) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); - if (test_and_clear_bit(DMA_TX_READY, &chip->flags)) - dw_dma_cyclic_free(chip->dma.tx_chan); + if (cpu_is_at32ap7000()) { + if (test_and_clear_bit(DMA_TX_READY, &chip->flags)) + dw_dma_cyclic_free(chip->dma.tx_chan); + } return snd_pcm_lib_free_pages(substream); } static int atmel_ac97c_capture_hw_free(struct snd_pcm_substream *substream) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); - if (test_and_clear_bit(DMA_RX_READY, &chip->flags)) - dw_dma_cyclic_free(chip->dma.rx_chan); + if (cpu_is_at32ap7000()) { + if (test_and_clear_bit(DMA_RX_READY, &chip->flags)) + dw_dma_cyclic_free(chip->dma.rx_chan); + } return snd_pcm_lib_free_pages(substream); } @@ -299,9 +316,11 @@ static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; + int block_size = frames_to_bytes(runtime, runtime->period_size); unsigned long word = ac97c_readl(chip, OCA); int retval; + chip->playback_period = 0; word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); /* assign channels to AC97C channel A */ @@ -324,7 +343,8 @@ static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream) switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: - word |= AC97C_CMR_CEM_LITTLE; + if (cpu_is_at32ap7000()) + word |= AC97C_CMR_CEM_LITTLE; break; case SNDRV_PCM_FORMAT_S16_BE: /* fall through */ word &= ~(AC97C_CMR_CEM_LITTLE); @@ -363,9 +383,18 @@ static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream) dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n", runtime->rate); - if (!test_bit(DMA_TX_READY, &chip->flags)) - retval = atmel_ac97c_prepare_dma(chip, substream, - DMA_TO_DEVICE); + if (cpu_is_at32ap7000()) { + if (!test_bit(DMA_TX_READY, &chip->flags)) + retval = atmel_ac97c_prepare_dma(chip, substream, + DMA_TO_DEVICE); + } else { + /* Initialize and start the PDC */ + writel(runtime->dma_addr, chip->regs + ATMEL_PDC_TPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_TCR); + writel(runtime->dma_addr + block_size, + chip->regs + ATMEL_PDC_TNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_TNCR); + } return retval; } @@ -374,9 +403,11 @@ static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; + int block_size = frames_to_bytes(runtime, runtime->period_size); unsigned long word = ac97c_readl(chip, ICA); int retval; + chip->capture_period = 0; word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); /* assign channels to AC97C channel A */ @@ -399,7 +430,8 @@ static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream) switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: - word |= AC97C_CMR_CEM_LITTLE; + if (cpu_is_at32ap7000()) + word |= AC97C_CMR_CEM_LITTLE; break; case SNDRV_PCM_FORMAT_S16_BE: /* fall through */ word &= ~(AC97C_CMR_CEM_LITTLE); @@ -438,9 +470,18 @@ static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream) dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n", runtime->rate); - if (!test_bit(DMA_RX_READY, &chip->flags)) - retval = atmel_ac97c_prepare_dma(chip, substream, - DMA_FROM_DEVICE); + if (cpu_is_at32ap7000()) { + if (!test_bit(DMA_RX_READY, &chip->flags)) + retval = atmel_ac97c_prepare_dma(chip, substream, + DMA_FROM_DEVICE); + } else { + /* Initialize and start the PDC */ + writel(runtime->dma_addr, chip->regs + ATMEL_PDC_RPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_RCR); + writel(runtime->dma_addr + block_size, + chip->regs + ATMEL_PDC_RNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_RNCR); + } return retval; } @@ -449,7 +490,7 @@ static int atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); - unsigned long camr; + unsigned long camr, ptcr = 0; int retval = 0; camr = ac97c_readl(chip, CAMR); @@ -458,15 +499,22 @@ atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ case SNDRV_PCM_TRIGGER_START: - retval = dw_dma_cyclic_start(chip->dma.tx_chan); - if (retval) - goto out; + if (cpu_is_at32ap7000()) { + retval = dw_dma_cyclic_start(chip->dma.tx_chan); + if (retval) + goto out; + } else { + ptcr = ATMEL_PDC_TXTEN; + } camr |= AC97C_CMR_CENA; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ case SNDRV_PCM_TRIGGER_STOP: - dw_dma_cyclic_stop(chip->dma.tx_chan); + if (cpu_is_at32ap7000()) + dw_dma_cyclic_stop(chip->dma.tx_chan); + else + ptcr |= ATMEL_PDC_TXTDIS; if (chip->opened <= 1) camr &= ~AC97C_CMR_CENA; break; @@ -476,6 +524,8 @@ atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd) } ac97c_writel(chip, CAMR, camr); + if (!cpu_is_at32ap7000()) + writel(ptcr, chip->regs + ATMEL_PDC_PTCR); out: return retval; } @@ -484,24 +534,32 @@ static int atmel_ac97c_capture_trigger(struct snd_pcm_substream *substream, int cmd) { struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); - unsigned long camr; + unsigned long camr, ptcr = 0; int retval = 0; camr = ac97c_readl(chip, CAMR); + ptcr = readl(chip->regs + ATMEL_PDC_PTSR); switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ case SNDRV_PCM_TRIGGER_START: - retval = dw_dma_cyclic_start(chip->dma.rx_chan); - if (retval) - goto out; + if (cpu_is_at32ap7000()) { + retval = dw_dma_cyclic_start(chip->dma.rx_chan); + if (retval) + goto out; + } else { + ptcr = ATMEL_PDC_RXTEN; + } camr |= AC97C_CMR_CENA; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ case SNDRV_PCM_TRIGGER_STOP: - dw_dma_cyclic_stop(chip->dma.rx_chan); + if (cpu_is_at32ap7000()) + dw_dma_cyclic_stop(chip->dma.rx_chan); + else + ptcr |= (ATMEL_PDC_RXTDIS); if (chip->opened <= 1) camr &= ~AC97C_CMR_CENA; break; @@ -511,6 +569,8 @@ atmel_ac97c_capture_trigger(struct snd_pcm_substream *substream, int cmd) } ac97c_writel(chip, CAMR, camr); + if (!cpu_is_at32ap7000()) + writel(ptcr, chip->regs + ATMEL_PDC_PTCR); out: return retval; } @@ -523,7 +583,10 @@ atmel_ac97c_playback_pointer(struct snd_pcm_substream *substream) snd_pcm_uframes_t frames; unsigned long bytes; - bytes = dw_dma_get_src_addr(chip->dma.tx_chan); + if (cpu_is_at32ap7000()) + bytes = dw_dma_get_src_addr(chip->dma.tx_chan); + else + bytes = readl(chip->regs + ATMEL_PDC_TPR); bytes -= runtime->dma_addr; frames = bytes_to_frames(runtime, bytes); @@ -540,7 +603,10 @@ atmel_ac97c_capture_pointer(struct snd_pcm_substream *substream) snd_pcm_uframes_t frames; unsigned long bytes; - bytes = dw_dma_get_dst_addr(chip->dma.rx_chan); + if (cpu_is_at32ap7000()) + bytes = dw_dma_get_dst_addr(chip->dma.rx_chan); + else + bytes = readl(chip->regs + ATMEL_PDC_RPR); bytes -= runtime->dma_addr; frames = bytes_to_frames(runtime, bytes); @@ -578,8 +644,11 @@ static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev) u32 sr = ac97c_readl(chip, SR); u32 casr = ac97c_readl(chip, CASR); u32 cosr = ac97c_readl(chip, COSR); + u32 camr = ac97c_readl(chip, CAMR); if (sr & AC97C_SR_CAEVT) { + struct snd_pcm_runtime *runtime; + int offset, next_period, block_size; dev_info(&chip->pdev->dev, "channel A event%s%s%s%s%s%s\n", casr & AC97C_CSR_OVRUN ? " OVRUN" : "", casr & AC97C_CSR_RXRDY ? " RXRDY" : "", @@ -587,6 +656,50 @@ static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev) casr & AC97C_CSR_TXEMPTY ? " TXEMPTY" : "", casr & AC97C_CSR_TXRDY ? " TXRDY" : "", !casr ? " NONE" : ""); + if (!cpu_is_at32ap7000()) { + if ((casr & camr) & AC97C_CSR_ENDTX) { + runtime = chip->playback_substream->runtime; + block_size = frames_to_bytes(runtime, + runtime->period_size); + chip->playback_period++; + + if (chip->playback_period == runtime->periods) + chip->playback_period = 0; + next_period = chip->playback_period + 1; + if (next_period == runtime->periods) + next_period = 0; + + offset = block_size * next_period; + + writel(runtime->dma_addr + offset, + chip->regs + ATMEL_PDC_TNPR); + writel(block_size / 2, + chip->regs + ATMEL_PDC_TNCR); + + snd_pcm_period_elapsed( + chip->playback_substream); + } + if ((casr & camr) & AC97C_CSR_ENDRX) { + runtime = chip->capture_substream->runtime; + block_size = frames_to_bytes(runtime, + runtime->period_size); + chip->capture_period++; + + if (chip->capture_period == runtime->periods) + chip->capture_period = 0; + next_period = chip->capture_period + 1; + if (next_period == runtime->periods) + next_period = 0; + + offset = block_size * next_period; + + writel(runtime->dma_addr + offset, + chip->regs + ATMEL_PDC_RNPR); + writel(block_size / 2, + chip->regs + ATMEL_PDC_RNCR); + snd_pcm_period_elapsed(chip->capture_substream); + } + } retval = IRQ_HANDLED; } @@ -608,15 +721,50 @@ static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev) return retval; } +static struct ac97_pcm at91_ac97_pcm_defs[] __devinitdata = { + /* Playback */ + { + .exclusive = 1, + .r = { { + .slots = ((1 << AC97_SLOT_PCM_LEFT) + | (1 << AC97_SLOT_PCM_RIGHT)), + } }, + }, + /* PCM in */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = ((1 << AC97_SLOT_PCM_LEFT) + | (1 << AC97_SLOT_PCM_RIGHT)), + } } + }, + /* Mic in */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1<flags); playback = test_bit(DMA_TX_CHAN_PRESENT, &chip->flags); + if (!cpu_is_at32ap7000()) { + err = snd_ac97_pcm_assign(chip->ac97_bus, + ARRAY_SIZE(at91_ac97_pcm_defs), + at91_ac97_pcm_defs); + if (err) + return err; + } retval = snd_pcm_new(chip->card, chip->card->shortname, chip->pdev->id, playback, capture, &pcm); if (retval) @@ -775,7 +923,12 @@ static int __devinit atmel_ac97c_probe(struct platform_device *pdev) return -ENXIO; } - pclk = clk_get(&pdev->dev, "pclk"); + if (cpu_is_at32ap7000()) { + pclk = clk_get(&pdev->dev, "pclk"); + } else { + pclk = clk_get(&pdev->dev, "ac97_clk"); + } + if (IS_ERR(pclk)) { dev_dbg(&pdev->dev, "no peripheral clock\n"); return PTR_ERR(pclk); @@ -844,43 +997,52 @@ static int __devinit atmel_ac97c_probe(struct platform_device *pdev) goto err_ac97_bus; } - if (pdata->rx_dws.dma_dev) { - struct dw_dma_slave *dws = &pdata->rx_dws; - dma_cap_mask_t mask; + if (cpu_is_at32ap7000()) { + if (pdata->rx_dws.dma_dev) { + struct dw_dma_slave *dws = &pdata->rx_dws; + dma_cap_mask_t mask; - dws->rx_reg = regs->start + AC97C_CARHR + 2; + dws->rx_reg = regs->start + AC97C_CARHR + 2; - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); - chip->dma.rx_chan = dma_request_channel(mask, filter, dws); + chip->dma.rx_chan = dma_request_channel(mask, filter, + dws); - dev_info(&chip->pdev->dev, "using %s for DMA RX\n", + dev_info(&chip->pdev->dev, "using %s for DMA RX\n", dev_name(&chip->dma.rx_chan->dev->device)); - set_bit(DMA_RX_CHAN_PRESENT, &chip->flags); - } + set_bit(DMA_RX_CHAN_PRESENT, &chip->flags); + } - if (pdata->tx_dws.dma_dev) { - struct dw_dma_slave *dws = &pdata->tx_dws; - dma_cap_mask_t mask; + if (pdata->tx_dws.dma_dev) { + struct dw_dma_slave *dws = &pdata->tx_dws; + dma_cap_mask_t mask; - dws->tx_reg = regs->start + AC97C_CATHR + 2; + dws->tx_reg = regs->start + AC97C_CATHR + 2; - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); - chip->dma.tx_chan = dma_request_channel(mask, filter, dws); + chip->dma.tx_chan = dma_request_channel(mask, filter, + dws); - dev_info(&chip->pdev->dev, "using %s for DMA TX\n", + dev_info(&chip->pdev->dev, "using %s for DMA TX\n", dev_name(&chip->dma.tx_chan->dev->device)); - set_bit(DMA_TX_CHAN_PRESENT, &chip->flags); - } + set_bit(DMA_TX_CHAN_PRESENT, &chip->flags); + } - if (!test_bit(DMA_RX_CHAN_PRESENT, &chip->flags) && - !test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) { - dev_dbg(&pdev->dev, "DMA not available\n"); - retval = -ENODEV; - goto err_dma; + if (!test_bit(DMA_RX_CHAN_PRESENT, &chip->flags) && + !test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) { + dev_dbg(&pdev->dev, "DMA not available\n"); + retval = -ENODEV; + goto err_dma; + } + } else { + /* Just pretend that we have DMA channel(for at91 i is actually + * the PDC) */ + set_bit(DMA_RX_CHAN_PRESENT, &chip->flags); + set_bit(DMA_TX_CHAN_PRESENT, &chip->flags); } retval = atmel_ac97c_pcm_new(chip); @@ -897,20 +1059,22 @@ static int __devinit atmel_ac97c_probe(struct platform_device *pdev) platform_set_drvdata(pdev, card); - dev_info(&pdev->dev, "Atmel AC97 controller at 0x%p\n", - chip->regs); + dev_info(&pdev->dev, "Atmel AC97 controller at 0x%p, irq = %d\n", + chip->regs, irq); return 0; err_dma: - if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags)) - dma_release_channel(chip->dma.rx_chan); - if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) - dma_release_channel(chip->dma.tx_chan); - clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags); - clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags); - chip->dma.rx_chan = NULL; - chip->dma.tx_chan = NULL; + if (cpu_is_at32ap7000()) { + if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags)) + dma_release_channel(chip->dma.rx_chan); + if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) + dma_release_channel(chip->dma.tx_chan); + clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags); + clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags); + chip->dma.rx_chan = NULL; + chip->dma.tx_chan = NULL; + } err_ac97_bus: snd_card_set_dev(card, NULL); @@ -934,10 +1098,12 @@ static int atmel_ac97c_suspend(struct platform_device *pdev, pm_message_t msg) struct snd_card *card = platform_get_drvdata(pdev); struct atmel_ac97c *chip = card->private_data; - if (test_bit(DMA_RX_READY, &chip->flags)) - dw_dma_cyclic_stop(chip->dma.rx_chan); - if (test_bit(DMA_TX_READY, &chip->flags)) - dw_dma_cyclic_stop(chip->dma.tx_chan); + if (cpu_is_at32ap7000()) { + if (test_bit(DMA_RX_READY, &chip->flags)) + dw_dma_cyclic_stop(chip->dma.rx_chan); + if (test_bit(DMA_TX_READY, &chip->flags)) + dw_dma_cyclic_stop(chip->dma.tx_chan); + } clk_disable(chip->pclk); return 0; @@ -949,11 +1115,12 @@ static int atmel_ac97c_resume(struct platform_device *pdev) struct atmel_ac97c *chip = card->private_data; clk_enable(chip->pclk); - if (test_bit(DMA_RX_READY, &chip->flags)) - dw_dma_cyclic_start(chip->dma.rx_chan); - if (test_bit(DMA_TX_READY, &chip->flags)) - dw_dma_cyclic_start(chip->dma.tx_chan); - + if (cpu_is_at32ap7000()) { + if (test_bit(DMA_RX_READY, &chip->flags)) + dw_dma_cyclic_start(chip->dma.rx_chan); + if (test_bit(DMA_TX_READY, &chip->flags)) + dw_dma_cyclic_start(chip->dma.tx_chan); + } return 0; } #else @@ -978,14 +1145,16 @@ static int __devexit atmel_ac97c_remove(struct platform_device *pdev) iounmap(chip->regs); free_irq(chip->irq, chip); - if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags)) - dma_release_channel(chip->dma.rx_chan); - if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) - dma_release_channel(chip->dma.tx_chan); - clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags); - clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags); - chip->dma.rx_chan = NULL; - chip->dma.tx_chan = NULL; + if (cpu_is_at32ap7000()) { + if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags)) + dma_release_channel(chip->dma.rx_chan); + if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) + dma_release_channel(chip->dma.tx_chan); + clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags); + clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags); + chip->dma.rx_chan = NULL; + chip->dma.tx_chan = NULL; + } snd_card_set_dev(card, NULL); snd_card_free(card); -- cgit v1.2.3-18-g5258 From ec2755a93d5df044cb52558d2c778780272e0980 Mon Sep 17 00:00:00 2001 From: Sedji Gaouaou Date: Thu, 25 Feb 2010 18:59:40 +0100 Subject: ALSA: AC97: add full duplex support for atmel AT91 and AVR. This patch add full duplex support on AT91 and AVR. It was a bug: we needed to check first if there are some chips opened so we could enable both reception and sending of the data. Signed-off-by: Sedji Gaouaou Acked-by: Liam Girdwood Acked-by: Mark Brown Signed-off-by: Takashi Iwai --- sound/atmel/ac97c.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c index 3c0a6f4e358..428121a7e70 100644 --- a/sound/atmel/ac97c.c +++ b/sound/atmel/ac97c.c @@ -339,7 +339,11 @@ static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream) ac97c_writel(chip, OCA, word); /* configure sample format and size */ - word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + word = ac97c_readl(chip, CAMR); + if (chip->opened <= 1) + word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + else + word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: @@ -426,7 +430,11 @@ static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream) ac97c_writel(chip, ICA, word); /* configure sample format and size */ - word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + word = ac97c_readl(chip, CAMR); + if (chip->opened <= 1) + word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + else + word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_LE: @@ -506,7 +514,7 @@ atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd) } else { ptcr = ATMEL_PDC_TXTEN; } - camr |= AC97C_CMR_CENA; + camr |= AC97C_CMR_CENA | AC97C_CSR_ENDTX; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ @@ -551,7 +559,7 @@ atmel_ac97c_capture_trigger(struct snd_pcm_substream *substream, int cmd) } else { ptcr = ATMEL_PDC_RXTEN; } - camr |= AC97C_CMR_CENA; + camr |= AC97C_CMR_CENA | AC97C_CSR_ENDRX; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ -- cgit v1.2.3-18-g5258 From cd508fe58b07499403e806b558c7f15c90442292 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Mar 2010 10:28:46 +0100 Subject: ALSA: hda-intel - add special 'hwio' model to bypass initialization Using the 'model=hwio' option, the driver bypasses any codec initialization and the reset procedure for codecs is also bypassed. This mode is usefull to enable direct access using hwdep interface (using hdaverb or hda-analyzer tools) and retain codec setup from BIOS. Signed-off-by: Jaroslav Kysela --- Documentation/sound/alsa/HD-Audio.txt | 5 +++++ sound/pci/hda/hda_codec.c | 10 ++++++++-- sound/pci/hda/hda_intel.c | 20 ++++++++++++-------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index f4dd3bf99d1..ecacf53a865 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -196,6 +196,11 @@ generic parser regardless of the codec. Usually the codec-specific parser is much better than the generic parser (as now). Thus this option is more about the debugging purpose. +Another special meaning has 'model=hwio'. For this model, the driver +bypasses any codec initialization and the reset procedure for codecs +is also bypassed. This mode is usefull to enable direct access using +hwdep interface (using hdaverb or hda-analyzer tools) and retain +codec setup from BIOS. Speaker and Headphone Output ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 0e76ac2b2ac..cf6280bdaa1 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -609,11 +609,15 @@ int /*__devinit*/ snd_hda_bus_new(struct snd_card *card, } EXPORT_SYMBOL_HDA(snd_hda_bus_new); +#define is_hwio_config(codec) \ + (codec->modelname && !strcmp(codec->modelname, "hwio")) #ifdef CONFIG_SND_HDA_GENERIC #define is_generic_config(codec) \ - (codec->modelname && !strcmp(codec->modelname, "generic")) + ((codec->modelname && !strcmp(codec->modelname, "generic")) || \ + is_hwio_config(codec)) #else -#define is_generic_config(codec) 0 +#define is_generic_config(codec) \ + is_hwio_config(codec) #endif #ifdef MODULE @@ -1113,6 +1117,8 @@ int snd_hda_codec_configure(struct hda_codec *codec) } if (is_generic_config(codec)) { + if (is_hwio_config(codec)) + goto patched; err = snd_hda_parse_generic_codec(codec); goto patched; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 8b2915631cc..8d477613bcc 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -858,10 +858,13 @@ static void azx_power_notify(struct hda_bus *bus); #endif /* reset codec link */ -static int azx_reset(struct azx *chip) +static int azx_reset(struct azx *chip, int full_reset) { int count; + if (!full_reset) + goto __skip; + /* clear STATESTS */ azx_writeb(chip, STATESTS, STATESTS_INT_MASK); @@ -887,6 +890,7 @@ static int azx_reset(struct azx *chip) /* Brent Chartrand said to wait >= 540us for codecs to initialize */ msleep(1); + __skip: /* check to see if controller is ready */ if (!azx_readb(chip, GCTL)) { snd_printd(SFX "azx_reset: controller not ready!\n"); @@ -998,13 +1002,13 @@ static void azx_stream_stop(struct azx *chip, struct azx_dev *azx_dev) /* * reset and start the controller registers */ -static void azx_init_chip(struct azx *chip) +static void azx_init_chip(struct azx *chip, int full_reset) { if (chip->initialized) return; /* reset controller */ - azx_reset(chip); + azx_reset(chip, full_reset); /* initialize interrupts */ azx_int_clear(chip); @@ -1348,7 +1352,7 @@ static void azx_bus_reset(struct hda_bus *bus) bus->in_reset = 1; azx_stop_chip(chip); - azx_init_chip(chip); + azx_init_chip(chip, 1); #ifdef CONFIG_PM if (chip->initialized) { int i; @@ -1422,7 +1426,7 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model) * get back to the sanity state. */ azx_stop_chip(chip); - azx_init_chip(chip); + azx_init_chip(chip, 1); } } } @@ -2112,7 +2116,7 @@ static void azx_power_notify(struct hda_bus *bus) } } if (power_on) - azx_init_chip(chip); + azx_init_chip(chip, 1); else if (chip->running && power_save_controller && !bus->power_keep_link_on) azx_stop_chip(chip); @@ -2182,7 +2186,7 @@ static int azx_resume(struct pci_dev *pci) azx_init_pci(chip); if (snd_hda_codecs_inuse(chip->bus)) - azx_init_chip(chip); + azx_init_chip(chip, 1); snd_hda_resume(chip->bus); snd_power_change_state(card, SNDRV_CTL_POWER_D0); @@ -2573,7 +2577,7 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, /* initialize chip */ azx_init_pci(chip); - azx_init_chip(chip); + azx_init_chip(chip, model[dev] == NULL || strcmp(model[dev], "hwio")); /* codec detection */ if (!chip->codec_mask) { -- cgit v1.2.3-18-g5258 From 0bf0e5a6f304ac1bc93a80cdd68b4d91f3519eb5 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Mar 2010 10:33:18 +0100 Subject: ALSA: hda-intel - AD1984 thinkpad - add analog beep input control For Lenovo Thinkpad T61/X61, the analog beep input is connected to node 0x20, index 3. Move the digital beep mute/volume controls as "Digital Beep" and create analog beep controls for mentioned node. Signed-off-by: Jaroslav Kysela --- sound/pci/hda/patch_analog.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index e6d1bdff1b6..262f5b5decf 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -71,9 +71,10 @@ struct ad198x_spec { struct hda_input_mux private_imux; hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; - unsigned int jack_present :1; - unsigned int inv_jack_detect:1; /* inverted jack-detection */ - unsigned int inv_eapd:1; /* inverted EAPD implementation */ + unsigned int jack_present: 1; + unsigned int inv_jack_detect: 1;/* inverted jack-detection */ + unsigned int inv_eapd: 1; /* inverted EAPD implementation */ + unsigned int analog_beep: 1; /* analog beep input present */ #ifdef CONFIG_SND_HDA_POWER_SAVE struct hda_loopback_check loopback; @@ -165,6 +166,12 @@ static struct snd_kcontrol_new ad_beep_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new ad_beep2_mixer[] = { + HDA_CODEC_VOLUME("Digital Beep Playback Volume", 0, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_BEEP("Digital Beep Playback Switch", 0, 0, HDA_OUTPUT), + { } /* end */ +}; + #define set_beep_amp(spec, nid, idx, dir) \ ((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir)) /* mono */ #else @@ -203,7 +210,8 @@ static int ad198x_build_controls(struct hda_codec *codec) #ifdef CONFIG_SND_HDA_INPUT_BEEP if (spec->beep_amp) { struct snd_kcontrol_new *knew; - for (knew = ad_beep_mixer; knew->name; knew++) { + knew = spec->analog_beep ? ad_beep2_mixer : ad_beep_mixer; + for ( ; knew->name; knew++) { struct snd_kcontrol *kctl; kctl = snd_ctl_new1(knew, codec); if (!kctl) @@ -3482,6 +3490,8 @@ static struct snd_kcontrol_new ad1984_thinkpad_mixers[] = { HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT), HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT), HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), HDA_CODEC_VOLUME("Docking Mic Playback Volume", 0x20, 0x04, HDA_INPUT), HDA_CODEC_MUTE("Docking Mic Playback Switch", 0x20, 0x04, HDA_INPUT), HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT), @@ -3523,6 +3533,8 @@ static struct hda_verb ad1984_thinkpad_init_verbs[] = { {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* docking mic boost */ {0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Analog PC Beeper - allow firmware/ACPI beeps */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3) | 0x1a}, /* Analog mixer - docking mic; mute as default */ {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* enable EAPD bit */ @@ -3655,6 +3667,7 @@ static int patch_ad1984(struct hda_codec *codec) spec->input_mux = &ad1984_thinkpad_capture_source; spec->mixers[0] = ad1984_thinkpad_mixers; spec->init_verbs[spec->num_init_verbs++] = ad1984_thinkpad_init_verbs; + spec->analog_beep = 1; break; case AD1984_DELL_DESKTOP: spec->multiout.dig_out_nid = 0; -- cgit v1.2.3-18-g5258 From 10e77ddac0e1f3b9cea04dfe8180f8641f329703 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Mar 2010 11:04:38 +0100 Subject: ALSA: hda-intel - remove model=hwio , use probe_only=3 instead The probe_only module parameter skips the codec initialization, too. Remove the model=hwio code and use second bit in probe_only to skip the HDA codec reset procedure. Signed-off-by: Jaroslav Kysela --- Documentation/sound/alsa/HD-Audio.txt | 3 +++ sound/pci/hda/hda_codec.c | 10 ++-------- sound/pci/hda/hda_intel.c | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index ecacf53a865..232a9077522 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -597,6 +597,9 @@ probing, the proc file is available, so you can get the raw codec information before modified by the driver. Of course, the driver isn't usable with `probe_only=1`. But you can continue the configuration via hwdep sysfs file if hda-reconfig option is enabled. +Using `probe_only` mask 2 skips the reset of HDA codecs (use +`probe_only=3` as module option). The hwdep interface can be used +to determine the BIOS codec initialization. hda-verb diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index cf6280bdaa1..0e76ac2b2ac 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -609,15 +609,11 @@ int /*__devinit*/ snd_hda_bus_new(struct snd_card *card, } EXPORT_SYMBOL_HDA(snd_hda_bus_new); -#define is_hwio_config(codec) \ - (codec->modelname && !strcmp(codec->modelname, "hwio")) #ifdef CONFIG_SND_HDA_GENERIC #define is_generic_config(codec) \ - ((codec->modelname && !strcmp(codec->modelname, "generic")) || \ - is_hwio_config(codec)) + (codec->modelname && !strcmp(codec->modelname, "generic")) #else -#define is_generic_config(codec) \ - is_hwio_config(codec) +#define is_generic_config(codec) 0 #endif #ifdef MODULE @@ -1117,8 +1113,6 @@ int snd_hda_codec_configure(struct hda_codec *codec) } if (is_generic_config(codec)) { - if (is_hwio_config(codec)) - goto patched; err = snd_hda_parse_generic_codec(codec); goto patched; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 8d477613bcc..6ce673c17ff 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2577,7 +2577,7 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, /* initialize chip */ azx_init_pci(chip); - azx_init_chip(chip, model[dev] == NULL || strcmp(model[dev], "hwio")); + azx_init_chip(chip, (probe_only[dev] & 2) == 0); /* codec detection */ if (!chip->codec_mask) { @@ -2666,7 +2666,7 @@ static int __devinit azx_probe(struct pci_dev *pci, goto out_free; } #endif - if (!probe_only[dev]) { + if ((probe_only[dev] & 1) == 0) { err = azx_codec_configure(chip); if (err < 0) goto out_free; -- cgit v1.2.3-18-g5258 From 38f57968f45333585293e88ea12ca577811fb9a7 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Mar 2010 11:14:55 +0100 Subject: ALSA: hda-intel - remove model=hwio from documentation Signed-off-by: Jaroslav Kysela --- Documentation/sound/alsa/HD-Audio.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index 232a9077522..bdb220c2831 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -196,12 +196,6 @@ generic parser regardless of the codec. Usually the codec-specific parser is much better than the generic parser (as now). Thus this option is more about the debugging purpose. -Another special meaning has 'model=hwio'. For this model, the driver -bypasses any codec initialization and the reset procedure for codecs -is also bypassed. This mode is usefull to enable direct access using -hwdep interface (using hdaverb or hda-analyzer tools) and retain -codec setup from BIOS. - Speaker and Headphone Output ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ One of the most frequent (and obvious) bugs with HD-audio is the -- cgit v1.2.3-18-g5258 From 079e683ebd882655b6d6e0b17989e51bae2d77d6 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 26 Mar 2010 11:16:59 +0100 Subject: ALSA: hda-intel - probe_only module option is int type now Signed-off-by: Jaroslav Kysela --- sound/pci/hda/hda_intel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 6ce673c17ff..a33c1c50b31 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -84,7 +84,7 @@ module_param_array(bdl_pos_adj, int, NULL, 0644); MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); module_param_array(probe_mask, int, NULL, 0444); MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); -module_param_array(probe_only, bool, NULL, 0444); +module_param_array(probe_only, int, NULL, 0444); MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization."); module_param(single_cmd, bool, 0444); MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs " -- cgit v1.2.3-18-g5258 From 4a942b457ee239eab04db8dd4d4c12ef6dc4a152 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 25 Mar 2010 19:15:51 +0900 Subject: ASoC: fsi: Add FIFO size calculate Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/sh/fsi.c | 70 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 7c295df6e85..ae888651a77 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -46,8 +46,9 @@ #define MUTE 0x020C #define CLK_RST 0x0210 #define SOFT_RST 0x0214 +#define FIFO_SZ 0x0218 #define MREG_START INT_ST -#define MREG_END SOFT_RST +#define MREG_END FIFO_SZ /* DO_FMT */ /* DI_FMT */ @@ -85,6 +86,11 @@ #define IR (1 << 4) /* Interrupt Reset */ #define FSISR (1 << 0) /* Software Reset */ +/* FIFO_SZ */ +#define OUT_SZ_MASK 0x7 +#define BO_SZ_SHIFT 8 +#define AO_SZ_SHIFT 0 + #define FSI_RATES SNDRV_PCM_RATE_8000_96000 #define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) @@ -384,9 +390,42 @@ static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) fsi_master_mask_set(master, CLK_RST, val, 0); } -static void fsi_fifo_init(struct fsi_priv *fsi, int is_play) +static void fsi_fifo_init(struct fsi_priv *fsi, + int is_play, + struct snd_soc_dai *dai) { - u32 ctrl; + struct fsi_master *master = fsi_get_master(fsi); + u32 ctrl, shift, i; + + /* get on-chip RAM capacity */ + shift = fsi_master_read(master, FIFO_SZ); + shift >>= fsi_is_port_a(fsi) ? AO_SZ_SHIFT : BO_SZ_SHIFT; + shift &= OUT_SZ_MASK; + fsi->fifo_max = 256 << shift; + dev_dbg(dai->dev, "fifo = %d words\n", fsi->fifo_max); + + /* + * The maximum number of sample data varies depending + * on the number of channels selected for the format. + * + * FIFOs are used in 4-channel units in 3-channel mode + * and in 8-channel units in 5- to 7-channel mode + * meaning that more FIFOs than the required size of DPRAM + * are used. + * + * ex) if 256 words of DP-RAM is connected + * 1 channel: 256 (256 x 1 = 256) + * 2 channels: 128 (128 x 2 = 256) + * 3 channels: 64 ( 64 x 3 = 192) + * 4 channels: 64 ( 64 x 4 = 256) + * 5 channels: 32 ( 32 x 5 = 160) + * 6 channels: 32 ( 32 x 6 = 192) + * 7 channels: 32 ( 32 x 7 = 224) + * 8 channels: 32 ( 32 x 8 = 256) + */ + for (i = 1; i < fsi->chan; i <<= 1) + fsi->fifo_max >>= 1; + dev_dbg(dai->dev, "%d channel %d store\n", fsi->chan, fsi->fifo_max); ctrl = is_play ? DOFF_CTL : DIFF_CTL; @@ -689,29 +728,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, dev_err(dai->dev, "unknown format.\n"); return -EINVAL; } - - switch (fsi->chan) { - case 1: - fsi->fifo_max = 256; - break; - case 2: - fsi->fifo_max = 128; - break; - case 3: - case 4: - fsi->fifo_max = 64; - break; - case 5: - case 6: - case 7: - case 8: - fsi->fifo_max = 32; - break; - default: - dev_err(dai->dev, "channel size error.\n"); - return -EINVAL; - } - fsi_reg_write(fsi, reg, data); /* @@ -725,7 +741,7 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, fsi_irq_clear_status(fsi); /* fifo init */ - fsi_fifo_init(fsi, is_play); + fsi_fifo_init(fsi, is_play, dai); return ret; } -- cgit v1.2.3-18-g5258 From cc780d380a004b58c139570b037d0e3b897bb2be Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 25 Mar 2010 19:15:53 +0900 Subject: ASoC: fsi: Add FSI2 device support ARM-SHMOBILE series have FIFO-buffered serial interface 2 (FSI2) device which is advanced version of FSI. This patch add simple support for it. Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/sh/Kconfig | 3 +-- sound/soc/sh/fsi.c | 56 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index f07f6d8b93e..a1d14bc3c76 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -1,5 +1,5 @@ menu "SoC Audio support for SuperH" - depends on SUPERH + depends on SUPERH || ARCH_SHMOBILE config SND_SOC_PCM_SH7760 tristate "SoC Audio support for Renesas SH7760" @@ -22,7 +22,6 @@ config SND_SOC_SH4_SSI config SND_SOC_SH4_FSI tristate "SH4 FSI support" - depends on CPU_SUBTYPE_SH7724 help This option enables FSI sound support diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index ae888651a77..f14bbb0410c 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -40,6 +40,10 @@ #define MUTE_ST 0x0028 #define REG_END MUTE_ST + +#define CPU_INT_ST 0x01F4 +#define CPU_IEMSK 0x01F8 +#define CPU_IMSK 0x01FC #define INT_ST 0x0200 #define IEMSK 0x0204 #define IMSK 0x0208 @@ -47,7 +51,7 @@ #define CLK_RST 0x0210 #define SOFT_RST 0x0214 #define FIFO_SZ 0x0218 -#define MREG_START INT_ST +#define MREG_START CPU_INT_ST #define MREG_END FIFO_SZ /* DO_FMT */ @@ -116,11 +120,18 @@ struct fsi_priv { int periods; }; +struct fsi_regs { + u32 int_st; + u32 iemsk; + u32 imsk; +}; + struct fsi_master { void __iomem *base; int irq; struct fsi_priv fsia; struct fsi_priv fsib; + struct fsi_regs *regs; struct sh_fsi_platform_info *info; spinlock_t lock; }; @@ -337,8 +348,8 @@ static void fsi_irq_enable(struct fsi_priv *fsi, int is_play) u32 data = fsi_port_ab_io_bit(fsi, is_play); struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(master, IMSK, data, data); - fsi_master_mask_set(master, IEMSK, data, data); + fsi_master_mask_set(master, master->regs->imsk, data, data); + fsi_master_mask_set(master, master->regs->iemsk, data, data); } static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) @@ -346,18 +357,18 @@ static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) u32 data = fsi_port_ab_io_bit(fsi, is_play); struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(master, IMSK, data, 0); - fsi_master_mask_set(master, IEMSK, data, 0); + fsi_master_mask_set(master, master->regs->imsk, data, 0); + fsi_master_mask_set(master, master->regs->iemsk, data, 0); } static u32 fsi_irq_get_status(struct fsi_master *master) { - return fsi_master_read(master, INT_ST); + return fsi_master_read(master, master->regs->int_st); } static void fsi_irq_clear_all_status(struct fsi_master *master) { - fsi_master_write(master, INT_ST, 0x0000000); + fsi_master_write(master, master->regs->int_st, 0x0000000); } static void fsi_irq_clear_status(struct fsi_priv *fsi) @@ -369,7 +380,7 @@ static void fsi_irq_clear_status(struct fsi_priv *fsi) data |= fsi_port_ab_io_bit(fsi, 1); /* clear interrupt factor */ - fsi_master_mask_set(master, INT_ST, data, 0); + fsi_master_mask_set(master, master->regs->int_st, data, 0); } /************************************************************************ @@ -953,6 +964,7 @@ EXPORT_SYMBOL_GPL(fsi_soc_platform); static int fsi_probe(struct platform_device *pdev) { struct fsi_master *master; + const struct platform_device_id *id_entry; struct resource *res; unsigned int irq; int ret; @@ -962,6 +974,12 @@ static int fsi_probe(struct platform_device *pdev) return -ENODEV; } + id_entry = pdev->id_entry; + if (!id_entry) { + dev_err(&pdev->dev, "unknown fsi device\n"); + return -ENODEV; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || (int)irq <= 0) { @@ -990,6 +1008,7 @@ static int fsi_probe(struct platform_device *pdev) master->fsia.master = master; master->fsib.base = master->base + 0x40; master->fsib.master = master; + master->regs = (struct fsi_regs *)id_entry->driver_data; spin_lock_init(&master->lock); pm_runtime_enable(&pdev->dev); @@ -1002,7 +1021,8 @@ static int fsi_probe(struct platform_device *pdev) fsi_soft_all_reset(master); - ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master); + ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, + id_entry->name, master); if (ret) { dev_err(&pdev->dev, "irq request err\n"); goto exit_iounmap; @@ -1069,6 +1089,23 @@ static struct dev_pm_ops fsi_pm_ops = { .runtime_resume = fsi_runtime_nop, }; +static struct fsi_regs fsi_regs = { + .int_st = INT_ST, + .iemsk = IEMSK, + .imsk = IMSK, +}; + +static struct fsi_regs fsi2_regs = { + .int_st = CPU_INT_ST, + .iemsk = CPU_IEMSK, + .imsk = CPU_IMSK, +}; + +static struct platform_device_id fsi_id_table[] = { + { "sh_fsi", (kernel_ulong_t)&fsi_regs }, + { "sh_fsi2", (kernel_ulong_t)&fsi2_regs }, +}; + static struct platform_driver fsi_driver = { .driver = { .name = "sh_fsi", @@ -1076,6 +1113,7 @@ static struct platform_driver fsi_driver = { }, .probe = fsi_probe, .remove = fsi_remove, + .id_table = fsi_id_table, }; static int __init fsi_mobile_init(void) -- cgit v1.2.3-18-g5258 From 36db0456582484aa78809376f1e41ec2873fc9d5 Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 29 Mar 2010 16:02:50 +1100 Subject: ALSA: usb - use of kmalloc/kfree requires the include of slab.h Signed-off-by: Stephen Rothwell Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 1 + sound/usb/format.c | 1 + sound/usb/helper.c | 1 + sound/usb/mixer_quirks.c | 1 + sound/usb/quirks.c | 1 + 5 files changed, 5 insertions(+) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 7b84b61a078..ef07a6d0dd5 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include diff --git a/sound/usb/format.c b/sound/usb/format.c index fcadedd9454..b87cf87c4e7 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include diff --git a/sound/usb/helper.c b/sound/usb/helper.c index ba7dba4d5cf..d48d6f8f6ac 100644 --- a/sound/usb/helper.c +++ b/sound/usb/helper.c @@ -16,6 +16,7 @@ */ #include +#include #include #include "usbaudio.h" diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 56b665938b1..e7df1e5e3f2 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -26,6 +26,7 @@ */ #include +#include #include #include diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d4ced64e465..136e5b4cf6d 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -15,6 +15,7 @@ */ #include +#include #include #include -- cgit v1.2.3-18-g5258 From 1b132ea03e169ce5eca75b00224a78a6782740e8 Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 29 Mar 2010 15:32:18 +1100 Subject: ASoC: update for removeal of slab.h from percpu.h Signed-off-by: Stephen Rothwell Signed-off-by: Takashi Iwai --- sound/soc/codecs/ad193x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index d034464e26f..e1082067075 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3-18-g5258 From 467126460818e287aa92c73232c38453ec2ab013 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 29 Mar 2010 09:19:38 +0200 Subject: ALSA: hda - Report errors when invalid values are passed to snd_hda_amp_*() The values should be in 8 bits. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index d72bcff8d27..d1424e7b9f3 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1461,6 +1461,8 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx)); if (!info) return 0; + if (snd_BUG_ON(mask & ~0xff)) + mask &= 0xff; val &= mask; val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask; if (info->vol[ch] == val) @@ -1486,6 +1488,9 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, int direction, int idx, int mask, int val) { int ch, ret = 0; + + if (snd_BUG_ON(mask & ~0xff)) + mask &= 0xff; for (ch = 0; ch < 2; ch++) ret |= snd_hda_codec_amp_update(codec, nid, ch, direction, idx, mask, val); -- cgit v1.2.3-18-g5258 From 68b40cc40af45dd969c50fdbac6ab5b99c91255b Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 29 Mar 2010 17:55:51 +1100 Subject: ASoC: TWL6040: use of kzalloc/kfree requires the include of slab.h Signed-off-by: Stephen Rothwell Signed-off-by: Takashi Iwai --- sound/soc/codecs/twl6040.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 0e4dce7de05..108c51a513c 100755 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3-18-g5258 From d01e14a6b96a3384b6806fac1a769ca3be0022bb Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 29 Mar 2010 09:31:57 +0200 Subject: ASoC: Fix file permission of soc/codecs/twl6040.c Signed-off-by: Takashi Iwai --- sound/soc/codecs/twl6040.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 sound/soc/codecs/twl6040.c diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c old mode 100755 new mode 100644 -- cgit v1.2.3-18-g5258 From 9966ddafe1b4f2a9a09ea748b42ef69417b23ff1 Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 29 Mar 2010 19:01:48 +1100 Subject: ALSA: usb pcm: use of kmalloc requires the include of slab.h Signed-off-by: Stephen Rothwell Signed-off-by: Takashi Iwai --- sound/usb/pcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 630e2203b34..2bf0d77d176 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include -- cgit v1.2.3-18-g5258 From 923a00427a141a6d1c83f1b151221a2458b2de8d Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 30 Mar 2010 02:52:29 +0900 Subject: ASoC: update gfp/slab.h includes Implicit slab.h inclusion via percpu.h is about to go away. Make sure gfp.h or slab.h is included as necessary. Signed-off-by: Tejun Heo Signed-off-by: Takashi Iwai --- sound/soc/codecs/cq93vc.c | 1 + sound/soc/davinci/davinci-vcif.c | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c index 51329744136..8f19b931064 100644 --- a/sound/soc/codecs/cq93vc.c +++ b/sound/soc/codecs/cq93vc.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c index 54b91e1768c..9aa980d3823 100644 --- a/sound/soc/davinci/davinci-vcif.c +++ b/sound/soc/davinci/davinci-vcif.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3-18-g5258 From 7b7b9042263f5cafb6ce85b3764375a8de7e22da Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 30 Mar 2010 02:52:29 +0900 Subject: ALSA: usb - update gfp/slab.h includes Implicit slab.h inclusion via percpu.h is about to go away. Make sure gfp.h or slab.h is included as necessary. Signed-off-by: Tejun Heo Signed-off-by: Takashi Iwai --- sound/usb/caiaq/input.c | 1 + sound/usb/urb.c | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 27ed0bc651a..8bbfbfd4c65 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -16,6 +16,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #include diff --git a/sound/usb/urb.c b/sound/usb/urb.c index e2b7c478424..5570a2ba573 100644 --- a/sound/usb/urb.c +++ b/sound/usb/urb.c @@ -15,6 +15,7 @@ * */ +#include #include #include #include -- cgit v1.2.3-18-g5258 From 9dd7b79a86c53a097463ee1582c0bc6c4b83b770 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Mon, 29 Mar 2010 11:16:00 +0800 Subject: ASoC: ad193x: move codec register/unregister to bus probe/remove The way i've factored out the bus probe and removal functions so that there's no code in the individual I2C and SPI functions means that the register() and unregister() functions could just be squashed into the bus_probe() and bus_remove() functions. Signed-off-by: Barry Song <21cnbao@gmail.com> Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ad193x.c | 102 ++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index e1082067075..4bfd66bc262 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -35,8 +35,6 @@ static const u8 ad193x_reg[AD193X_NUM_REGS] = { static struct snd_soc_codec *ad193x_codec; struct snd_soc_codec_device soc_codec_dev_ad193x; -static int ad193x_register(struct ad193x_priv *ad193x, int bus_type); -static void ad193x_unregister(struct ad193x_priv *ad193x); /* * AD193X volume/mute/de-emphasis etc. controls @@ -290,69 +288,23 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) { struct snd_soc_codec *codec; struct ad193x_priv *ad193x; + int ret; + + if (ad193x_codec) { + dev_err(dev, "Another ad193x is registered\n"); + return -EINVAL; + } ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL); if (ad193x == NULL) return -ENOMEM; - codec = &ad193x->codec; - codec->control_data = ctrl_data; - codec->dev = dev; - dev_set_drvdata(dev, ad193x); - return ad193x_register(ad193x, bus_type); -} - -static int ad193x_bus_remove(struct device *dev) -{ - struct ad193x_priv *ad193x = dev_get_drvdata(dev); - - ad193x_unregister(ad193x); - return 0; -} - -static struct snd_soc_dai_ops ad193x_dai_ops = { - .hw_params = ad193x_hw_params, - .digital_mute = ad193x_mute, - .set_tdm_slot = ad193x_set_tdm_slot, - .set_fmt = ad193x_set_dai_fmt, -}; - -/* codec DAI instance */ -struct snd_soc_dai ad193x_dai = { - .name = "AD193X", - .playback = { - .stream_name = "Playback", - .channels_min = 2, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .ops = &ad193x_dai_ops, -}; -EXPORT_SYMBOL_GPL(ad193x_dai); - -static int ad193x_register(struct ad193x_priv *ad193x, int bus_type) -{ - int ret; - struct snd_soc_codec *codec = &ad193x->codec; - - if (ad193x_codec) { - dev_err(codec->dev, "Another ad193x is registered\n"); - return -EINVAL; - } - + codec = &ad193x->codec; mutex_init(&codec->mutex); + codec->control_data = ctrl_data; + codec->dev = dev; codec->private_data = ad193x; codec->reg_cache = ad193x->reg_cache; codec->reg_cache_size = AD193X_NUM_REGS; @@ -413,14 +365,48 @@ static int ad193x_register(struct ad193x_priv *ad193x, int bus_type) return 0; } -static void ad193x_unregister(struct ad193x_priv *ad193x) +static int ad193x_bus_remove(struct device *dev) { + struct ad193x_priv *ad193x = dev_get_drvdata(dev); + snd_soc_unregister_dai(&ad193x_dai); snd_soc_unregister_codec(&ad193x->codec); kfree(ad193x); ad193x_codec = NULL; + + return 0; } +static struct snd_soc_dai_ops ad193x_dai_ops = { + .hw_params = ad193x_hw_params, + .digital_mute = ad193x_mute, + .set_tdm_slot = ad193x_set_tdm_slot, + .set_fmt = ad193x_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad193x_dai = { + .name = "AD193X", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad193x_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad193x_dai); + static int ad193x_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); -- cgit v1.2.3-18-g5258 From ad35879aa192e783db2d41c196f48f3fc059ce22 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 30 Mar 2010 18:00:59 +0200 Subject: ALSA: hda - Add mute LED support for HP laptop with ALC269 Some HP laptops have a mute LED that is controlled over the unused MIC2 VREF pin. Implement the LED updater like patch_sigmatel.c for this model. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 1694f60631a..9a361c2c733 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -3467,6 +3467,10 @@ static int alc_init(struct hda_codec *codec) if (spec->init_hook) spec->init_hook(codec); +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (codec->patch_ops.check_power_status) + codec->patch_ops.check_power_status(codec, 0x01); +#endif return 0; } @@ -3827,6 +3831,10 @@ static int alc_resume(struct hda_codec *codec) codec->patch_ops.init(codec); snd_hda_codec_resume_amp(codec); snd_hda_codec_resume_cache(codec); +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (codec->patch_ops.check_power_status) + codec->patch_ops.check_power_status(codec, 0x01); +#endif return 0; } #endif @@ -13983,6 +13991,34 @@ static struct hda_pcm_stream alc269_44k_pcm_analog_capture = { /* NID is set in alc_build_pcms */ }; +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int alc269_mic2_for_mute_led(struct hda_codec *codec) +{ + switch (codec->subsystem_id) { + case 0x103c1586: + return 1; + } + return 0; +} + +static int alc269_mic2_mute_check_ps(struct hda_codec *codec, hda_nid_t nid) +{ + /* update mute-LED according to the speaker mute state */ + if (nid == 0x01 || nid == 0x14) { + int pinval; + if (snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0) & + HDA_AMP_MUTE) + pinval = 0x24; + else + pinval = 0x20; + /* mic2 vref pin is used for mute LED control */ + snd_hda_codec_write(codec, 0x19, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pinval); + } + return alc_check_power_status(codec, nid); +} +#endif /* CONFIG_SND_HDA_POWER_SAVE */ + /* * BIOS auto configuration */ @@ -14330,6 +14366,8 @@ static int patch_alc269(struct hda_codec *codec) #ifdef CONFIG_SND_HDA_POWER_SAVE if (!spec->loopback.amplist) spec->loopback.amplist = alc269_loopbacks; + if (alc269_mic2_for_mute_led(codec)) + codec->patch_ops.check_power_status = alc269_mic2_mute_check_ps; #endif return 0; -- cgit v1.2.3-18-g5258 From a68d5a5419d2a15161488b20f740115068e7b666 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 30 Mar 2010 18:03:44 +0200 Subject: ALSA: hda - introduce snd_hda_codec_update_cache() Add a new helper, snd_hda_codec_update_cache(), for reducing the unneeded verbs. This function checks the cached value and skips if it's identical with the given one. Otherwise it works like snd_hda_codec_write_cache(). The alc269 code uses this function as an example. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 68 +++++++++++++++++++++++++++++++++++-------- sound/pci/hda/hda_codec.h | 3 ++ sound/pci/hda/patch_realtek.c | 5 ++-- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index d1424e7b9f3..a3d638c8c1f 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1209,8 +1209,7 @@ static void free_hda_cache(struct hda_cache_rec *cache) } /* query the hash. allocate an entry if not found. */ -static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache, - u32 key) +static struct hda_cache_head *get_hash(struct hda_cache_rec *cache, u32 key) { u16 idx = key % (u16)ARRAY_SIZE(cache->hash); u16 cur = cache->hash[idx]; @@ -1222,17 +1221,27 @@ static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache, return info; cur = info->next; } + return NULL; +} - /* add a new hash entry */ - info = snd_array_new(&cache->buf); - if (!info) - return NULL; - cur = snd_array_index(&cache->buf, info); - info->key = key; - info->val = 0; - info->next = cache->hash[idx]; - cache->hash[idx] = cur; - +/* query the hash. allocate an entry if not found. */ +static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache, + u32 key) +{ + struct hda_cache_head *info = get_hash(cache, key); + if (!info) { + u16 idx, cur; + /* add a new hash entry */ + info = snd_array_new(&cache->buf); + if (!info) + return NULL; + cur = snd_array_index(&cache->buf, info); + info->key = key; + info->val = 0; + idx = key % (u16)ARRAY_SIZE(cache->hash); + info->next = cache->hash[idx]; + cache->hash[idx] = cur; + } return info; } @@ -2721,6 +2730,41 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, } EXPORT_SYMBOL_HDA(snd_hda_codec_write_cache); +/** + * snd_hda_codec_update_cache - check cache and write the cmd only when needed + * @codec: the HDA codec + * @nid: NID to send the command + * @direct: direct flag + * @verb: the verb to send + * @parm: the parameter for the verb + * + * This function works like snd_hda_codec_write_cache(), but it doesn't send + * command if the parameter is already identical with the cached value. + * If not, it sends the command and refreshes the cache. + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid, + int direct, unsigned int verb, unsigned int parm) +{ + struct hda_cache_head *c; + u32 key; + + /* parm may contain the verb stuff for get/set amp */ + verb = verb | (parm >> 8); + parm &= 0xff; + key = build_cmd_cache_key(nid, verb); + mutex_lock(&codec->bus->cmd_mutex); + c = get_hash(&codec->cmd_cache, key); + if (c && c->val == parm) { + mutex_unlock(&codec->bus->cmd_mutex); + return 0; + } + mutex_unlock(&codec->bus->cmd_mutex); + return snd_hda_codec_write_cache(codec, nid, direct, verb, parm); +} +EXPORT_SYMBOL_HDA(snd_hda_codec_update_cache); + /** * snd_hda_codec_resume_cache - Resume the all commands from the cache * @codec: HD-audio codec diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index b75da47571e..49e939e7e5c 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -885,9 +885,12 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, int direct, unsigned int verb, unsigned int parm); void snd_hda_sequence_write_cache(struct hda_codec *codec, const struct hda_verb *seq); +int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid, + int direct, unsigned int verb, unsigned int parm); void snd_hda_codec_resume_cache(struct hda_codec *codec); #else #define snd_hda_codec_write_cache snd_hda_codec_write +#define snd_hda_codec_update_cache snd_hda_codec_write #define snd_hda_sequence_write_cache snd_hda_sequence_write #endif diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 9a361c2c733..8ae48061e8c 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -14012,8 +14012,9 @@ static int alc269_mic2_mute_check_ps(struct hda_codec *codec, hda_nid_t nid) else pinval = 0x20; /* mic2 vref pin is used for mute LED control */ - snd_hda_codec_write(codec, 0x19, 0, - AC_VERB_SET_PIN_WIDGET_CONTROL, pinval); + snd_hda_codec_update_cache(codec, 0x19, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pinval); } return alc_check_power_status(codec, nid); } -- cgit v1.2.3-18-g5258 From 8c0d8fa26ba5ec1f69e88044c671dab58c51e0e1 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 30 Mar 2010 02:52:40 +0900 Subject: mfd: update gfp/slab.h includes Implicit slab.h inclusion via percpu.h is about to go away. Make sure gfp.h or slab.h is included as necessary. Signed-off-by: Tejun Heo Acked-by: Samuel Ortiz Signed-off-by: Mark Brown --- drivers/mfd/davinci_voicecodec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c index 9886aa8de25..3e75f02e477 100644 --- a/drivers/mfd/davinci_voicecodec.c +++ b/drivers/mfd/davinci_voicecodec.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3-18-g5258 From 819ef70b135ba66cd1659c913255686bf931e3d4 Mon Sep 17 00:00:00 2001 From: Risto Suominen Date: Sun, 4 Apr 2010 07:59:30 +0300 Subject: ALSA: powermac - Reverse HP detection on G4 DA Reverse headphone detection bit on PowerMac G4 Digital Audio (Tumbler). Signed-off-by: Risto Suominen Signed-off-by: Takashi Iwai --- sound/ppc/tumbler.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 789f44f4ac7..3b7471c344b 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,8 @@ #define DBG(fmt...) #endif +#define IS_G4DA (machine_is_compatible("PowerMac3,4")) + /* i2c address for tumbler */ #define TAS_I2C_ADDR 0x34 @@ -1134,7 +1137,7 @@ static long tumbler_find_device(const char *device, const char *platform, gp->inactive_val = (*base) ? 0x4 : 0x5; } else { const u32 *prop = NULL; - gp->active_state = 0; + gp->active_state = IS_G4DA && !strcmp(device, "keywest-gpio15"); gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and -- cgit v1.2.3-18-g5258 From b6d7335001f331f2d295ff15d67e385615ceff81 Mon Sep 17 00:00:00 2001 From: Risto Suominen Date: Sun, 4 Apr 2010 08:00:00 +0300 Subject: ALSA: powermac - Lineout detection on G4 DA Lineout (Pro Speaker) detection on PowerMac G4 Digital Audio (Tumbler). Signed-off-by: Risto Suominen Signed-off-by: Takashi Iwai --- sound/ppc/tumbler.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 3b7471c344b..696280c3c05 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -1137,7 +1137,8 @@ static long tumbler_find_device(const char *device, const char *platform, gp->inactive_val = (*base) ? 0x4 : 0x5; } else { const u32 *prop = NULL; - gp->active_state = IS_G4DA && !strcmp(device, "keywest-gpio15"); + gp->active_state = IS_G4DA + && !strncmp(device, "keywest-gpio1", 13); gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and @@ -1315,6 +1316,9 @@ static int __devinit tumbler_init(struct snd_pmac *chip) if (irq <= NO_IRQ) irq = tumbler_find_device("line-output-detect", NULL, &mix->line_detect, 1); + if (IS_G4DA && irq <= NO_IRQ) + irq = tumbler_find_device("keywest-gpio16", + NULL, &mix->line_detect, 1); mix->lineout_irq = irq; tumbler_reset_audio(chip); -- cgit v1.2.3-18-g5258 From f1b1f75e251049281cfe3d3b82d403fc2dc1e235 Mon Sep 17 00:00:00 2001 From: Risto Suominen Date: Sun, 4 Apr 2010 08:00:29 +0300 Subject: ALSA: powermac - Add debug log Add some debug log in tumbler.c. Signed-off-by: Risto Suominen Signed-off-by: Takashi Iwai --- sound/ppc/tumbler.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 696280c3c05..0b69e96f0d1 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -246,6 +246,7 @@ static int tumbler_set_master_volume(struct pmac_tumbler *mix) snd_printk(KERN_ERR "failed to set volume \n"); return -EINVAL; } + DBG("(I) succeeded to set volume (%u, %u)\n", left_vol, right_vol); return 0; } @@ -356,6 +357,7 @@ static int tumbler_set_drc(struct pmac_tumbler *mix) snd_printk(KERN_ERR "failed to set DRC\n"); return -EINVAL; } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); return 0; } @@ -392,6 +394,7 @@ static int snapper_set_drc(struct pmac_tumbler *mix) snd_printk(KERN_ERR "failed to set DRC\n"); return -EINVAL; } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); return 0; } -- cgit v1.2.3-18-g5258 From c9fbf7e070bbf9cc7adc1420df87706c62cb04ed Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 26 Mar 2010 16:49:15 +0000 Subject: mfd: Add WM8994 interrupt controller support The WM8994 has an interrupt controller which supports interrupts for both CODEC and GPIO portions of the chip. Support this using genirq, while allowing for systems that do not have an interrupt hooked up. Wrapper functions are provided for the IRQ request and free to simplify the code in consumer drivers when handling cases where IRQs are not set up. Signed-off-by: Mark Brown Acked-by: Samuel Ortiz --- drivers/mfd/Kconfig | 4 +- drivers/mfd/Makefile | 2 +- drivers/mfd/wm8994-core.c | 43 +++++- drivers/mfd/wm8994-irq.c | 310 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/wm8994/core.h | 53 ++++++- include/linux/mfd/wm8994/pdata.h | 1 + 6 files changed, 406 insertions(+), 7 deletions(-) create mode 100644 drivers/mfd/wm8994-irq.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index b2073e0266f..de3e74cde51 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -301,9 +301,9 @@ config MFD_WM8350_I2C selected to enable support for the functionality of the chip. config MFD_WM8994 - tristate "Support Wolfson Microelectronics WM8994" + bool "Support Wolfson Microelectronics WM8994" select MFD_CORE - depends on I2C + depends on I2C=y && GENERIC_HARDIRQS help The WM8994 is a highly integrated hi-fi CODEC designed for smartphone applicatiosn. As well as audio functionality it diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4fbf8f89a49..87935f967aa 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -26,7 +26,7 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o wm8350-objs += wm8350-irq.o obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o -obj-$(CONFIG_MFD_WM8994) += wm8994-core.o +obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 844e1c1b7d9..39cde82afb4 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -172,9 +172,34 @@ static struct mfd_cell wm8994_regulator_devs[] = { { .name = "wm8994-ldo", .id = 2 }, }; +static struct resource wm8994_codec_resources[] = { + { + .start = WM8994_IRQ_TEMP_SHUT, + .end = WM8994_IRQ_TEMP_WARN, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm8994_gpio_resources[] = { + { + .start = WM8994_IRQ_GPIO(1), + .end = WM8994_IRQ_GPIO(11), + .flags = IORESOURCE_IRQ, + }, +}; + static struct mfd_cell wm8994_devs[] = { - { .name = "wm8994-codec" }, - { .name = "wm8994-gpio" }, + { + .name = "wm8994-codec", + .num_resources = ARRAY_SIZE(wm8994_codec_resources), + .resources = wm8994_codec_resources, + }, + + { + .name = "wm8994-gpio", + .num_resources = ARRAY_SIZE(wm8994_gpio_resources), + .resources = wm8994_gpio_resources, + }, }; /* @@ -235,6 +260,11 @@ static int wm8994_device_resume(struct device *dev) return ret; } + ret = wm8994_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK, + WM8994_NUM_IRQ_REGS * 2, &wm8994->irq_masks_cur); + if (ret < 0) + dev_err(dev, "Failed to restore interrupt masks: %d\n", ret); + ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, &wm8994->ldo_regs); if (ret < 0) @@ -347,6 +377,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) if (pdata) { + wm8994->irq_base = pdata->irq_base; wm8994->gpio_base = pdata->gpio_base; /* GPIO configuration is only applied if it's non-zero */ @@ -374,16 +405,20 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) WM8994_LDO1_DISCH, 0); } + wm8994_irq_init(wm8994); + ret = mfd_add_devices(wm8994->dev, -1, wm8994_devs, ARRAY_SIZE(wm8994_devs), NULL, 0); if (ret != 0) { dev_err(wm8994->dev, "Failed to add children: %d\n", ret); - goto err_enable; + goto err_irq; } return 0; +err_irq: + wm8994_irq_exit(wm8994); err_enable: regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); @@ -400,6 +435,7 @@ err: static void wm8994_device_exit(struct wm8994 *wm8994) { mfd_remove_devices(wm8994->dev); + wm8994_irq_exit(wm8994); regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); @@ -468,6 +504,7 @@ static int wm8994_i2c_probe(struct i2c_client *i2c, wm8994->control_data = i2c; wm8994->read_dev = wm8994_i2c_read_device; wm8994->write_dev = wm8994_i2c_write_device; + wm8994->irq = i2c->irq; return wm8994_device_init(wm8994, id->driver_data, i2c->irq); } diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c new file mode 100644 index 00000000000..8400eb1ee5d --- /dev/null +++ b/drivers/mfd/wm8994-irq.c @@ -0,0 +1,310 @@ +/* + * wm8994-irq.c -- Interrupt controller support for Wolfson WM8994 + * + * Copyright 2010 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include + +struct wm8994_irq_data { + int reg; + int mask; +}; + +static struct wm8994_irq_data wm8994_irqs[] = { + [WM8994_IRQ_TEMP_SHUT] = { + .reg = 2, + .mask = WM8994_TEMP_SHUT_EINT, + }, + [WM8994_IRQ_MIC1_DET] = { + .reg = 2, + .mask = WM8994_MIC1_DET_EINT, + }, + [WM8994_IRQ_MIC1_SHRT] = { + .reg = 2, + .mask = WM8994_MIC1_SHRT_EINT, + }, + [WM8994_IRQ_MIC2_DET] = { + .reg = 2, + .mask = WM8994_MIC2_DET_EINT, + }, + [WM8994_IRQ_MIC2_SHRT] = { + .reg = 2, + .mask = WM8994_MIC2_SHRT_EINT, + }, + [WM8994_IRQ_FLL1_LOCK] = { + .reg = 2, + .mask = WM8994_FLL1_LOCK_EINT, + }, + [WM8994_IRQ_FLL2_LOCK] = { + .reg = 2, + .mask = WM8994_FLL2_LOCK_EINT, + }, + [WM8994_IRQ_SRC1_LOCK] = { + .reg = 2, + .mask = WM8994_SRC1_LOCK_EINT, + }, + [WM8994_IRQ_SRC2_LOCK] = { + .reg = 2, + .mask = WM8994_SRC2_LOCK_EINT, + }, + [WM8994_IRQ_AIF1DRC1_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF1DRC1_SIG_DET, + }, + [WM8994_IRQ_AIF1DRC2_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF1DRC2_SIG_DET_EINT, + }, + [WM8994_IRQ_AIF2DRC_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF2DRC_SIG_DET_EINT, + }, + [WM8994_IRQ_FIFOS_ERR] = { + .reg = 2, + .mask = WM8994_FIFOS_ERR_EINT, + }, + [WM8994_IRQ_WSEQ_DONE] = { + .reg = 2, + .mask = WM8994_WSEQ_DONE_EINT, + }, + [WM8994_IRQ_DCS_DONE] = { + .reg = 2, + .mask = WM8994_DCS_DONE_EINT, + }, + [WM8994_IRQ_TEMP_WARN] = { + .reg = 2, + .mask = WM8994_TEMP_WARN_EINT, + }, + [WM8994_IRQ_GPIO(1)] = { + .reg = 1, + .mask = WM8994_GP1_EINT, + }, + [WM8994_IRQ_GPIO(2)] = { + .reg = 1, + .mask = WM8994_GP2_EINT, + }, + [WM8994_IRQ_GPIO(3)] = { + .reg = 1, + .mask = WM8994_GP3_EINT, + }, + [WM8994_IRQ_GPIO(4)] = { + .reg = 1, + .mask = WM8994_GP4_EINT, + }, + [WM8994_IRQ_GPIO(5)] = { + .reg = 1, + .mask = WM8994_GP5_EINT, + }, + [WM8994_IRQ_GPIO(6)] = { + .reg = 1, + .mask = WM8994_GP6_EINT, + }, + [WM8994_IRQ_GPIO(7)] = { + .reg = 1, + .mask = WM8994_GP7_EINT, + }, + [WM8994_IRQ_GPIO(8)] = { + .reg = 1, + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(9)] = { + .reg = 1, + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(10)] = { + .reg = 1, + .mask = WM8994_GP10_EINT, + }, + [WM8994_IRQ_GPIO(11)] = { + .reg = 1, + .mask = WM8994_GP11_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm8994_irq_data *irq_data) +{ + return WM8994_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline int irq_data_to_mask_reg(struct wm8994_irq_data *irq_data) +{ + return WM8994_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; +} + +static inline struct wm8994_irq_data *irq_to_wm8994_irq(struct wm8994 *wm8994, + int irq) +{ + return &wm8994_irqs[irq - wm8994->irq_base]; +} + +static void wm8994_irq_lock(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + + mutex_lock(&wm8994->irq_lock); +} + +static void wm8994_irq_sync_unlock(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + int i; + + for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + if (wm8994->irq_masks_cur[i] != wm8994->irq_masks_cache[i]) { + wm8994->irq_masks_cache[i] = wm8994->irq_masks_cur[i]; + wm8994_reg_write(wm8994, + WM8994_INTERRUPT_STATUS_1_MASK + i, + wm8994->irq_masks_cur[i]); + } + } + + mutex_unlock(&wm8994->irq_lock); +} + +static void wm8994_irq_unmask(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq); + + wm8994->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void wm8994_irq_mask(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq); + + wm8994->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip wm8994_irq_chip = { + .name = "wm8994", + .bus_lock = wm8994_irq_lock, + .bus_sync_unlock = wm8994_irq_sync_unlock, + .mask = wm8994_irq_mask, + .unmask = wm8994_irq_unmask, +}; + +/* The processing of the primary interrupt occurs in a thread so that + * we can interact with the device over I2C or SPI. */ +static irqreturn_t wm8994_irq_thread(int irq, void *data) +{ + struct wm8994 *wm8994 = data; + unsigned int i; + u16 status[WM8994_NUM_IRQ_REGS]; + int ret; + + ret = wm8994_bulk_read(wm8994, WM8994_INTERRUPT_STATUS_1, + WM8994_NUM_IRQ_REGS, status); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read interrupt status: %d\n", + ret); + return IRQ_NONE; + } + + /* Apply masking */ + for (i = 0; i < WM8994_NUM_IRQ_REGS; i++) + status[i] &= ~wm8994->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { + if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) + handle_nested_irq(wm8994->irq_base + i); + } + + /* Ack any unmasked IRQs */ + for (i = 0; i < ARRAY_SIZE(status); i++) { + if (status[i]) + wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1 + i, + status[i]); + } + + return IRQ_HANDLED; +} + +int wm8994_irq_init(struct wm8994 *wm8994) +{ + int i, cur_irq, ret; + + mutex_init(&wm8994->irq_lock); + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { + wm8994->irq_masks_cur[i] = 0xffff; + wm8994->irq_masks_cache[i] = 0xffff; + wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + if (!wm8994->irq) { + dev_warn(wm8994->dev, + "No interrupt specified, no interrupts\n"); + wm8994->irq_base = 0; + return 0; + } + + if (!wm8994->irq_base) { + dev_err(wm8994->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + + /* Register them with genirq */ + for (cur_irq = wm8994->irq_base; + cur_irq < ARRAY_SIZE(wm8994_irqs) + wm8994->irq_base; + cur_irq++) { + set_irq_chip_data(cur_irq, wm8994); + set_irq_chip_and_handler(cur_irq, &wm8994_irq_chip, + handle_edge_irq); + set_irq_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(wm8994->irq, NULL, wm8994_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "wm8994", wm8994); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to request IRQ %d: %d\n", + wm8994->irq, ret); + return ret; + } + + /* Enable top level interrupt if it was masked */ + wm8994_reg_write(wm8994, WM8994_INTERRUPT_CONTROL, 0); + + return 0; +} + +void wm8994_irq_exit(struct wm8994 *wm8994) +{ + if (wm8994->irq) + free_irq(wm8994->irq, wm8994); +} diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index b06ff284674..de79baee492 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -15,14 +15,38 @@ #ifndef __MFD_WM8994_CORE_H__ #define __MFD_WM8994_CORE_H__ +#include + struct regulator_dev; struct regulator_bulk_data; #define WM8994_NUM_GPIO_REGS 11 -#define WM8994_NUM_LDO_REGS 2 +#define WM8994_NUM_LDO_REGS 2 +#define WM8994_NUM_IRQ_REGS 2 + +#define WM8994_IRQ_TEMP_SHUT 0 +#define WM8994_IRQ_MIC1_DET 1 +#define WM8994_IRQ_MIC1_SHRT 2 +#define WM8994_IRQ_MIC2_DET 3 +#define WM8994_IRQ_MIC2_SHRT 4 +#define WM8994_IRQ_FLL1_LOCK 5 +#define WM8994_IRQ_FLL2_LOCK 6 +#define WM8994_IRQ_SRC1_LOCK 7 +#define WM8994_IRQ_SRC2_LOCK 8 +#define WM8994_IRQ_AIF1DRC1_SIG_DET 9 +#define WM8994_IRQ_AIF1DRC2_SIG_DET 10 +#define WM8994_IRQ_AIF2DRC_SIG_DET 11 +#define WM8994_IRQ_FIFOS_ERR 12 +#define WM8994_IRQ_WSEQ_DONE 13 +#define WM8994_IRQ_DCS_DONE 14 +#define WM8994_IRQ_TEMP_WARN 15 + +/* GPIOs in the chip are numbered from 1-11 */ +#define WM8994_IRQ_GPIO(x) (x + WM8994_IRQ_TEMP_WARN) struct wm8994 { struct mutex io_lock; + struct mutex irq_lock; struct device *dev; int (*read_dev)(struct wm8994 *wm8994, unsigned short reg, @@ -33,6 +57,11 @@ struct wm8994 { void *control_data; int gpio_base; + int irq_base; + + int irq; + u16 irq_masks_cur[WM8994_NUM_IRQ_REGS]; + u16 irq_masks_cache[WM8994_NUM_IRQ_REGS]; /* Used over suspend/resume */ u16 ldo_regs[WM8994_NUM_LDO_REGS]; @@ -51,4 +80,26 @@ int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg, int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, int count, u16 *buf); + +/* Helper to save on boilerplate */ +static inline int wm8994_request_irq(struct wm8994 *wm8994, int irq, + irq_handler_t handler, const char *name, + void *data) +{ + if (!wm8994->irq_base) + return -EINVAL; + return request_threaded_irq(wm8994->irq_base + irq, NULL, handler, + IRQF_TRIGGER_RISING, name, + data); +} +static inline void wm8994_free_irq(struct wm8994 *wm8994, int irq, void *data) +{ + if (!wm8994->irq_base) + return; + free_irq(wm8994->irq_base + irq, data); +} + +int wm8994_irq_init(struct wm8994 *wm8994); +void wm8994_irq_exit(struct wm8994 *wm8994); + #endif diff --git a/include/linux/mfd/wm8994/pdata.h b/include/linux/mfd/wm8994/pdata.h index 70d6a8687dc..5c51f367c06 100644 --- a/include/linux/mfd/wm8994/pdata.h +++ b/include/linux/mfd/wm8994/pdata.h @@ -70,6 +70,7 @@ struct wm8994_pdata { struct wm8994_ldo_pdata ldo[WM8994_NUM_LDO]; + int irq_base; /** Base IRQ number for WM8994, required for IRQs */ int num_drc_cfgs; struct wm8994_drc_cfg *drc_cfgs; -- cgit v1.2.3-18-g5258 From ddf438cf2a6de9aa47783a45907e1188b9437ff7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 2 Apr 2010 14:51:59 +0100 Subject: gpiolib: Implement gpio_to_irq for WM8994 GPIO controller Signed-off-by: Mark Brown --- drivers/gpio/wm8994-gpio.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpio/wm8994-gpio.c b/drivers/gpio/wm8994-gpio.c index de28b4a470e..3ee35badcf1 100644 --- a/drivers/gpio/wm8994-gpio.c +++ b/drivers/gpio/wm8994-gpio.c @@ -80,6 +80,18 @@ static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value) wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value); } +static int wm8994_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (!wm8994->irq_base) + return -EINVAL; + + return wm8994->irq_base + offset; +} + + #ifdef CONFIG_DEBUG_FS static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { -- cgit v1.2.3-18-g5258 From 8876698406147986a9a7748586a54c4b14514c0e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 29 Mar 2010 20:57:12 +0100 Subject: ASoC: Implement interrupt based WM8994 microphone detection Support interrupt based microphone bias detection. The WM8994 has two microphone bias supplies, with detection supported on both. Detection using GPIOs together with the standard GPIO based jack framework is already supported via the platform data for the WM8994 core driver. Note that as well as the microphone bias itself the system clock and whichever AIF clock is supplying the system clock will need to be enabled for detection to function. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 137 +++++++++++++++++++++++++++++++++++++++++++++- sound/soc/codecs/wm8994.h | 3 + 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index bdda0936a40..fc3dce81492 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -61,6 +61,12 @@ static int wm8994_retune_mobile_base[] = { #define WM8994_REG_CACHE_SIZE 0x621 +struct wm8994_micdet { + struct snd_soc_jack *jack; + int det; + int shrt; +}; + /* codec private data */ struct wm8994_priv { struct wm_hubs_data hubs; @@ -86,6 +92,8 @@ struct wm8994_priv { int retune_mobile_cfg[WM8994_NUM_EQ]; struct soc_enum retune_mobile_enum; + struct wm8994_micdet micdet[2]; + struct wm8994_pdata *pdata; }; @@ -3702,6 +3710,96 @@ struct snd_soc_codec_device soc_codec_dev_wm8994 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8994); +/** + * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ + * + * @codec: WM8994 codec + * @jack: jack to report detection events on + * @micbias: microphone bias to detect on + * @det: value to report for presence detection + * @shrt: value to report for short detection + * + * Enable microphone detection via IRQ on the WM8994. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8903 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * Configuration of detection levels is available via the micbias1_lvl + * and micbias2_lvl platform data members. + */ +int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int micbias, int det, int shrt) +{ + struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_micdet *micdet; + int reg; + + switch (micbias) { + case 1: + micdet = &wm8994->micdet[0]; + break; + case 2: + micdet = &wm8994->micdet[1]; + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n", + micbias, det, shrt); + + /* Store the configuration */ + micdet->jack = jack; + micdet->det = det; + micdet->shrt = shrt; + + /* If either of the jacks is set up then enable detection */ + if (wm8994->micdet[0].jack || wm8994->micdet[1].jack) + reg = WM8994_MICD_ENA; + else + reg = 0; + + snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8994_mic_detect); + +static irqreturn_t wm8994_mic_irq(int irq, void *data) +{ + struct wm8994_priv *priv = data; + struct snd_soc_codec *codec = &priv->codec; + int reg; + int report; + + reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2); + if (reg < 0) { + dev_err(codec->dev, "Failed to read microphone status: %d\n", + reg); + return IRQ_HANDLED; + } + + dev_dbg(codec->dev, "Microphone status: %x\n", reg); + + report = 0; + if (reg & WM8994_MIC1_DET_STS) + report |= priv->micdet[0].det; + if (reg & WM8994_MIC1_SHRT_STS) + report |= priv->micdet[0].shrt; + snd_soc_jack_report(priv->micdet[0].jack, report, + priv->micdet[0].det | priv->micdet[0].shrt); + + report = 0; + if (reg & WM8994_MIC2_DET_STS) + report |= priv->micdet[1].det; + if (reg & WM8994_MIC2_SHRT_STS) + report |= priv->micdet[1].shrt; + snd_soc_jack_report(priv->micdet[1].jack, report, + priv->micdet[1].det | priv->micdet[1].shrt); + + return IRQ_HANDLED; +} + static int wm8994_codec_probe(struct platform_device *pdev) { int ret; @@ -3774,6 +3872,30 @@ static int wm8994_codec_probe(struct platform_device *pdev) } + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_DET, + wm8994_mic_irq, "Mic 1 detect", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic1 detect IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, + wm8994_mic_irq, "Mic 1 short", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic1 short IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_DET, + wm8994_mic_irq, "Mic 2 detect", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic2 detect IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, + wm8994_mic_irq, "Mic 2 short", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic2 short IRQ: %d\n", ret); + /* Remember if AIFnLRCLK is configured as a GPIO. This should be * configured on init - if a system wants to do this dynamically * at runtime we can deal with that then. @@ -3781,7 +3903,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1); if (ret < 0) { dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret); - goto err; + goto err_irq; } if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { wm8994->lrclk_shared[0] = 1; @@ -3793,7 +3915,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6); if (ret < 0) { dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret); - goto err; + goto err_irq; } if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { wm8994->lrclk_shared[1] = 1; @@ -3843,7 +3965,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_irq; } ret = snd_soc_register_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); @@ -3858,6 +3980,11 @@ static int wm8994_codec_probe(struct platform_device *pdev) err_codec: snd_soc_unregister_codec(codec); +err_irq: + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); err: kfree(wm8994); return ret; @@ -3871,6 +3998,10 @@ static int __devexit wm8994_codec_remove(struct platform_device *pdev) wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); snd_soc_unregister_codec(&wm8994->codec); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); kfree(wm8994); wm8994_codec = NULL; diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 0a5e1424dea..79d5915ae4b 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -23,4 +23,7 @@ extern struct snd_soc_dai wm8994_dai[]; #define WM8994_FLL1 1 #define WM8994_FLL2 2 +int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int micbias, int det, int shrt); + #endif -- cgit v1.2.3-18-g5258 From 68c7ccb8f85801655aa681391dc0ee037a2b66d4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 07:45:46 +0200 Subject: ALSA: powermac - Fix obsoleted machine_is_compatible() machine_is_compatible() was renamed to of_machine_is_compatible(). Signed-off-by: Takashi Iwai --- sound/ppc/tumbler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 0b69e96f0d1..20afdf9772e 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -47,7 +47,7 @@ #define DBG(fmt...) #endif -#define IS_G4DA (machine_is_compatible("PowerMac3,4")) +#define IS_G4DA (of_machine_is_compatible("PowerMac3,4")) /* i2c address for tumbler */ #define TAS_I2C_ADDR 0x34 -- cgit v1.2.3-18-g5258 From 489008cd58740fe3842822681d33bf87c07c3412 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 09:06:00 +0200 Subject: ALSA: hda - Fix ALC882 DAC connections in auto mode Assign DACs properly to each output. Currently, the front output is bound to HP/speaker outputs blindly, but they should be assigned to individual DACs. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 45 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index c9c31c51863..f4ca9b2d878 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -10191,21 +10191,21 @@ static int alc882_auto_create_input_ctls(struct hda_codec *codec, static void alc882_auto_set_output_and_unmute(struct hda_codec *codec, hda_nid_t nid, int pin_type, - int dac_idx) + hda_nid_t dac) { - /* set as output */ - struct alc_spec *spec = codec->spec; int idx; + printk("XXX set output pin %x, dac %x\n", nid, dac); + /* set as output */ alc_set_pin_output(codec, nid, pin_type); - if (dac_idx >= spec->multiout.num_dacs) - return; - if (spec->multiout.dac_nids[dac_idx] == 0x25) + + if (dac == 0x25) idx = 4; + else if (dac >= 0x02 && dac <= 0x05) + idx = dac - 2; else - idx = spec->multiout.dac_nids[dac_idx] - 2; + return; snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, idx); - } static void alc882_auto_init_multi_out(struct hda_codec *codec) @@ -10218,22 +10218,29 @@ static void alc882_auto_init_multi_out(struct hda_codec *codec) int pin_type = get_pin_type(spec->autocfg.line_out_type); if (nid) alc882_auto_set_output_and_unmute(codec, nid, pin_type, - i); + spec->multiout.dac_nids[i]); } } static void alc882_auto_init_hp_out(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; - hda_nid_t pin; + hda_nid_t pin, dac; pin = spec->autocfg.hp_pins[0]; - if (pin) /* connect to front */ - /* use dac 0 */ - alc882_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); + if (pin) { + dac = spec->multiout.hp_nid; + if (!dac) + dac = spec->multiout.dac_nids[0]; /* to front */ + alc882_auto_set_output_and_unmute(codec, pin, PIN_HP, dac); + } pin = spec->autocfg.speaker_pins[0]; - if (pin) - alc882_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); + if (pin) { + dac = spec->multiout.extra_out_nid[0]; + if (!dac) + dac = spec->multiout.dac_nids[0]; /* to front */ + alc882_auto_set_output_and_unmute(codec, pin, PIN_OUT, dac); + } } static void alc882_auto_init_analog_input(struct hda_codec *codec) @@ -10347,6 +10354,10 @@ static int alc882_parse_auto_config(struct hda_codec *codec) if (err < 0) return err; err = alc880_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc880_auto_create_extra_out(spec, spec->autocfg.hp_pins[0], + "Headphone"); if (err < 0) return err; err = alc880_auto_create_extra_out(spec, @@ -10354,10 +10365,6 @@ static int alc882_parse_auto_config(struct hda_codec *codec) "Speaker"); if (err < 0) return err; - err = alc880_auto_create_extra_out(spec, spec->autocfg.hp_pins[0], - "Headphone"); - if (err < 0) - return err; err = alc882_auto_create_input_ctls(codec, &spec->autocfg); if (err < 0) return err; -- cgit v1.2.3-18-g5258 From 25e8d9b67b206b8bf9bf11699d1a7e67c87c1a2e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 14:53:00 +0200 Subject: ALSA: hda - Remove left-over debug printk in patch_realtek.c Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index f4ca9b2d878..83610308192 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -10195,7 +10195,6 @@ static void alc882_auto_set_output_and_unmute(struct hda_codec *codec, { int idx; - printk("XXX set output pin %x, dac %x\n", nid, dac); /* set as output */ alc_set_pin_output(codec, nid, pin_type); -- cgit v1.2.3-18-g5258 From d05468b72a32ec45aefb48caa00bd99350b9cf86 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 18:29:46 +0200 Subject: ALSA: pcm - Remove BKL from async callback It's simply calling fasync_helper(). Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 87288762403..cadba308776 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3303,18 +3303,13 @@ static int snd_pcm_fasync(int fd, struct file * file, int on) struct snd_pcm_file * pcm_file; struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - int err = -ENXIO; - lock_kernel(); pcm_file = file->private_data; substream = pcm_file->substream; if (PCM_RUNTIME_CHECK(substream)) - goto out; + return -ENXIO; runtime = substream->runtime; - err = fasync_helper(fd, file, on, &runtime->fasync); -out: - unlock_kernel(); - return err; + return fasync_helper(fd, file, on, &runtime->fasync); } /* -- cgit v1.2.3-18-g5258 From 5b5cd553e3ac49e6a9bac148f07ab94d3d96dae5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 18:33:57 +0200 Subject: ALSA: info - Remove BKL Use the fine-grained mutex for the assigned info object, instead. Signed-off-by: Takashi Iwai --- sound/core/info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/core/info.c b/sound/core/info.c index d749a0d394a..fe836618fa2 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -167,7 +167,7 @@ static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) data = file->private_data; entry = data->entry; - lock_kernel(); + mutex_lock(&entry->access); switch (entry->content) { case SNDRV_INFO_CONTENT_TEXT: switch (orig) { @@ -196,7 +196,7 @@ static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) } ret = -ENXIO; out: - unlock_kernel(); + mutex_unlock(&entry->access); return ret; } -- cgit v1.2.3-18-g5258 From 4cf19b848f92641eeb2585949a09eedec57fb53a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 7 Apr 2010 18:52:08 +0200 Subject: ALSA: Remove BKL from open multiplexer Use a local mutex instead of BKL. This should suffice since each device type has also its open_mutex. Also, a bit of clean-up of the legacy device auto-loading code. Signed-off-by: Takashi Iwai --- sound/core/sound.c | 73 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/sound/core/sound.c b/sound/core/sound.c index 563d1967a0a..ac42af42b78 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -120,7 +120,29 @@ void *snd_lookup_minor_data(unsigned int minor, int type) EXPORT_SYMBOL(snd_lookup_minor_data); -static int __snd_open(struct inode *inode, struct file *file) +#ifdef CONFIG_MODULES +static struct snd_minor *autoload_device(unsigned int minor) +{ + int dev; + mutex_unlock(&sound_mutex); /* release lock temporarily */ + dev = SNDRV_MINOR_DEVICE(minor); + if (dev == SNDRV_MINOR_CONTROL) { + /* /dev/aloadC? */ + int card = SNDRV_MINOR_CARD(minor); + if (snd_cards[card] == NULL) + snd_request_card(card); + } else if (dev == SNDRV_MINOR_GLOBAL) { + /* /dev/aloadSEQ */ + snd_request_other(minor); + } + mutex_lock(&sound_mutex); /* reacuire lock */ + return snd_minors[minor]; +} +#else /* !CONFIG_MODULES */ +#define autoload_device(minor) NULL +#endif /* CONFIG_MODULES */ + +static int snd_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); struct snd_minor *mptr = NULL; @@ -129,55 +151,36 @@ static int __snd_open(struct inode *inode, struct file *file) if (minor >= ARRAY_SIZE(snd_minors)) return -ENODEV; + mutex_lock(&sound_mutex); mptr = snd_minors[minor]; if (mptr == NULL) { -#ifdef CONFIG_MODULES - int dev = SNDRV_MINOR_DEVICE(minor); - if (dev == SNDRV_MINOR_CONTROL) { - /* /dev/aloadC? */ - int card = SNDRV_MINOR_CARD(minor); - if (snd_cards[card] == NULL) - snd_request_card(card); - } else if (dev == SNDRV_MINOR_GLOBAL) { - /* /dev/aloadSEQ */ - snd_request_other(minor); - } -#ifndef CONFIG_SND_DYNAMIC_MINORS - /* /dev/snd/{controlC?,seq} */ - mptr = snd_minors[minor]; - if (mptr == NULL) -#endif -#endif + mptr = autoload_device(minor); + if (!mptr) { + mutex_unlock(&sound_mutex); return -ENODEV; + } } old_fops = file->f_op; file->f_op = fops_get(mptr->f_ops); if (file->f_op == NULL) { file->f_op = old_fops; - return -ENODEV; + err = -ENODEV; } - if (file->f_op->open) + mutex_unlock(&sound_mutex); + if (err < 0) + return err; + + if (file->f_op->open) { err = file->f_op->open(inode, file); - if (err) { - fops_put(file->f_op); - file->f_op = fops_get(old_fops); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } } fops_put(old_fops); return err; } - -/* BKL pushdown: nasty #ifdef avoidance wrapper */ -static int snd_open(struct inode *inode, struct file *file) -{ - int ret; - - lock_kernel(); - ret = __snd_open(inode, file); - unlock_kernel(); - return ret; -} - static const struct file_operations snd_fops = { .owner = THIS_MODULE, -- cgit v1.2.3-18-g5258 From 6ca0c22ef8a4e988e2487d25964d55e6c37c5785 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 8 Apr 2010 20:48:51 +0200 Subject: ASoC: WM8750: Convert to new API Register the WM8750 as a SPI or I2C device. This patch mostly shuffles code around. Hugely inspired by WM8753 which was already converted. Also, this patch fixes the Jive and Spitz machine. Signed-off-by: Marek Vasut Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/wm8750.c | 295 +++++++++++++++++++--------------------- sound/soc/pxa/spitz.c | 43 +++++- sound/soc/s3c24xx/jive_wm8750.c | 5 - 3 files changed, 175 insertions(+), 168 deletions(-) diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index ee084083a49..4bbc512c083 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -29,11 +29,6 @@ #include "wm8750.h" -/* codec private data */ -struct wm8750_priv { - unsigned int sysclk; -}; - /* * wm8750 register cache * We can't read the WM8750 register space when we @@ -53,6 +48,13 @@ static const u16 wm8750_reg[] = { 0x0079, 0x0079, 0x0079, /* 40 */ }; +/* codec private data */ +struct wm8750_priv { + unsigned int sysclk; + struct snd_soc_codec codec; + u16 reg_cache[ARRAY_SIZE(wm8750_reg)]; +}; + #define wm8750_reset(c) snd_soc_write(c, WM8750_RESET, 0) /* @@ -695,25 +697,90 @@ static int wm8750_resume(struct platform_device *pdev) return 0; } +static struct snd_soc_codec *wm8750_codec; + +static int wm8750_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!wm8750_codec) { + dev_err(&pdev->dev, "WM8750 codec not yet registered\n"); + return -EINVAL; + } + + socdev->card->codec = wm8750_codec; + codec = wm8750_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to create pcms\n"); + goto err; + } + + snd_soc_add_controls(codec, wm8750_snd_controls, + ARRAY_SIZE(wm8750_snd_controls)); + wm8750_add_widgets(codec); + + return 0; + +err: + return ret; +} + +/* power down chip */ +static int wm8750_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_wm8750 = { + .probe = wm8750_probe, + .remove = wm8750_remove, + .suspend = wm8750_suspend, + .resume = wm8750_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); + /* * initialise the WM8750 driver * register the mixer and dsp interfaces with the kernel */ -static int wm8750_init(struct snd_soc_device *socdev, - enum snd_soc_control_type control) +static int wm8750_register(struct wm8750_priv *wm8750, + enum snd_soc_control_type control) { - struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_codec *codec = &wm8750->codec; int reg, ret = 0; + if (wm8750_codec) { + dev_err(codec->dev, "Multiple WM8750 devices not supported\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->name = "WM8750"; codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_STANDBY; codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; - codec->reg_cache_size = ARRAY_SIZE(wm8750_reg); - codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); - if (codec->reg_cache == NULL) - return -ENOMEM; + codec->private_data = wm8750; + codec->reg_cache_size = ARRAY_SIZE(wm8750->reg_cache) + 1; + codec->reg_cache = &wm8750->reg_cache; + codec->private_data = wm8750; + + memcpy(codec->reg_cache, wm8750_reg, sizeof(wm8750->reg_cache)); ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { @@ -727,13 +794,6 @@ static int wm8750_init(struct snd_soc_device *socdev, goto err; } - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - printk(KERN_ERR "wm8750: failed to create pcms\n"); - goto err; - } - /* charge output caps */ wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -755,19 +815,37 @@ static int wm8750_init(struct snd_soc_device *socdev, reg = snd_soc_read(codec, WM8750_RINVOL); snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100); - snd_soc_add_controls(codec, wm8750_snd_controls, - ARRAY_SIZE(wm8750_snd_controls)); - wm8750_add_widgets(codec); - return ret; + wm8750_codec = codec; + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(&wm8750_dai, 1); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); err: - kfree(codec->reg_cache); + kfree(wm8750); return ret; } -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ -static struct snd_soc_device *wm8750_socdev; +static void wm8750_unregister(struct wm8750_priv *wm8750) +{ + wm8750_set_bias_level(&wm8750->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dais(&wm8750_dai, 1); + snd_soc_unregister_codec(&wm8750->codec); + kfree(wm8750); + wm8750_codec = NULL; +} #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -781,24 +859,26 @@ static struct snd_soc_device *wm8750_socdev; static int wm8750_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->card->codec; - int ret; + struct snd_soc_codec *codec; + struct wm8750_priv *wm8750; - i2c_set_clientdata(i2c, codec); + wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; + + codec = &wm8750->codec; codec->control_data = i2c; + i2c_set_clientdata(i2c, wm8750); - ret = wm8750_init(socdev, SND_SOC_I2C); - if (ret < 0) - pr_err("failed to initialise WM8750\n"); + codec->dev = &i2c->dev; - return ret; + return wm8750_register(wm8750, SND_SOC_I2C); } static int wm8750_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); + struct wm8750_priv *wm8750 = i2c_get_clientdata(client); + wm8750_unregister(wm8750); return 0; } @@ -817,66 +897,31 @@ static struct i2c_driver wm8750_i2c_driver = { .remove = wm8750_i2c_remove, .id_table = wm8750_i2c_id, }; - -static int wm8750_add_i2c_device(struct platform_device *pdev, - const struct wm8750_setup_data *setup) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - int ret; - - ret = i2c_add_driver(&wm8750_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "wm8750", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; - } - - return 0; - -err_driver: - i2c_del_driver(&wm8750_i2c_driver); - return -ENODEV; -} #endif #if defined(CONFIG_SPI_MASTER) static int __devinit wm8750_spi_probe(struct spi_device *spi) { - struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->card->codec; - int ret; + struct snd_soc_codec *codec; + struct wm8750_priv *wm8750; + wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; + + codec = &wm8750->codec; codec->control_data = spi; + codec->dev = &spi->dev; - ret = wm8750_init(socdev, SND_SOC_SPI); - if (ret < 0) - dev_err(&spi->dev, "failed to initialise WM8750\n"); + dev_set_drvdata(&spi->dev, wm8750); - return ret; + return wm8750_register(wm8750, SND_SOC_SPI); } static int __devexit wm8750_spi_remove(struct spi_device *spi) { + struct wm8750_priv *wm8750 = dev_get_drvdata(&spi->dev); + wm8750_unregister(wm8750); return 0; } @@ -891,93 +936,31 @@ static struct spi_driver wm8750_spi_driver = { }; #endif -static int wm8750_probe(struct platform_device *pdev) +static int __init wm8750_modinit(void) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct wm8750_setup_data *setup = socdev->codec_data; - struct snd_soc_codec *codec; - struct wm8750_priv *wm8750; int ret; - - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; - - wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); - if (wm8750 == NULL) { - kfree(codec); - return -ENOMEM; - } - - codec->private_data = wm8750; - socdev->card->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - wm8750_socdev = socdev; - - ret = -ENODEV; - #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - ret = wm8750_add_i2c_device(pdev, setup); - } + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) + pr_err("Failed to register WM8750 I2C driver: %d\n", ret); #endif #if defined(CONFIG_SPI_MASTER) - if (setup->spi) { - ret = spi_register_driver(&wm8750_spi_driver); - if (ret != 0) - printk(KERN_ERR "can't add spi driver"); - } + ret = spi_register_driver(&wm8750_spi_driver); + if (ret != 0) + pr_err("Failed to register WM8750 SPI driver: %d\n", ret); #endif - - if (ret != 0) { - kfree(codec->private_data); - kfree(codec); - } - return ret; + return 0; } +module_init(wm8750_modinit); -/* power down chip */ -static int wm8750_remove(struct platform_device *pdev) +static void __exit wm8750_exit(void) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - - if (codec->control_data) - wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8750_i2c_driver); #endif #if defined(CONFIG_SPI_MASTER) spi_unregister_driver(&wm8750_spi_driver); #endif - kfree(codec->private_data); - kfree(codec); - - return 0; -} - -struct snd_soc_codec_device soc_codec_dev_wm8750 = { - .probe = wm8750_probe, - .remove = wm8750_remove, - .suspend = wm8750_suspend, - .resume = wm8750_resume, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); - -static int __init wm8750_modinit(void) -{ - return snd_soc_register_dai(&wm8750_dai); -} -module_init(wm8750_modinit); - -static void __exit wm8750_exit(void) -{ - snd_soc_unregister_dai(&wm8750_dai); } module_exit(wm8750_exit); diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index c4cd2acaacb..1941a357e8c 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -322,19 +322,44 @@ static struct snd_soc_card snd_soc_spitz = { .num_links = 1, }; -/* spitz audio private data */ -static struct wm8750_setup_data spitz_wm8750_setup = { - .i2c_bus = 0, - .i2c_address = 0x1b, -}; - /* spitz audio subsystem */ static struct snd_soc_device spitz_snd_devdata = { .card = &snd_soc_spitz, .codec_dev = &soc_codec_dev_wm8750, - .codec_data = &spitz_wm8750_setup, }; +/* + * FIXME: This is a temporary bodge to avoid cross-tree merge issues. + * New drivers should register the wm8750 I2C device in the machine + * setup code (under arch/arm for ARM systems). + */ +static int wm8750_i2c_register(void) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = 0x1b; + strlcpy(info.type, "wm8750", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(0); + if (!adapter) { + printk(KERN_ERR "can't get i2c adapter 0\n"); + return -ENODEV; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + printk(KERN_ERR "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + return -ENODEV; + } + + return 0; +} + static struct platform_device *spitz_snd_device; static int __init spitz_init(void) @@ -344,6 +369,10 @@ static int __init spitz_init(void) if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita())) return -ENODEV; + ret = wm8750_i2c_setup(); + if (ret != 0) + return ret; + spitz_snd_device = platform_device_alloc("soc-audio", -1); if (!spitz_snd_device) return -ENOMEM; diff --git a/sound/soc/s3c24xx/jive_wm8750.c b/sound/soc/s3c24xx/jive_wm8750.c index 59dc2c6b56d..97d8ff3196b 100644 --- a/sound/soc/s3c24xx/jive_wm8750.c +++ b/sound/soc/s3c24xx/jive_wm8750.c @@ -152,15 +152,10 @@ static struct snd_soc_card snd_soc_machine_jive = { .num_links = 1, }; -/* jive audio private data */ -static struct wm8750_setup_data jive_wm8750_setup = { -}; - /* jive audio subsystem */ static struct snd_soc_device jive_snd_devdata = { .card = &snd_soc_machine_jive, .codec_dev = &soc_codec_dev_wm8750, - .codec_data = &jive_wm8750_setup, }; static struct platform_device *jive_snd_device; -- cgit v1.2.3-18-g5258 From e135443e2133473f44aa439ad333554dad14c21a Mon Sep 17 00:00:00 2001 From: Bill Gatliff Date: Fri, 9 Apr 2010 18:08:08 +0100 Subject: ASoC: Use SNDRV_PCM_RATE_8000_96000 macro for WM8731 Signed-off-by: Bill Gatliff Acked-by: Richard Purdie Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 5a2619dbf28..d5e1c5b681e 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -389,11 +389,7 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, return 0; } -#define WM8731_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) +#define WM8731_RATES SNDRV_PCM_RATE_8000_96000 #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) -- cgit v1.2.3-18-g5258 From d21e0f4cd16656f71207683ee27465600ad21625 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 5 Apr 2010 06:13:38 +0200 Subject: ASoC: Zipit Z2 WM8750 ASoC driver This patch adds support for sound through the WM8750 codec on Zipit Z2. Also, this patch incorporates support for detecting headset jack insertion through the jack detection API. Signed-off-by: Marek Vasut Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/pxa/Kconfig | 8 ++ sound/soc/pxa/Makefile | 2 + sound/soc/pxa/z2.c | 246 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 sound/soc/pxa/z2.c diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 376e14a9c27..495a36fba36 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -42,6 +42,14 @@ config SND_PXA2XX_SOC_SPITZ Say Y if you want to add support for SoC audio on Sharp Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita). +config SND_PXA2XX_SOC_Z2 + tristate "SoC Audio support for Zipit Z2" + depends on SND_PXA2XX_SOC && MACH_ZIPIT2 + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8750 + help + Say Y if you want to add support for SoC audio on Zipit Z2. + config SND_PXA2XX_SOC_POODLE tristate "SoC Audio support for Poodle" depends on SND_PXA2XX_SOC && MACH_POODLE diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index f3e08fd40ca..caa03d8f478 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-z2-objs := z2.o snd-soc-imote2-objs := imote2.o snd-soc-raumfeld-objs := raumfeld.o @@ -36,6 +37,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o +obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c new file mode 100644 index 00000000000..4e4d2fa8ddc --- /dev/null +++ b/sound/soc/pxa/z2.c @@ -0,0 +1,246 @@ +/* + * linux/sound/soc/pxa/z2.c + * + * SoC Audio driver for Aeronix Zipit Z2 + * + * Copyright (C) 2009 Ken McGuire + * Copyright (C) 2010 Marek Vasut + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8750.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" + +static struct snd_soc_card snd_soc_z2; + +static int z2_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 *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* 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; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_jack hs_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .gpio = GPIO37_ZIPITZ2_HEADSET_DETECT, + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; + +/* z2 machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + + /* headset is a mic and mono headphone */ + SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +/* Z2 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* headphone connected to LOUT1, ROUT1 */ + {"Headphone Jack", NULL, "LOUT1"}, + {"Headphone Jack", NULL, "ROUT1"}, + + /* ext speaker connected to LOUT2, ROUT2 */ + {"Ext Spk", NULL , "ROUT2"}, + {"Ext Spk", NULL , "LOUT2"}, + + /* mic is connected to R input 2 - with bias */ + {"RINPUT2", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, +}; + +/* + * Logic for a wm8750 as connected on a Z2 Device + */ +static int z2_wm8750_init(struct snd_soc_codec *codec) +{ + int ret; + + /* NC codec pins */ + snd_soc_dapm_disable_pin(codec, "LINPUT3"); + snd_soc_dapm_disable_pin(codec, "RINPUT3"); + snd_soc_dapm_disable_pin(codec, "OUT3"); + snd_soc_dapm_disable_pin(codec, "MONO"); + + /* Add z2 specific widgets */ + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); + + /* Set up z2 specific audio paths */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + ret = snd_soc_dapm_sync(codec); + if (ret) + goto err; + + /* Jack detection API stuff */ + ret = snd_soc_jack_new(&snd_soc_z2, "Headset Jack", SND_JACK_HEADSET, + &hs_jack); + if (ret) + goto err; + + ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + if (ret) + goto err; + + ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +static struct snd_soc_ops z2_ops = { + .hw_params = z2_hw_params, +}; + +/* z2 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link z2_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8750_dai, + .init = z2_wm8750_init, + .ops = &z2_ops, +}; + +/* z2 audio machine driver */ +static struct snd_soc_card snd_soc_z2 = { + .name = "Z2", + .platform = &pxa2xx_soc_platform, + .dai_link = &z2_dai, + .num_links = 1, +}; + +/* z2 audio subsystem */ +static struct snd_soc_device z2_snd_devdata = { + .card = &snd_soc_z2, + .codec_dev = &soc_codec_dev_wm8750, +}; + +static struct platform_device *z2_snd_device; + +static int __init z2_init(void) +{ + int ret; + + if (!machine_is_zipit2()) + return -ENODEV; + + z2_snd_device = platform_device_alloc("soc-audio", -1); + if (!z2_snd_device) + return -ENOMEM; + + platform_set_drvdata(z2_snd_device, &z2_snd_devdata); + z2_snd_devdata.dev = &z2_snd_device->dev; + ret = platform_device_add(z2_snd_device); + + if (ret) + platform_device_put(z2_snd_device); + + return ret; +} + +static void __exit z2_exit(void) +{ + platform_device_unregister(z2_snd_device); +} + +module_init(z2_init); +module_exit(z2_exit); + +MODULE_AUTHOR("Ken McGuire , " + "Marek Vasut "); +MODULE_DESCRIPTION("ALSA SoC ZipitZ2"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-18-g5258 From 24e4a1211f691fc671de44685430dbad757d8487 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Apr 2010 11:22:01 +0200 Subject: ALSA: info - Use standard types for info callbacks Use loff_t, size_t and ssize_t for arguments of info callbacks to follow the standard procfs. Signed-off-by: Takashi Iwai --- include/sound/info.h | 24 ++++++++++++------------ sound/drivers/opl4/opl4_proc.c | 21 +++++++++++++-------- sound/isa/gus/gus_mem_proc.c | 16 ++++++++-------- sound/pci/cs4281.c | 16 ++++++++-------- sound/pci/cs46xx/cs46xx_lib.c | 7 ++++--- sound/pci/emu10k1/emuproc.c | 8 ++++---- sound/pci/mixart/mixart.c | 32 ++++++++++++++++---------------- 7 files changed, 65 insertions(+), 59 deletions(-) diff --git a/include/sound/info.h b/include/sound/info.h index 112e8949e1a..4e94cf1ff76 100644 --- a/include/sound/info.h +++ b/include/sound/info.h @@ -51,18 +51,18 @@ struct snd_info_entry_ops { unsigned short mode, void **file_private_data); int (*release)(struct snd_info_entry *entry, unsigned short mode, void *file_private_data); - long (*read)(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos); - long (*write)(struct snd_info_entry *entry, void *file_private_data, - struct file *file, const char __user *buf, - unsigned long count, unsigned long pos); - long long (*llseek)(struct snd_info_entry *entry, - void *file_private_data, struct file *file, - long long offset, int orig); - unsigned int(*poll)(struct snd_info_entry *entry, - void *file_private_data, struct file *file, - poll_table *wait); + ssize_t (*read)(struct snd_info_entry *entry, void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos); + ssize_t (*write)(struct snd_info_entry *entry, void *file_private_data, + struct file *file, const char __user *buf, + size_t count, loff_t pos); + loff_t (*llseek)(struct snd_info_entry *entry, + void *file_private_data, struct file *file, + loff_t offset, int orig); + unsigned int (*poll)(struct snd_info_entry *entry, + void *file_private_data, struct file *file, + poll_table *wait); int (*ioctl)(struct snd_info_entry *entry, void *file_private_data, struct file *file, unsigned int cmd, unsigned long arg); int (*mmap)(struct snd_info_entry *entry, void *file_private_data, diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c index 1679300b758..eb72814dfd5 100644 --- a/sound/drivers/opl4/opl4_proc.c +++ b/sound/drivers/opl4/opl4_proc.c @@ -49,9 +49,10 @@ static int snd_opl4_mem_proc_release(struct snd_info_entry *entry, return 0; } -static long snd_opl4_mem_proc_read(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *_buf, - unsigned long count, unsigned long pos) +static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *_buf, + size_t count, loff_t pos) { struct snd_opl4 *opl4 = entry->private_data; long size; @@ -75,9 +76,11 @@ static long snd_opl4_mem_proc_read(struct snd_info_entry *entry, void *file_priv return 0; } -static long snd_opl4_mem_proc_write(struct snd_info_entry *entry, void *file_private_data, - struct file *file, const char __user *_buf, - unsigned long count, unsigned long pos) +static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + const char __user *_buf, + size_t count, size_t pos) { struct snd_opl4 *opl4 = entry->private_data; long size; @@ -101,8 +104,10 @@ static long snd_opl4_mem_proc_write(struct snd_info_entry *entry, void *file_pri return 0; } -static long long snd_opl4_mem_proc_llseek(struct snd_info_entry *entry, void *file_private_data, - struct file *file, long long offset, int orig) +static loff_t snd_opl4_mem_proc_llseek(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + loff_t offset, int orig) { switch (orig) { case SEEK_SET: diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c index 2803e227aec..b2d2dba6c86 100644 --- a/sound/isa/gus/gus_mem_proc.c +++ b/sound/isa/gus/gus_mem_proc.c @@ -31,9 +31,10 @@ struct gus_proc_private { struct snd_gus_card * gus; }; -static long snd_gf1_mem_proc_dump(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { long size; struct gus_proc_private *priv = entry->private_data; @@ -51,11 +52,10 @@ static long snd_gf1_mem_proc_dump(struct snd_info_entry *entry, void *file_priva return 0; } -static long long snd_gf1_mem_proc_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - long long offset, - int orig) +static loff_t snd_gf1_mem_proc_llseek(struct snd_info_entry *entry, + void *private_file_data, + struct file *file, + loff_t offset, int orig) { struct gus_proc_private *priv = entry->private_data; diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index 9edc65059e3..b0bba2e86b1 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1139,10 +1139,10 @@ static void snd_cs4281_proc_read(struct snd_info_entry *entry, snd_iprintf(buffer, "Spurious end IRQs : %u\n", chip->spurious_dtc_irq); } -static long snd_cs4281_BA0_read(struct snd_info_entry *entry, - void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_cs4281_BA0_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { long size; struct cs4281 *chip = entry->private_data; @@ -1157,10 +1157,10 @@ static long snd_cs4281_BA0_read(struct snd_info_entry *entry, return size; } -static long snd_cs4281_BA1_read(struct snd_info_entry *entry, - void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { long size; struct cs4281 *chip = entry->private_data; diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 3f99a5e8528..08117b14238 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -2657,9 +2657,10 @@ static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { } * proc interface */ -static long snd_cs46xx_io_read(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { long size; struct snd_cs46xx_region *region = entry->private_data; diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index baa7cd508cd..347b2415db5 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -341,10 +341,10 @@ static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, #define TOTAL_SIZE_CODE (0x200*8) #define A_TOTAL_SIZE_CODE (0x400*8) -static long snd_emu10k1_fx8010_read(struct snd_info_entry *entry, - void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { long size; struct snd_emu10k1 *emu = entry->private_data; diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index 3be8f97c8bc..b5df78bcc25 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -1102,11 +1102,10 @@ static int snd_mixart_free(struct mixart_mgr *mgr) /* * proc interface */ -static long long snd_mixart_BA0_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - long long offset, - int orig) +static loff_t snd_mixart_BA0_llseek(struct snd_info_entry *entry, + void *private_file_data, + struct file *file, + loff_t offset, int orig) { offset = offset & ~3; /* 4 bytes aligned */ @@ -1128,11 +1127,10 @@ static long long snd_mixart_BA0_llseek(struct snd_info_entry *entry, return file->f_pos; } -static long long snd_mixart_BA1_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - long long offset, - int orig) +static loff_t snd_mixart_BA1_llseek(struct snd_info_entry *entry, + void *private_file_data, + struct file *file, + loff_t offset, int orig) { offset = offset & ~3; /* 4 bytes aligned */ @@ -1157,9 +1155,10 @@ static long long snd_mixart_BA1_llseek(struct snd_info_entry *entry, /* mixart_BA0 proc interface for BAR 0 - read callback */ -static long snd_mixart_BA0_read(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { struct mixart_mgr *mgr = entry->private_data; unsigned long maxsize; @@ -1178,9 +1177,10 @@ static long snd_mixart_BA0_read(struct snd_info_entry *entry, void *file_private /* mixart_BA1 proc interface for BAR 1 - read callback */ -static long snd_mixart_BA1_read(struct snd_info_entry *entry, void *file_private_data, - struct file *file, char __user *buf, - unsigned long count, unsigned long pos) +static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) { struct mixart_mgr *mgr = entry->private_data; unsigned long maxsize; -- cgit v1.2.3-18-g5258 From d97e1b78239c7e7e441088e0b644bd3b076002e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Apr 2010 11:33:54 +0200 Subject: ALSA: info - Check file position validity in common layer Check the validity of the file position in the common info layer before calling read or write callbacks in assumption that entry->size is set up properly to indicate the max file size. Removed the redundant checks from the callbacks as well. Signed-off-by: Takashi Iwai --- sound/core/info.c | 14 ++++++++++--- sound/drivers/opl4/opl4_proc.c | 46 +++++++++++++++--------------------------- sound/isa/gus/gus_mem_proc.c | 14 ++++--------- sound/pci/cs4281.c | 24 ++++++---------------- sound/pci/cs46xx/cs46xx_lib.c | 12 +++-------- sound/pci/emu10k1/emuproc.c | 43 ++++++++++++++++++--------------------- sound/pci/mixart/mixart.c | 12 ----------- 7 files changed, 60 insertions(+), 105 deletions(-) diff --git a/sound/core/info.c b/sound/core/info.c index ff968be8167..f90a6fd43fb 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -232,10 +232,15 @@ static ssize_t snd_info_entry_read(struct file *file, char __user *buffer, return -EFAULT; break; case SNDRV_INFO_CONTENT_DATA: - if (entry->c.ops->read) + if (pos >= entry->size) + return 0; + if (entry->c.ops->read) { + size = entry->size - pos; + size = min(count, size); size = entry->c.ops->read(entry, data->file_private_data, - file, buffer, count, pos); + file, buffer, size, pos); + } break; } if ((ssize_t) size > 0) @@ -282,10 +287,13 @@ static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer size = count; break; case SNDRV_INFO_CONTENT_DATA: - if (entry->c.ops->write) + if (entry->c.ops->write && count > 0) { + size_t maxsize = entry->size - pos; + count = min(count, maxsize); size = entry->c.ops->write(entry, data->file_private_data, file, buffer, count, pos); + } break; } if ((ssize_t) size > 0) diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c index eb72814dfd5..210b89de06d 100644 --- a/sound/drivers/opl4/opl4_proc.c +++ b/sound/drivers/opl4/opl4_proc.c @@ -55,25 +55,18 @@ static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry, size_t count, loff_t pos) { struct snd_opl4 *opl4 = entry->private_data; - long size; char* buf; - size = count; - if (pos + size > entry->size) - size = entry->size - pos; - if (size > 0) { - buf = vmalloc(size); - if (!buf) - return -ENOMEM; - snd_opl4_read_memory(opl4, buf, pos, size); - if (copy_to_user(_buf, buf, size)) { - vfree(buf); - return -EFAULT; - } + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + snd_opl4_read_memory(opl4, buf, pos, count); + if (copy_to_user(_buf, buf, count)) { vfree(buf); - return size; + return -EFAULT; } - return 0; + vfree(buf); + return count; } static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, @@ -83,25 +76,18 @@ static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, size_t count, size_t pos) { struct snd_opl4 *opl4 = entry->private_data; - long size; char *buf; - size = count; - if (pos + size > entry->size) - size = entry->size - pos; - if (size > 0) { - buf = vmalloc(size); - if (!buf) - return -ENOMEM; - if (copy_from_user(buf, _buf, size)) { - vfree(buf); - return -EFAULT; - } - snd_opl4_write_memory(opl4, buf, pos, size); + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + if (copy_from_user(buf, _buf, count)) { vfree(buf); - return size; + return -EFAULT; } - return 0; + snd_opl4_write_memory(opl4, buf, pos, count); + vfree(buf); + return count; } static loff_t snd_opl4_mem_proc_llseek(struct snd_info_entry *entry, diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c index b2d2dba6c86..faa2bec8f6b 100644 --- a/sound/isa/gus/gus_mem_proc.c +++ b/sound/isa/gus/gus_mem_proc.c @@ -36,20 +36,14 @@ static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry, struct file *file, char __user *buf, size_t count, loff_t pos) { - long size; struct gus_proc_private *priv = entry->private_data; struct snd_gus_card *gus = priv->gus; int err; - size = count; - if (pos + size > priv->size) - size = (long)priv->size - pos; - if (size > 0) { - if ((err = snd_gus_dram_read(gus, buf, pos, size, priv->rom)) < 0) - return err; - return size; - } - return 0; + err = snd_gus_dram_read(gus, buf, pos, count, priv->rom); + if (err < 0) + return err; + return count; } static loff_t snd_gf1_mem_proc_llseek(struct snd_info_entry *entry, diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index b0bba2e86b1..6772070ed49 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1144,17 +1144,11 @@ static ssize_t snd_cs4281_BA0_read(struct snd_info_entry *entry, struct file *file, char __user *buf, size_t count, loff_t pos) { - long size; struct cs4281 *chip = entry->private_data; - size = count; - if (pos + size > CS4281_BA0_SIZE) - size = (long)CS4281_BA0_SIZE - pos; - if (size > 0) { - if (copy_to_user_fromio(buf, chip->ba0 + pos, size)) - return -EFAULT; - } - return size; + if (copy_to_user_fromio(buf, chip->ba0 + pos, count)) + return -EFAULT; + return count; } static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry, @@ -1162,17 +1156,11 @@ static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry, struct file *file, char __user *buf, size_t count, loff_t pos) { - long size; struct cs4281 *chip = entry->private_data; - size = count; - if (pos + size > CS4281_BA1_SIZE) - size = (long)CS4281_BA1_SIZE - pos; - if (size > 0) { - if (copy_to_user_fromio(buf, chip->ba1 + pos, size)) - return -EFAULT; - } - return size; + if (copy_to_user_fromio(buf, chip->ba1 + pos, count)) + return -EFAULT; + return count; } static struct snd_info_entry_ops snd_cs4281_proc_ops_BA0 = { diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 08117b14238..aad37082cb6 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -2662,17 +2662,11 @@ static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry, struct file *file, char __user *buf, size_t count, loff_t pos) { - long size; struct snd_cs46xx_region *region = entry->private_data; - size = count; - if (pos + (size_t)size > region->size) - size = region->size - pos; - if (size > 0) { - if (copy_to_user_fromio(buf, region->remap_addr + pos, size)) - return -EFAULT; - } - return size; + if (copy_to_user_fromio(buf, region->remap_addr + pos, count)) + return -EFAULT; + return count; } static struct snd_info_entry_ops snd_cs46xx_proc_io_ops = { diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 347b2415db5..bc38dd4d071 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -346,10 +346,12 @@ static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry, struct file *file, char __user *buf, size_t count, loff_t pos) { - long size; struct snd_emu10k1 *emu = entry->private_data; unsigned int offset; int tram_addr = 0; + unsigned int *tmp; + long res; + unsigned int idx; if (!strcmp(entry->name, "fx8010_tram_addr")) { offset = TANKMEMADDRREGBASE; @@ -361,30 +363,25 @@ static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry, } else { offset = emu->audigy ? A_FXGPREGBASE : FXGPREGBASE; } - size = count; - if (pos + size > entry->size) - size = (long)entry->size - pos; - if (size > 0) { - unsigned int *tmp; - long res; - unsigned int idx; - if ((tmp = kmalloc(size + 8, GFP_KERNEL)) == NULL) - return -ENOMEM; - for (idx = 0; idx < ((pos & 3) + size + 3) >> 2; idx++) - if (tram_addr && emu->audigy) { - tmp[idx] = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0) >> 11; - tmp[idx] |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20; - } else - tmp[idx] = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0); - if (copy_to_user(buf, ((char *)tmp) + (pos & 3), size)) - res = -EFAULT; - else { - res = size; + + tmp = kmalloc(count + 8, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + for (idx = 0; idx < ((pos & 3) + count + 3) >> 2; idx++) { + unsigned int val; + val = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0); + if (tram_addr && emu->audigy) { + val >>= 11; + val |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20; } - kfree(tmp); - return res; + tmp[idx] = val; } - return 0; + if (copy_to_user(buf, ((char *)tmp) + (pos & 3), count)) + res = -EFAULT; + else + res = count; + kfree(tmp); + return res; } static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index b5df78bcc25..be95e005c81 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -1161,13 +1161,7 @@ static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry, size_t count, loff_t pos) { struct mixart_mgr *mgr = entry->private_data; - unsigned long maxsize; - if (pos >= MIXART_BA0_SIZE) - return 0; - maxsize = MIXART_BA0_SIZE - pos; - if (count > maxsize) - count = maxsize; count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ if (copy_to_user_fromio(buf, MIXART_MEM(mgr, pos), count)) return -EFAULT; @@ -1183,13 +1177,7 @@ static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry, size_t count, loff_t pos) { struct mixart_mgr *mgr = entry->private_data; - unsigned long maxsize; - if (pos > MIXART_BA1_SIZE) - return 0; - maxsize = MIXART_BA1_SIZE - pos; - if (count > maxsize) - count = maxsize; count = count & ~3; /* make sure the read size is a multiple of 4 bytes */ if (copy_to_user_fromio(buf, MIXART_REG(mgr, pos), count)) return -EFAULT; -- cgit v1.2.3-18-g5258 From 73029e0ff18dfac8a1aab1dc188e1e150bbe3adc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Apr 2010 11:39:47 +0200 Subject: ALSA: info - Implement common llseek for binary mode The llseek implementation is identical for existing driver implementations, so let's merge to the common layer. The same code for the text proc file can be used even for the binary proc file. The driver can provide its own llseek method if needed. Then the common code will be skipped. Signed-off-by: Takashi Iwai --- sound/core/info.c | 56 ++++++++++++++++++++++-------------------- sound/drivers/opl4/opl4_proc.c | 24 ------------------ sound/isa/gus/gus_mem_proc.c | 26 -------------------- sound/pci/mixart/mixart.c | 51 -------------------------------------- 4 files changed, 30 insertions(+), 127 deletions(-) diff --git a/sound/core/info.c b/sound/core/info.c index f90a6fd43fb..b70564ed8b3 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -164,39 +164,43 @@ static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) { struct snd_info_private_data *data; struct snd_info_entry *entry; - loff_t ret; + loff_t ret = -EINVAL, size; data = file->private_data; entry = data->entry; mutex_lock(&entry->access); - switch (entry->content) { - case SNDRV_INFO_CONTENT_TEXT: - switch (orig) { - case SEEK_SET: - file->f_pos = offset; - ret = file->f_pos; - goto out; - case SEEK_CUR: - file->f_pos += offset; - ret = file->f_pos; - goto out; - case SEEK_END: - default: - ret = -EINVAL; - goto out; - } + if (entry->content == SNDRV_INFO_CONTENT_DATA && + entry->c.ops->llseek) { + offset = entry->c.ops->llseek(entry, + data->file_private_data, + file, offset, orig); + goto out; + } + if (entry->content == SNDRV_INFO_CONTENT_DATA) + size = entry->size; + else + size = 0; + switch (orig) { + case SEEK_SET: break; - case SNDRV_INFO_CONTENT_DATA: - if (entry->c.ops->llseek) { - ret = entry->c.ops->llseek(entry, - data->file_private_data, - file, offset, orig); + case SEEK_CUR: + offset += file->f_pos; + break; + case SEEK_END: + if (!size) goto out; - } + offset += size; break; - } - ret = -ENXIO; -out: + default: + goto out; + } + if (offset < 0) + goto out; + if (size && offset > size) + offset = size; + file->f_pos = offset; + ret = offset; + out: mutex_unlock(&entry->access); return ret; } diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c index 210b89de06d..c5c13c4c260 100644 --- a/sound/drivers/opl4/opl4_proc.c +++ b/sound/drivers/opl4/opl4_proc.c @@ -90,35 +90,11 @@ static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, return count; } -static loff_t snd_opl4_mem_proc_llseek(struct snd_info_entry *entry, - void *file_private_data, - struct file *file, - loff_t offset, int orig) -{ - switch (orig) { - case SEEK_SET: - file->f_pos = offset; - break; - case SEEK_CUR: - file->f_pos += offset; - break; - case SEEK_END: /* offset is negative */ - file->f_pos = entry->size + offset; - break; - default: - return -EINVAL; - } - if (file->f_pos > entry->size) - file->f_pos = entry->size; - return file->f_pos; -} - static struct snd_info_entry_ops snd_opl4_mem_proc_ops = { .open = snd_opl4_mem_proc_open, .release = snd_opl4_mem_proc_release, .read = snd_opl4_mem_proc_read, .write = snd_opl4_mem_proc_write, - .llseek = snd_opl4_mem_proc_llseek, }; int snd_opl4_create_proc(struct snd_opl4 *opl4) diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c index faa2bec8f6b..2ccb3fadd7b 100644 --- a/sound/isa/gus/gus_mem_proc.c +++ b/sound/isa/gus/gus_mem_proc.c @@ -46,31 +46,6 @@ static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry, return count; } -static loff_t snd_gf1_mem_proc_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - loff_t offset, int orig) -{ - struct gus_proc_private *priv = entry->private_data; - - switch (orig) { - case SEEK_SET: - file->f_pos = offset; - break; - case SEEK_CUR: - file->f_pos += offset; - break; - case SEEK_END: /* offset is negative */ - file->f_pos = priv->size + offset; - break; - default: - return -EINVAL; - } - if (file->f_pos > priv->size) - file->f_pos = priv->size; - return file->f_pos; -} - static void snd_gf1_mem_proc_free(struct snd_info_entry *entry) { struct gus_proc_private *priv = entry->private_data; @@ -79,7 +54,6 @@ static void snd_gf1_mem_proc_free(struct snd_info_entry *entry) static struct snd_info_entry_ops snd_gf1_mem_proc_ops = { .read = snd_gf1_mem_proc_dump, - .llseek = snd_gf1_mem_proc_llseek, }; int snd_gf1_mem_proc_init(struct snd_gus_card * gus) diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index be95e005c81..6c3fd4d1c49 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -1102,55 +1102,6 @@ static int snd_mixart_free(struct mixart_mgr *mgr) /* * proc interface */ -static loff_t snd_mixart_BA0_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - loff_t offset, int orig) -{ - offset = offset & ~3; /* 4 bytes aligned */ - - switch(orig) { - case SEEK_SET: - file->f_pos = offset; - break; - case SEEK_CUR: - file->f_pos += offset; - break; - case SEEK_END: /* offset is negative */ - file->f_pos = MIXART_BA0_SIZE + offset; - break; - default: - return -EINVAL; - } - if(file->f_pos > MIXART_BA0_SIZE) - file->f_pos = MIXART_BA0_SIZE; - return file->f_pos; -} - -static loff_t snd_mixart_BA1_llseek(struct snd_info_entry *entry, - void *private_file_data, - struct file *file, - loff_t offset, int orig) -{ - offset = offset & ~3; /* 4 bytes aligned */ - - switch(orig) { - case SEEK_SET: - file->f_pos = offset; - break; - case SEEK_CUR: - file->f_pos += offset; - break; - case SEEK_END: /* offset is negative */ - file->f_pos = MIXART_BA1_SIZE + offset; - break; - default: - return -EINVAL; - } - if(file->f_pos > MIXART_BA1_SIZE) - file->f_pos = MIXART_BA1_SIZE; - return file->f_pos; -} /* mixart_BA0 proc interface for BAR 0 - read callback @@ -1186,12 +1137,10 @@ static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry, static struct snd_info_entry_ops snd_mixart_proc_ops_BA0 = { .read = snd_mixart_BA0_read, - .llseek = snd_mixart_BA0_llseek }; static struct snd_info_entry_ops snd_mixart_proc_ops_BA1 = { .read = snd_mixart_BA1_read, - .llseek = snd_mixart_BA1_llseek }; -- cgit v1.2.3-18-g5258 From 02f4865fa415a87de28cc8c2e4d798ff46be1cf8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Apr 2010 11:49:04 +0200 Subject: ALSA: core - Define llseek fops Set no_llseek to llseek file ops of each sound component (but for hwdep). This avoids the implicit BKL invocation via generic_file_llseek() used as default when fops.llseek is NULL. Also call nonseekable_open() at each open ops to ensure the file flags have no seek bit. Signed-off-by: Takashi Iwai --- sound/core/control.c | 5 +++++ sound/core/oss/mixer_oss.c | 5 +++++ sound/core/oss/pcm_oss.c | 5 +++++ sound/core/pcm_native.c | 10 ++++++++-- sound/core/rawmidi.c | 5 +++++ sound/core/seq/seq_clientmgr.c | 6 ++++++ sound/core/timer.c | 6 ++++++ 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/sound/core/control.c b/sound/core/control.c index 439ce64f9d8..070aab49019 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -50,6 +50,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) struct snd_ctl_file *ctl; int err; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); if (!card) { err = -ENODEV; @@ -1388,6 +1392,7 @@ static const struct file_operations snd_ctl_f_ops = .read = snd_ctl_read, .open = snd_ctl_open, .release = snd_ctl_release, + .llseek = no_llseek, .poll = snd_ctl_poll, .unlocked_ioctl = snd_ctl_ioctl, .compat_ioctl = snd_ctl_ioctl_compat, diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index 54e2eb56e4c..f50ebf20df9 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -43,6 +43,10 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) struct snd_mixer_oss_file *fmixer; int err; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + card = snd_lookup_oss_minor_data(iminor(inode), SNDRV_OSS_DEVICE_TYPE_MIXER); if (card == NULL) @@ -397,6 +401,7 @@ static const struct file_operations snd_mixer_oss_f_ops = .owner = THIS_MODULE, .open = snd_mixer_oss_open, .release = snd_mixer_oss_release, + .llseek = no_llseek, .unlocked_ioctl = snd_mixer_oss_ioctl, .compat_ioctl = snd_mixer_oss_ioctl_compat, }; diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index 82d4e3329b3..5c8c7dff8ed 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -2379,6 +2379,10 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) int nonblock; wait_queue_t wait; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + pcm = snd_lookup_oss_minor_data(iminor(inode), SNDRV_OSS_DEVICE_TYPE_PCM); if (pcm == NULL) { @@ -2977,6 +2981,7 @@ static const struct file_operations snd_pcm_oss_f_reg = .write = snd_pcm_oss_write, .open = snd_pcm_oss_open, .release = snd_pcm_oss_release, + .llseek = no_llseek, .poll = snd_pcm_oss_poll, .unlocked_ioctl = snd_pcm_oss_ioctl, .compat_ioctl = snd_pcm_oss_ioctl_compat, diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index cadba308776..5b22443ed60 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2107,7 +2107,9 @@ static int snd_pcm_open_file(struct file *file, static int snd_pcm_playback_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - + int err = nonseekable_open(inode, file); + if (err < 0) + return err; pcm = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_PCM_PLAYBACK); return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); @@ -2116,7 +2118,9 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) static int snd_pcm_capture_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - + int err = nonseekable_open(inode, file); + if (err < 0) + return err; pcm = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_PCM_CAPTURE); return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); @@ -3450,6 +3454,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .aio_write = snd_pcm_aio_write, .open = snd_pcm_playback_open, .release = snd_pcm_release, + .llseek = no_llseek, .poll = snd_pcm_playback_poll, .unlocked_ioctl = snd_pcm_playback_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, @@ -3463,6 +3468,7 @@ const struct file_operations snd_pcm_f_ops[2] = { .aio_read = snd_pcm_aio_read, .open = snd_pcm_capture_open, .release = snd_pcm_release, + .llseek = no_llseek, .poll = snd_pcm_capture_poll, .unlocked_ioctl = snd_pcm_capture_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 0f5a194695d..eb68326c37d 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -376,6 +376,10 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) return -EINVAL; /* invalid combination */ + err = nonseekable_open(inode, file); + if (err < 0) + return err; + if (maj == snd_major) { rmidi = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_RAWMIDI); @@ -1391,6 +1395,7 @@ static const struct file_operations snd_rawmidi_f_ops = .write = snd_rawmidi_write, .open = snd_rawmidi_open, .release = snd_rawmidi_release, + .llseek = no_llseek, .poll = snd_rawmidi_poll, .unlocked_ioctl = snd_rawmidi_ioctl, .compat_ioctl = snd_rawmidi_ioctl_compat, diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 48eca9ff9ee..99a485f1364 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -318,6 +318,11 @@ static int snd_seq_open(struct inode *inode, struct file *file) int c, mode; /* client id */ struct snd_seq_client *client; struct snd_seq_user_client *user; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; if (mutex_lock_interruptible(®ister_mutex)) return -ERESTARTSYS; @@ -2550,6 +2555,7 @@ static const struct file_operations snd_seq_f_ops = .write = snd_seq_write, .open = snd_seq_open, .release = snd_seq_release, + .llseek = no_llseek, .poll = snd_seq_poll, .unlocked_ioctl = snd_seq_ioctl, .compat_ioctl = snd_seq_ioctl_compat, diff --git a/sound/core/timer.c b/sound/core/timer.c index 73943651cae..8c9a661df05 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1237,6 +1237,11 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, static int snd_timer_user_open(struct inode *inode, struct file *file) { struct snd_timer_user *tu; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; tu = kzalloc(sizeof(*tu), GFP_KERNEL); if (tu == NULL) @@ -1921,6 +1926,7 @@ static const struct file_operations snd_timer_f_ops = .read = snd_timer_user_read, .open = snd_timer_user_open, .release = snd_timer_user_release, + .llseek = no_llseek, .poll = snd_timer_user_poll, .unlocked_ioctl = snd_timer_user_ioctl, .compat_ioctl = snd_timer_user_ioctl_compat, -- cgit v1.2.3-18-g5258 From c56a3b18475b0f93b484c25162c1379f5aee367a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 13 Apr 2010 12:55:09 +0200 Subject: ALSA: Update the documentation for changes of proc files Signed-off-by: Takashi Iwai --- Documentation/DocBook/writing-an-alsa-driver.tmpl | 27 ++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Documentation/DocBook/writing-an-alsa-driver.tmpl b/Documentation/DocBook/writing-an-alsa-driver.tmpl index 0d0f7b4d4b1..0ba149de260 100644 --- a/Documentation/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/DocBook/writing-an-alsa-driver.tmpl @@ -5518,34 +5518,41 @@ struct _snd_pcm_runtime { ]]> + + For the raw data, size field must be + set properly. This specifies the maximum size of the proc file access. - The callback is much more complicated than the text-file - version. You need to use a low-level I/O functions such as + The read/write callbacks of raw mode are more direct than the text mode. + You need to use a low-level I/O functions such as copy_from/to_user() to transfer the data. local_max_size) - size = local_max_size - pos; - if (copy_to_user(buf, local_data + pos, size)) + if (copy_to_user(buf, local_data + pos, count)) return -EFAULT; - return size; + return count; } ]]> + + If the size of the info entry has been set up properly, + count and pos are + guaranteed to fit within 0 and the given size. + You don't have to check the range in the callbacks unless any + other condition is required. + -- cgit v1.2.3-18-g5258 From 7b2bfdbc0dee5a321b5c02febe157adebd33ab3a Mon Sep 17 00:00:00 2001 From: Jens Taprogge Date: Wed, 14 Apr 2010 23:42:04 +0200 Subject: ALSA: hda - Add initial support for Thinkpad T410s HDA codec attached please find a patch that adds support for at least the T410s HDA codec. Most likely it will also add support for the T410 and T510 based models. The patch was derived from Ideapad support. Support for the laptop's and docking-station output connectors as well as the docking-station microphone connector and the laptops internal devices has been tested. Since it has been developed without a data-sheet available, support for digital outputs and the laptop's microphone input may well be incorrect. Microphone mute functionality is not included: The microphone mute button seems to be reported through thinkpad_acpi key 0000101b. The mute button LED seems to be wired to thinkpad_acpi led number 15. Signed-off-by: Jens Taprogge Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_conexant.c | 153 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 4 deletions(-) diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 61682e1d09d..509a1049ff4 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -115,6 +115,7 @@ struct conexant_spec { unsigned int port_d_mode; unsigned int dell_vostro:1; unsigned int ideapad:1; + unsigned int thinkpad:1; unsigned int ext_mic_present; unsigned int recording; @@ -2031,6 +2032,9 @@ static void cxt5066_update_speaker(struct hda_codec *codec) /* Port D (HP/LO) */ pinctl = ((spec->hp_present & 2) && spec->cur_eapd) ? spec->port_d_mode : 0; + /* Mute if Port A is connected on Thinkpad */ + if (spec->thinkpad && (spec->hp_present & 1)) + pinctl = 0; snd_hda_codec_write(codec, 0x1c, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl); @@ -2211,6 +2215,50 @@ static void cxt5066_ideapad_automic(struct hda_codec *codec) } } +/* toggle input of built-in digital mic and mic jack appropriately + order is: external mic -> dock mic -> interal mic */ +static void cxt5066_thinkpad_automic(struct hda_codec *codec) +{ + unsigned int ext_present, dock_present; + + static struct hda_verb ext_mic_present[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0}, + {0x17, AC_VERB_SET_CONNECT_SEL, 1}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {} + }; + static struct hda_verb dock_mic_present[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {} + }; + static struct hda_verb ext_mic_absent[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 2}, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {} + }; + + ext_present = snd_hda_jack_detect(codec, 0x1b); + dock_present = snd_hda_jack_detect(codec, 0x1a); + if (ext_present) { + snd_printdd("CXT5066: external microphone detected\n"); + snd_hda_sequence_write(codec, ext_mic_present); + } else if (dock_present) { + snd_printdd("CXT5066: dock microphone detected\n"); + snd_hda_sequence_write(codec, dock_mic_present); + } else { + snd_printdd("CXT5066: external microphone absent\n"); + snd_hda_sequence_write(codec, ext_mic_absent); + } +} + /* mute internal speaker if HP is plugged */ static void cxt5066_hp_automute(struct hda_codec *codec) { @@ -2223,7 +2271,8 @@ static void cxt5066_hp_automute(struct hda_codec *codec) /* Port D */ portD = snd_hda_jack_detect(codec, 0x1c); - spec->hp_present = !!(portA | portD); + spec->hp_present = !!(portA); + spec->hp_present |= portD ? 2 : 0; snd_printdd("CXT5066: hp automute portA=%x portD=%x present=%d\n", portA, portD, spec->hp_present); cxt5066_update_speaker(codec); @@ -2274,6 +2323,20 @@ static void cxt5066_ideapad_event(struct hda_codec *codec, unsigned int res) } } +/* unsolicited event for jack sensing */ +static void cxt5066_thinkpad_event(struct hda_codec *codec, unsigned int res) +{ + snd_printdd("CXT5066_thinkpad: unsol event %x (%x)\n", res, res >> 26); + switch (res >> 26) { + case CONEXANT_HP_EVENT: + cxt5066_hp_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5066_thinkpad_automic(codec); + break; + } +} + static const struct hda_input_mux cxt5066_analog_mic_boost = { .num_items = 5, .items = { @@ -2292,7 +2355,7 @@ static void cxt5066_set_mic_boost(struct hda_codec *codec) AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT | cxt5066_analog_mic_boost.items[spec->mic_boost].index); - if (spec->ideapad) { + if (spec->ideapad || spec->thinkpad) { /* adjust the internal mic as well...it is not through 0x17 */ snd_hda_codec_write_cache(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, @@ -2780,6 +2843,64 @@ static struct hda_verb cxt5066_init_verbs_ideapad[] = { { } /* end */ }; +static struct hda_verb cxt5066_init_verbs_thinkpad[] = { + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */ + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */ + + /* Port G: internal speakers */ + {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + /* Port A: HP, Amp */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + /* Port B: Mic Dock */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + + /* Port C: Mic */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + + /* Port D: HP Dock, Amp */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + /* DAC1 */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x14, AC_VERB_SET_CONNECT_SEL, 2}, /* default to internal mic */ + + /* Audio input selector */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2}, + {0x17, AC_VERB_SET_CONNECT_SEL, 1}, /* route ext mic */ + + /* SPDIF route: PCM */ + {0x20, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x22, AC_VERB_SET_CONNECT_SEL, 0x0}, + + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* internal microphone */ + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */ + + /* EAPD */ + {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ + + /* enable unsolicited events for Port A, B, C and D */ + {0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + {0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + {0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + { } /* end */ +}; + static struct hda_verb cxt5066_init_verbs_portd_lo[] = { {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, { } /* end */ @@ -2798,6 +2919,8 @@ static int cxt5066_init(struct hda_codec *codec) cxt5066_vostro_automic(codec); else if (spec->ideapad) cxt5066_ideapad_automic(codec); + else if (spec->thinkpad) + cxt5066_thinkpad_automic(codec); } cxt5066_set_mic_boost(codec); return 0; @@ -2819,20 +2942,22 @@ static int cxt5066_olpc_init(struct hda_codec *codec) } enum { - CXT5066_LAPTOP, /* Laptops w/ EAPD support */ + CXT5066_LAPTOP, /* Laptops w/ EAPD support */ CXT5066_DELL_LAPTOP, /* Dell Laptop */ CXT5066_OLPC_XO_1_5, /* OLPC XO 1.5 */ CXT5066_DELL_VOSTO, /* Dell Vostro 1015i */ CXT5066_IDEAPAD, /* Lenovo IdeaPad U150 */ + CXT5066_THINKPAD, /* Lenovo ThinkPad T410s, others? */ CXT5066_MODELS }; static const char *cxt5066_models[CXT5066_MODELS] = { - [CXT5066_LAPTOP] = "laptop", + [CXT5066_LAPTOP] = "laptop", [CXT5066_DELL_LAPTOP] = "dell-laptop", [CXT5066_OLPC_XO_1_5] = "olpc-xo-1_5", [CXT5066_DELL_VOSTO] = "dell-vostro", [CXT5066_IDEAPAD] = "ideapad", + [CXT5066_THINKPAD] = "thinkpad", }; static struct snd_pci_quirk cxt5066_cfg_tbl[] = { @@ -2843,6 +2968,7 @@ static struct snd_pci_quirk cxt5066_cfg_tbl[] = { SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5), SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTO), SND_PCI_QUIRK(0x17aa, 0x3a0d, "ideapad", CXT5066_IDEAPAD), + SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo Thinkpad", CXT5066_THINKPAD), {} }; @@ -2947,6 +3073,22 @@ static int patch_cxt5066(struct hda_codec *codec) /* no S/PDIF out */ spec->multiout.dig_out_nid = 0; + /* input source automatically selected */ + spec->input_mux = NULL; + break; + case CXT5066_THINKPAD: + codec->patch_ops.init = cxt5066_init; + codec->patch_ops.unsol_event = cxt5066_thinkpad_event; + spec->mixers[spec->num_mixers++] = cxt5066_mixer_master; + spec->mixers[spec->num_mixers++] = cxt5066_mixers; + spec->init_verbs[0] = cxt5066_init_verbs_thinkpad; + spec->thinkpad = 1; + spec->port_d_mode = PIN_OUT; + spec->mic_boost = 2; /* default 20dB gain */ + + /* no S/PDIF out */ + spec->multiout.dig_out_nid = 0; + /* input source automatically selected */ spec->input_mux = NULL; break; @@ -2969,6 +3111,8 @@ static struct hda_codec_preset snd_hda_preset_conexant[] = { .patch = patch_cxt5066 }, { .id = 0x14f15067, .name = "CX20583 (Pebble HSF)", .patch = patch_cxt5066 }, + { .id = 0x14f15069, .name = "CX20585", + .patch = patch_cxt5066 }, {} /* terminator */ }; @@ -2977,6 +3121,7 @@ MODULE_ALIAS("snd-hda-codec-id:14f15047"); MODULE_ALIAS("snd-hda-codec-id:14f15051"); MODULE_ALIAS("snd-hda-codec-id:14f15066"); MODULE_ALIAS("snd-hda-codec-id:14f15067"); +MODULE_ALIAS("snd-hda-codec-id:14f15069"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Conexant HD-audio codec"); -- cgit v1.2.3-18-g5258 From 1cff399ecd9125d8e6a634a1957be1aeb3195a12 Mon Sep 17 00:00:00 2001 From: Brian Waters Date: Thu, 15 Apr 2010 04:03:29 -0400 Subject: ALSA: i2c: Fixed 8 checkpatch errors Fixed 8 checkpatch errors (ERROR: do not use assignment in if condition) in sound/i2c/i2c.c. Signed-off-by: Brian Waters Signed-off-by: Takashi Iwai --- sound/i2c/i2c.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sound/i2c/i2c.c b/sound/i2c/i2c.c index 5c0c77dd01c..eb7c7d05a7c 100644 --- a/sound/i2c/i2c.c +++ b/sound/i2c/i2c.c @@ -98,7 +98,8 @@ int snd_i2c_bus_create(struct snd_card *card, const char *name, bus->master = master; } strlcpy(bus->name, name, sizeof(bus->name)); - if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &ops)) < 0) { + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &ops); + if (err < 0) { snd_i2c_bus_free(bus); return err; } @@ -246,7 +247,8 @@ static int snd_i2c_bit_sendbyte(struct snd_i2c_bus *bus, unsigned char data) for (i = 7; i >= 0; i--) snd_i2c_bit_send(bus, !!(data & (1 << i))); - if ((err = snd_i2c_bit_ack(bus)) < 0) + err = snd_i2c_bit_ack(bus); + if (err < 0) return err; return 0; } @@ -278,12 +280,14 @@ static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device, if (device->flags & SND_I2C_DEVICE_ADDRTEN) return -EIO; /* not yet implemented */ snd_i2c_bit_start(bus); - if ((err = snd_i2c_bit_sendbyte(bus, device->addr << 1)) < 0) { + err = snd_i2c_bit_sendbyte(bus, device->addr << 1); + if (err < 0) { snd_i2c_bit_hw_stop(bus); return err; } while (count-- > 0) { - if ((err = snd_i2c_bit_sendbyte(bus, *bytes++)) < 0) { + err = snd_i2c_bit_sendbyte(bus, *bytes++); + if (err < 0) { snd_i2c_bit_hw_stop(bus); return err; } @@ -302,12 +306,14 @@ static int snd_i2c_bit_readbytes(struct snd_i2c_device *device, if (device->flags & SND_I2C_DEVICE_ADDRTEN) return -EIO; /* not yet implemented */ snd_i2c_bit_start(bus); - if ((err = snd_i2c_bit_sendbyte(bus, (device->addr << 1) | 1)) < 0) { + err = snd_i2c_bit_sendbyte(bus, (device->addr << 1) | 1); + if (err < 0) { snd_i2c_bit_hw_stop(bus); return err; } while (count-- > 0) { - if ((err = snd_i2c_bit_readbyte(bus, count == 0)) < 0) { + err = snd_i2c_bit_readbyte(bus, count == 0); + if (err < 0) { snd_i2c_bit_hw_stop(bus); return err; } -- cgit v1.2.3-18-g5258 From ca4c2adaf2d7efcc43e16c8010cf4c30def75058 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 16 Apr 2010 10:32:54 +0200 Subject: ALSA: usb/mixer - use get_iface_desc() rather than direct structure Signed-off-by: Jaroslav Kysela --- sound/usb/mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 1deef623c08..21613fe0c1a 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2055,7 +2055,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, } host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; - mixer->protocol = host_iface->desc.bInterfaceProtocol; + mixer->protocol = get_iface_desc(host_iface)->bInterfaceProtocol; if ((err = snd_usb_mixer_controls(mixer)) < 0 || (err = snd_usb_mixer_status_create(mixer)) < 0) -- cgit v1.2.3-18-g5258 From 0340c7dccd80d8706c636e030a6ebbddbddca690 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 16 Apr 2010 13:10:10 +0200 Subject: ALSA: Release v1.0.23 Signed-off-by: Jaroslav Kysela --- include/sound/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sound/version.h b/include/sound/version.h index 7fed23442db..bf69a5b7e65 100644 --- a/include/sound/version.h +++ b/include/sound/version.h @@ -1,3 +1,3 @@ /* include/version.h */ -#define CONFIG_SND_VERSION "1.0.22.1" +#define CONFIG_SND_VERSION "1.0.23" #define CONFIG_SND_DATE "" -- cgit v1.2.3-18-g5258 From b2c812e22de88bb79c290c0e718280f10b64a48d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 14 Apr 2010 15:35:19 +0900 Subject: ASoC: Add indirection for CODEC private data One of the features of the multi CODEC work is that it embeds a struct device in the CODEC to provide diagnostics via a sysfs class rather than via the device tree, at which point it's much better to use the struct device private data rather than having two places to store it. Provide an accessor function to allow this change to be made more easily, and update all the CODEC drivers are updated. To ensure use of the accessor the private data structure member is renamed, meaning that if code developed with older an older core that still uses private_data is merged it will fail to build. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/soc.h | 13 ++++++++++++- sound/soc/codecs/ad1836.c | 2 +- sound/soc/codecs/ad193x.c | 2 +- sound/soc/codecs/ak4104.c | 2 +- sound/soc/codecs/ak4535.c | 10 +++++----- sound/soc/codecs/ak4642.c | 2 +- sound/soc/codecs/ak4671.c | 2 +- sound/soc/codecs/cs4270.c | 20 ++++++++++---------- sound/soc/codecs/cx20442.c | 2 +- sound/soc/codecs/da7210.c | 2 +- sound/soc/codecs/ssm2602.c | 12 ++++++------ sound/soc/codecs/stac9766.c | 2 +- sound/soc/codecs/tlv320aic3x.c | 10 +++++----- sound/soc/codecs/tlv320dac33.c | 36 ++++++++++++++++++------------------ sound/soc/codecs/twl4030.c | 32 ++++++++++++++++---------------- sound/soc/codecs/uda134x.c | 16 ++++++++-------- sound/soc/codecs/uda1380.c | 4 ++-- sound/soc/codecs/wm8350.c | 28 ++++++++++++++-------------- sound/soc/codecs/wm8400.c | 16 ++++++++-------- sound/soc/codecs/wm8523.c | 10 +++++----- sound/soc/codecs/wm8580.c | 4 ++-- sound/soc/codecs/wm8711.c | 6 +++--- sound/soc/codecs/wm8731.c | 10 +++++----- sound/soc/codecs/wm8750.c | 7 +++---- sound/soc/codecs/wm8753.c | 8 ++++---- sound/soc/codecs/wm8776.c | 6 +++--- sound/soc/codecs/wm8900.c | 10 +++++----- sound/soc/codecs/wm8903.c | 20 ++++++++++---------- sound/soc/codecs/wm8904.c | 42 +++++++++++++++++++++--------------------- sound/soc/codecs/wm8940.c | 4 ++-- sound/soc/codecs/wm8955.c | 16 ++++++++-------- sound/soc/codecs/wm8960.c | 6 +++--- sound/soc/codecs/wm8961.c | 6 +++--- sound/soc/codecs/wm8971.c | 12 ++++++------ sound/soc/codecs/wm8974.c | 2 +- sound/soc/codecs/wm8978.c | 12 ++++++------ sound/soc/codecs/wm8988.c | 8 ++++---- sound/soc/codecs/wm8990.c | 8 ++++---- sound/soc/codecs/wm8993.c | 24 ++++++++++++------------ sound/soc/codecs/wm8994.c | 34 +++++++++++++++++----------------- sound/soc/codecs/wm9081.c | 18 +++++++++--------- sound/soc/codecs/wm9713.c | 13 +++++++------ sound/soc/codecs/wm_hubs.c | 6 +++--- 43 files changed, 258 insertions(+), 247 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 4ab3dad4a9c..01e9c66ddc6 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -414,7 +414,7 @@ struct snd_soc_codec { struct snd_ac97 *ac97; /* for ad-hoc ac97 devices */ unsigned int active; unsigned int pcm_devs; - void *private_data; + void *drvdata; /* codec IO */ void *control_data; /* codec control (i2c/3wire) data */ @@ -597,6 +597,17 @@ static inline unsigned int snd_soc_write(struct snd_soc_codec *codec, return codec->write(codec, reg, val); } +static inline void snd_soc_codec_set_drvdata(struct snd_soc_codec *codec, + void *data) +{ + codec->drvdata = data; +} + +static inline void *snd_soc_codec_get_drvdata(struct snd_soc_codec *codec) +{ + return codec->drvdata; +} + #include #endif diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 3c80137d593..48e1272892f 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -277,7 +277,7 @@ static int ad1836_register(struct ad1836_priv *ad1836) mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ad1836; + snd_soc_codec_set_drvdata(codec, ad1836); codec->reg_cache = ad1836->reg_cache; codec->reg_cache_size = AD1836_NUM_REGS; codec->name = "AD1836"; diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index 4bfd66bc262..7ed787e2e51 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -305,7 +305,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) mutex_init(&codec->mutex); codec->control_data = ctrl_data; codec->dev = dev; - codec->private_data = ad193x; + snd_soc_codec_set_drvdata(codec, ad193x); codec->reg_cache = ad193x->reg_cache; codec->reg_cache_size = AD193X_NUM_REGS; codec->name = "AD193X"; diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index b68d99fb6af..962d7468212 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -221,7 +221,7 @@ static int ak4104_spi_probe(struct spi_device *spi) codec->owner = THIS_MODULE; codec->dai = &ak4104_dai; codec->num_dai = 1; - codec->private_data = ak4104; + snd_soc_codec_set_drvdata(codec, ak4104); codec->control_data = spi; codec->reg_cache = ak4104->reg_cache; codec->reg_cache_size = AK4104_NUM_REGS; diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index ff966567e2b..2f6221b2ebb 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -301,7 +301,7 @@ static int ak4535_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 ak4535_priv *ak4535 = codec->private_data; + struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec); ak4535->sysclk = freq; return 0; @@ -314,7 +314,7 @@ static int ak4535_hw_params(struct snd_pcm_substream *substream, 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 ak4535_priv *ak4535 = codec->private_data; + struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec); u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); int rate = params_rate(params), fs = 256; @@ -599,7 +599,7 @@ static int ak4535_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = ak4535; + snd_soc_codec_set_drvdata(codec, ak4535); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -616,7 +616,7 @@ static int ak4535_probe(struct platform_device *pdev) #endif if (ret != 0) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } return ret; @@ -638,7 +638,7 @@ static int ak4535_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&ak4535_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index de1809dc8d9..1254a7ac561 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -431,7 +431,7 @@ static int ak4642_init(struct ak4642_priv *ak4642) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ak4642; + snd_soc_codec_set_drvdata(codec, ak4642); codec->name = "AK4642"; codec->owner = THIS_MODULE; codec->read = ak4642_read_reg_cache; diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index 82fca284d00..18fd4475413 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -701,7 +701,7 @@ static int ak4671_register(struct ak4671_priv *ak4671, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ak4671; + snd_soc_codec_set_drvdata(codec, ak4671); codec->name = "AK4671"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index dfbeb2db61b..a7f09f33c56 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -210,7 +210,7 @@ static int cs4270_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 cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); unsigned int rates = 0; unsigned int rate_min = -1; unsigned int rate_max = 0; @@ -269,7 +269,7 @@ static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int format) { struct snd_soc_codec *codec = codec_dai->codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int ret = 0; /* set DAI format */ @@ -411,7 +411,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, 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 cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int ret; unsigned int i; unsigned int rate; @@ -490,7 +490,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int reg6; reg6 = snd_soc_read(codec, CS4270_MUTE); @@ -523,7 +523,7 @@ static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int left = !ucontrol->value.integer.value[0]; int right = !ucontrol->value.integer.value[1]; @@ -599,7 +599,7 @@ static int cs4270_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int i, ret; /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ @@ -656,7 +656,7 @@ static int cs4270_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); snd_soc_free_pcms(socdev); regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies); @@ -729,7 +729,7 @@ static int cs4270_i2c_probe(struct i2c_client *i2c_client, codec->owner = THIS_MODULE; codec->dai = &cs4270_dai; codec->num_dai = 1; - codec->private_data = cs4270; + snd_soc_codec_set_drvdata(codec, cs4270); codec->control_data = i2c_client; codec->read = cs4270_read_reg_cache; codec->write = cs4270_i2c_write; @@ -842,7 +842,7 @@ MODULE_DEVICE_TABLE(i2c, cs4270_id); static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg) { struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int reg, ret; reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; @@ -862,7 +862,7 @@ static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg) static int cs4270_soc_resume(struct platform_device *pdev) { struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c_client = codec->control_data; int reg; diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index e000cdfec1e..203e51f9148 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -386,7 +386,7 @@ static int cx20442_register(struct cx20442_priv *cx20442) codec->name = "CX20442"; codec->owner = THIS_MODULE; - codec->private_data = cx20442; + snd_soc_codec_set_drvdata(codec, cx20442); codec->dai = &cx20442_dai; codec->num_dai = 1; diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 35b88163865..89cea32f942 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -460,7 +460,7 @@ static int da7210_init(struct da7210_priv *da7210) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = da7210; + snd_soc_codec_set_drvdata(codec, da7210); codec->name = "DA7210"; codec->owner = THIS_MODULE; codec->read = da7210_read; diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 942f5dc3080..99ddec7df61 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -277,7 +277,7 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, 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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; int i = get_coeff(ssm2602->sysclk, params_rate(params)); @@ -322,7 +322,7 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, 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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -373,7 +373,7 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, 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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); /* deactivate */ if (!codec->active) @@ -401,7 +401,7 @@ static int ssm2602_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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: case 12000000: @@ -726,7 +726,7 @@ static int ssm2602_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = ssm2602; + snd_soc_codec_set_drvdata(codec, ssm2602); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -759,7 +759,7 @@ static int ssm2602_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&ssm2602_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index 81b8c9dfe7f..a1c7ca869d0 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -409,7 +409,7 @@ reset_err: pcm_err: snd_soc_free_ac97_codec(codec); codec_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); cache_err: kfree(socdev->card->codec); socdev->card->codec = NULL; diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index e4b946a19ea..556123b4059 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -763,7 +763,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, 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 aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; u16 d, pll_d = 1; @@ -930,7 +930,7 @@ static int aic3x_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 aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); aic3x->sysclk = freq; return 0; @@ -940,7 +940,7 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); u8 iface_areg, iface_breg; int delay = 0; @@ -994,7 +994,7 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, static int aic3x_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); u8 reg; switch (level) { @@ -1338,7 +1338,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, codec = &aic3x->codec; codec->dev = &i2c->dev; - codec->private_data = aic3x; + snd_soc_codec_set_drvdata(codec, aic3x); codec->control_data = i2c; codec->hw_write = (hw_write_t) i2c_master_send; diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index ee2e1e36d99..824bb354ebc 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -167,7 +167,7 @@ static inline void dac33_write_reg_cache(struct snd_soc_codec *codec, static int dac33_read(struct snd_soc_codec *codec, unsigned int reg, u8 *value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int val; *value = reg & 0xff; @@ -192,7 +192,7 @@ static int dac33_read(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 data[2]; int ret = 0; @@ -219,7 +219,7 @@ static int dac33_write(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret; mutex_lock(&dac33->mutex); @@ -233,7 +233,7 @@ static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 data[3]; int ret = 0; @@ -265,7 +265,7 @@ static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, static void dac33_restore_regs(struct snd_soc_codec *codec) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 *cache = codec->reg_cache; u8 data[2]; int i, ret; @@ -319,7 +319,7 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) static int dac33_hard_power(struct snd_soc_codec *codec, int power) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret; mutex_lock(&dac33->mutex); @@ -366,7 +366,7 @@ static int dac33_get_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = dac33->nsample; @@ -377,7 +377,7 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; if (dac33->nsample == ucontrol->value.integer.value[0]) @@ -396,7 +396,7 @@ static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = dac33->fifo_mode; @@ -407,7 +407,7 @@ static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; if (dac33->fifo_mode == ucontrol->value.integer.value[0]) @@ -630,7 +630,7 @@ static void dac33_work(struct work_struct *work) static irqreturn_t dac33_interrupt_handler(int irq, void *dev) { struct snd_soc_codec *codec = dev; - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); queue_work(dac33->dac33_wq, &dac33->work); @@ -697,7 +697,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) 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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int oscset, ratioset, pwr_ctrl, reg_tmp; u8 aictrl_a, aictrl_b, fifoctrl_a; @@ -883,7 +883,7 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) 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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int nsample_limit; /* Number of samples (16bit, stereo) in one period */ @@ -936,7 +936,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, 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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (cmd) { @@ -967,7 +967,7 @@ static int dac33_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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 ioc_reg, asrcb_reg; ioc_reg = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL); @@ -997,7 +997,7 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 aictrl_a, aictrl_b; aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A); @@ -1088,7 +1088,7 @@ static int dac33_soc_probe(struct platform_device *pdev) codec = tlv320dac33_codec; socdev->card->codec = codec; - dac33 = codec->private_data; + dac33 = snd_soc_codec_get_drvdata(codec); /* Power up the codec */ dac33_hard_power(codec, 1); @@ -1209,7 +1209,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, return -ENOMEM; codec = &dac33->codec; - codec->private_data = dac33; + snd_soc_codec_set_drvdata(codec, dac33); codec->control_data = client; mutex_init(&codec->mutex); diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index bf59b8a4d1d..4eb5a2e59f3 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -175,7 +175,7 @@ static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec, static int twl4030_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); int write_to_reg = 0; twl4030_write_reg_cache(codec, reg, value); @@ -220,7 +220,7 @@ static int twl4030_write(struct snd_soc_codec *codec, static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); int mode; if (enable == twl4030->codec_powered) @@ -258,7 +258,7 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); int status; if (enable == twl4030->apll_enabled) @@ -279,7 +279,7 @@ static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) static void twl4030_power_up(struct snd_soc_codec *codec) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 anamicl, regmisc1, byte; int i = 0; @@ -570,7 +570,7 @@ static int micpath_event(struct snd_soc_dapm_widget *w, static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ struct snd_kcontrol *kcontrol, int event) \ { \ - struct twl4030_priv *twl4030 = w->codec->private_data; \ + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); \ \ switch (event) { \ case SND_SOC_DAPM_POST_PMU: \ @@ -678,7 +678,7 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) struct twl4030_setup_data *setup = socdev->codec_data; unsigned char hs_gain, hs_pop; - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); /* Base values for ramp delay calculation: 2^19 - 2^26 */ unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864}; @@ -741,7 +741,7 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) static int headsetlpga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct twl4030_priv *twl4030 = w->codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -765,7 +765,7 @@ static int headsetlpga_event(struct snd_soc_dapm_widget *w, static int headsetrpga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct twl4030_priv *twl4030 = w->codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -957,7 +957,7 @@ static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val; unsigned short mask, bitmask; @@ -1627,7 +1627,7 @@ static int twl4030_startup(struct snd_pcm_substream *substream, 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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream) { twl4030->slave_substream = substream; @@ -1658,7 +1658,7 @@ static void twl4030_shutdown(struct snd_pcm_substream *substream, 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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream == substream) twl4030->master_substream = twl4030->slave_substream; @@ -1684,7 +1684,7 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, 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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode, old_mode, format, old_format; /* If the substream has 4 channel, do the necessary setup */ @@ -1804,7 +1804,7 @@ static int twl4030_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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 19200000: @@ -1919,7 +1919,7 @@ static int twl4030_voice_startup(struct snd_pcm_substream *substream, 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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode; /* If the system master clock is not 26MHz, the voice PCM interface is @@ -2164,7 +2164,7 @@ static int twl4030_soc_probe(struct platform_device *pdev) BUG_ON(!twl4030_codec); codec = twl4030_codec; - twl4030 = codec->private_data; + twl4030 = snd_soc_codec_get_drvdata(codec); socdev->card->codec = codec; /* Configuration for headset ramp delay from setup data */ @@ -2227,7 +2227,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) } codec = &twl4030->codec; - codec->private_data = twl4030; + snd_soc_codec_set_drvdata(codec, twl4030); codec->dev = &pdev->dev; twl4030_dai[0].dev = &pdev->dev; twl4030_dai[1].dev = &pdev->dev; diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 3e99fe5131d..20deaca8e10 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -174,7 +174,7 @@ static int uda134x_startup(struct snd_pcm_substream *substream, 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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); struct snd_pcm_runtime *master_runtime; if (uda134x->master_substream) { @@ -207,7 +207,7 @@ static void uda134x_shutdown(struct snd_pcm_substream *substream, 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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); if (uda134x->master_substream == substream) uda134x->master_substream = uda134x->slave_substream; @@ -222,7 +222,7 @@ static int uda134x_hw_params(struct snd_pcm_substream *substream, 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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); u8 hw_params; if (substream == uda134x->slave_substream) { @@ -294,7 +294,7 @@ static int uda134x_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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__, clk_id, freq, dir); @@ -316,7 +316,7 @@ static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); pr_debug("%s fmt: %08X\n", __func__, fmt); @@ -503,7 +503,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL); if (uda134x == NULL) goto priv_err; - codec->private_data = uda134x; + snd_soc_codec_set_drvdata(codec, uda134x); codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg), GFP_KERNEL); @@ -567,7 +567,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) pcm_err: kfree(codec->reg_cache); reg_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); priv_err: kfree(codec); return ret; @@ -585,7 +585,7 @@ static int uda134x_soc_remove(struct platform_device *pdev) snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec->reg_cache); kfree(codec); diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 9cd0a66b766..ecba686563a 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -476,7 +476,7 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, 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 uda1380_priv *uda1380 = codec->private_data; + struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec); int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); switch (cmd) { @@ -774,7 +774,7 @@ static int uda1380_register(struct uda1380_priv *uda1380) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = uda1380; + snd_soc_codec_set_drvdata(codec, uda1380); codec->name = "UDA1380"; codec->owner = THIS_MODULE; codec->read = uda1380_read_reg_cache; diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 8ac92f5e4f9..145467d3705 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -95,7 +95,7 @@ static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg, */ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) { - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1; struct wm8350 *wm8350 = codec->control_data; int left_complete = 0, right_complete = 0; @@ -161,7 +161,7 @@ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) */ static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) { - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out2 = &wm8350_data->out2; struct wm8350 *wm8350 = codec->control_data; int left_complete = 0, right_complete = 0; @@ -231,7 +231,7 @@ static void wm8350_pga_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1, *out2 = &wm8350_data->out2; int i, out1_complete, out2_complete; @@ -278,7 +278,7 @@ static int pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out; switch (w->shift) { @@ -323,7 +323,7 @@ static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out = NULL; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; @@ -366,7 +366,7 @@ static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_priv->out1; struct wm8350_output *out2 = &wm8350_priv->out2; struct soc_mixer_control *mc = @@ -1108,7 +1108,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, { struct snd_soc_codec *codec = codec_dai->codec; struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; int ret = 0; u16 fll_1, fll_4; @@ -1160,7 +1160,7 @@ static int wm8350_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350_audio_platform_data *platform = wm8350->codec.platform_data; u16 pm1; @@ -1399,7 +1399,7 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data) int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report) { - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350 *wm8350 = codec->control_data; int irq; int ena; @@ -1471,7 +1471,7 @@ int wm8350_mic_jack_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int detect_report, int short_report) { - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350 *wm8350 = codec->control_data; priv->mic.jack = jack; @@ -1508,7 +1508,7 @@ static int wm8350_probe(struct platform_device *pdev) socdev->card->codec = wm8350_codec; codec = socdev->card->codec; wm8350 = codec->control_data; - priv = codec->private_data; + priv = snd_soc_codec_get_drvdata(codec); /* Enable the codec */ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); @@ -1579,7 +1579,7 @@ static int wm8350_remove(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); int ret; wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, @@ -1698,7 +1698,7 @@ static __devinit int wm8350_codec_probe(struct platform_device *pdev) codec->dai = &wm8350_dai; codec->num_dai = 1; codec->reg_cache_size = WM8350_MAX_REGISTER; - codec->private_data = priv; + snd_soc_codec_set_drvdata(codec, priv); codec->control_data = wm8350; /* Put the codec into reset if it wasn't already */ @@ -1730,7 +1730,7 @@ static int __devexit wm8350_codec_remove(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct snd_soc_codec *codec = wm8350->codec.codec; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); snd_soc_unregister_dai(&wm8350_dai); snd_soc_unregister_codec(codec); diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index b432f4d4a32..a7506ae2b8c 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -76,7 +76,7 @@ struct wm8400_priv { static inline unsigned int wm8400_read(struct snd_soc_codec *codec, unsigned int reg) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); if (reg == WM8400_INTDRIVBITS) return wm8400->fake_register; @@ -90,7 +90,7 @@ static inline unsigned int wm8400_read(struct snd_soc_codec *codec, static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); if (reg == WM8400_INTDRIVBITS) { wm8400->fake_register = value; @@ -101,7 +101,7 @@ static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, static void wm8400_codec_reset(struct snd_soc_codec *codec) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); wm8400_reset_codec_reg_cache(wm8400->wm8400); } @@ -925,7 +925,7 @@ static int wm8400_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 wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); wm8400->sysclk = freq; return 0; @@ -1014,7 +1014,7 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); struct fll_factors factors; int ret; u16 reg; @@ -1203,7 +1203,7 @@ static int wm8400_mute(struct snd_soc_dai *dai, int mute) static int wm8400_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); u16 val; int ret; @@ -1466,7 +1466,7 @@ static int wm8400_codec_probe(struct platform_device *dev) return -ENOMEM; codec = &priv->codec; - codec->private_data = priv; + snd_soc_codec_set_drvdata(codec, priv); codec->control_data = dev_get_drvdata(&dev->dev); priv->wm8400 = dev_get_drvdata(&dev->dev); @@ -1529,7 +1529,7 @@ err: static int __exit wm8400_codec_remove(struct platform_device *dev) { - struct wm8400_priv *priv = wm8400_codec->private_data; + struct wm8400_priv *priv = snd_soc_codec_get_drvdata(wm8400_codec); u16 reg; snd_soc_unregister_dai(&wm8400_dai); diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index d3a61d7ea0c..2cdffb0b2a8 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -137,7 +137,7 @@ static int wm8523_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); /* The set of sample rates that can be supported depends on the * MCLK supplied to the CODEC - enforce this. @@ -163,7 +163,7 @@ static int wm8523_hw_params(struct snd_pcm_substream *substream, 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 wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); int i; u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1); u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2); @@ -210,7 +210,7 @@ static int wm8523_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 wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); unsigned int val; int i; @@ -317,7 +317,7 @@ static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, static int wm8523_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); int ret, i; switch (level) { @@ -488,7 +488,7 @@ static int wm8523_register(struct wm8523_priv *wm8523, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8523; + snd_soc_codec_set_drvdata(codec, wm8523); codec->name = "WM8523"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index d077df6f5e7..94b3f4c4d64 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -411,7 +411,7 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, { int offset; struct snd_soc_codec *codec = codec_dai->codec; - struct wm8580_priv *wm8580 = codec->private_data; + struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec); struct pll_state *state; struct _pll_div pll_div; unsigned int reg; @@ -839,7 +839,7 @@ static int wm8580_register(struct wm8580_priv *wm8580, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8580; + snd_soc_codec_set_drvdata(codec, wm8580); codec->name = "WM8580"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index 24a35603bcf..f350c5ecd09 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -162,7 +162,7 @@ static int wm8711_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8711_priv *wm8711 = codec->private_data; + struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0xfffc; int i = get_coeff(wm8711->sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr << 2) | @@ -226,7 +226,7 @@ static int wm8711_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 wm8711_priv *wm8711 = codec->private_data; + struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -445,7 +445,7 @@ static int wm8711_register(struct wm8711_priv *wm8711, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8711; + snd_soc_codec_set_drvdata(codec, wm8711); codec->name = "WM8711"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index d5e1c5b681e..63f90c4ad93 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -224,7 +224,7 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, 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 wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3; int i = get_coeff(wm8731->sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr << 2) | @@ -291,7 +291,7 @@ static int wm8731_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 wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -427,7 +427,7 @@ static int wm8731_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; - struct wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); snd_soc_write(codec, WM8731_ACTIVE, 0x0); wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -440,7 +440,7 @@ static int wm8731_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); int i, ret; u8 data[2]; u16 *cache = codec->reg_cache; @@ -535,7 +535,7 @@ static int wm8731_register(struct wm8731_priv *wm8731, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8731; + snd_soc_codec_set_drvdata(codec, wm8731); codec->name = "WM8731"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 4bbc512c083..60d7cf88bf1 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -482,7 +482,7 @@ static int wm8750_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 wm8750_priv *wm8750 = codec->private_data; + struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -561,7 +561,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, 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 wm8750_priv *wm8750 = codec->private_data; + struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0; int coeff = get_coeff(wm8750->sysclk, params_rate(params)); @@ -775,10 +775,9 @@ static int wm8750_register(struct wm8750_priv *wm8750, codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; - codec->private_data = wm8750; codec->reg_cache_size = ARRAY_SIZE(wm8750->reg_cache) + 1; codec->reg_cache = &wm8750->reg_cache; - codec->private_data = wm8750; + snd_soc_codec_set_drvdata(codec, wm8750); memcpy(codec->reg_cache, wm8750_reg, sizeof(wm8750->reg_cache)); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index c2444e7c848..f9e56a1dfde 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -850,7 +850,7 @@ static int wm8753_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 wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -913,7 +913,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, 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 wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3; u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f; @@ -1147,7 +1147,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, 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 wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0; u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3; int coeff; @@ -1645,7 +1645,7 @@ static int wm8753_register(struct wm8753_priv *wm8753) codec->num_dai = 2; codec->reg_cache_size = ARRAY_SIZE(wm8753->reg_cache) + 1; codec->reg_cache = &wm8753->reg_cache; - codec->private_data = wm8753; + snd_soc_codec_set_drvdata(codec, wm8753); memcpy(codec->reg_cache, wm8753_reg, sizeof(wm8753->reg_cache)); INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index 44e7d9d82f8..694600969d6 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -226,7 +226,7 @@ static int wm8776_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8776_priv *wm8776 = codec->private_data; + struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); int iface_reg, iface; int ratio_shift, master; int i; @@ -303,7 +303,7 @@ static int wm8776_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8776_priv *wm8776 = codec->private_data; + struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); BUG_ON(dai->id >= ARRAY_SIZE(wm8776->sysclk)); @@ -490,7 +490,7 @@ static int wm8776_register(struct wm8776_priv *wm8776, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8776; + snd_soc_codec_set_drvdata(codec, wm8776); codec->name = "WM8776"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index dbc368c0826..19a6b25988c 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -744,7 +744,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, static int wm8900_set_fll(struct snd_soc_codec *codec, int fll_id, unsigned int freq_in, unsigned int freq_out) { - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; unsigned int reg; @@ -1131,7 +1131,7 @@ static int wm8900_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; - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); int fll_out = wm8900->fll_out; int fll_in = wm8900->fll_in; int ret; @@ -1155,7 +1155,7 @@ static int wm8900_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); u16 *cache; int i, ret; @@ -1205,7 +1205,7 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8900->codec; - codec->private_data = wm8900; + snd_soc_codec_set_drvdata(codec, wm8900); codec->reg_cache = &wm8900->reg_cache[0]; codec->reg_cache_size = WM8900_MAXREG; @@ -1304,7 +1304,7 @@ static __devexit int wm8900_i2c_remove(struct i2c_client *client) wm8900_set_bias_level(wm8900_codec, SND_SOC_BIAS_OFF); wm8900_dai.dev = NULL; - kfree(wm8900_codec->private_data); + kfree(snd_soc_codec_get_drvdata(wm8900_codec)); wm8900_codec = NULL; return 0; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 134b17532f2..5a2510c39c1 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -252,7 +252,7 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) { u16 reg[5]; struct i2c_client *i2c = codec->control_data; - struct wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); BUG_ON(start > 48); @@ -420,7 +420,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *codec = widget->codec; - struct wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; u16 reg; int ret; @@ -1001,7 +1001,7 @@ static int wm8903_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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); wm8903->sysclk = freq; @@ -1229,7 +1229,7 @@ static int wm8903_startup(struct snd_pcm_substream *substream, 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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -1265,7 +1265,7 @@ static void wm8903_shutdown(struct snd_pcm_substream *substream, 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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) wm8903->playback_active--; @@ -1285,7 +1285,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, 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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; int fs = params_rate(params); int bclk; @@ -1464,7 +1464,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int det, int shrt) { - struct wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT; dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n", @@ -1674,7 +1674,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(wm8903->reg_cache); codec->reg_cache = &wm8903->reg_cache[0]; - codec->private_data = wm8903; + snd_soc_codec_set_drvdata(codec, wm8903); codec->volatile_register = wm8903_volatile_register; init_completion(&wm8903->wseq); @@ -1812,7 +1812,7 @@ err: static __devexit int wm8903_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); - struct wm8903_priv *priv = codec->private_data; + struct wm8903_priv *priv = snd_soc_codec_get_drvdata(codec); snd_soc_unregister_dai(&wm8903_dai); snd_soc_unregister_codec(codec); @@ -1822,7 +1822,7 @@ static __devexit int wm8903_i2c_remove(struct i2c_client *client) if (client->irq) free_irq(client->irq, priv); - kfree(codec->private_data); + kfree(priv); wm8903_codec = NULL; wm8903_dai.dev = NULL; diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index c5b50d73f2d..a022ca7e758 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -612,7 +612,7 @@ static int wm8904_reset(struct snd_soc_codec *codec) static int wm8904_configure_clocking(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); unsigned int clock0, clock2, rate; /* Gate the clock while we're updating to avoid misclocking */ @@ -668,7 +668,7 @@ static int wm8904_configure_clocking(struct snd_soc_codec *codec) static void wm8904_set_drc(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int save, i; @@ -688,7 +688,7 @@ static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int value = ucontrol->value.integer.value[0]; @@ -706,7 +706,7 @@ static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8904->drc_cfg; @@ -715,7 +715,7 @@ static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol, static void wm8904_set_retune_mobile(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int best, best_val, save, i, cfg; @@ -759,7 +759,7 @@ static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int value = ucontrol->value.integer.value[0]; @@ -777,7 +777,7 @@ static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg; @@ -788,7 +788,7 @@ static int deemph_settings[] = { 0, 32000, 44100, 48000 }; static int wm8904_set_deemph(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int val, i, best; /* If we're using deemphasis select the nearest available sample @@ -817,7 +817,7 @@ static int wm8904_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); return wm8904->deemph; } @@ -826,7 +826,7 @@ static int wm8904_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; if (deemph > 1) @@ -942,7 +942,7 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -980,7 +980,7 @@ static int out_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int reg, val; int dcs_mask; int dcs_l, dcs_r; @@ -1428,7 +1428,7 @@ static const struct snd_soc_dapm_route wm8912_intercon[] = { static int wm8904_add_widgets(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); snd_soc_dapm_new_controls(codec, wm8904_core_dapm_widgets, ARRAY_SIZE(wm8904_core_dapm_widgets)); @@ -1542,7 +1542,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int aif1 = 0; unsigned int aif2 = 0; @@ -1669,7 +1669,7 @@ static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *priv = codec->private_data; + struct wm8904_priv *priv = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM8904_CLK_MCLK: @@ -1785,7 +1785,7 @@ static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int aif1 = 0; /* Don't need to validate anything if we're turning off TDM */ @@ -1942,7 +1942,7 @@ static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; int ret, val; int clock2, fll1; @@ -2094,7 +2094,7 @@ static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute) static void wm8904_sync_cache(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int i; if (!codec->cache_sync) @@ -2121,7 +2121,7 @@ static void wm8904_sync_cache(struct snd_soc_codec *codec) static int wm8904_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { @@ -2394,7 +2394,7 @@ static int wm8904_probe(struct platform_device *pdev) goto pcm_err; } - wm8904_handle_pdata(codec->private_data); + wm8904_handle_pdata(snd_soc_codec_get_drvdata(codec)); wm8904_add_widgets(codec); @@ -2439,7 +2439,7 @@ static int wm8904_register(struct wm8904_priv *wm8904, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8904; + snd_soc_codec_set_drvdata(codec, wm8904); codec->name = "WM8904"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 31e39ffd1d8..b2f12828630 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -580,7 +580,7 @@ static int wm8940_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 wm8940_priv *wm8940 = codec->private_data; + struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -772,7 +772,7 @@ static int wm8940_register(struct wm8940_priv *wm8940, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8940; + snd_soc_codec_set_drvdata(codec, wm8940); codec->name = "WM8940"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 615dab2b62e..5f0bde56cf0 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -234,7 +234,7 @@ static struct { static int wm8955_configure_clocking(struct snd_soc_codec *codec) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int i, ret, val; int clocking = 0; int srate = 0; @@ -352,7 +352,7 @@ static int deemph_settings[] = { 0, 32000, 44100, 48000 }; static int wm8955_set_deemph(struct snd_soc_codec *codec) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int val, i, best; /* If we're using deemphasis select the nearest available sample @@ -381,7 +381,7 @@ static int wm8955_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); return wm8955->deemph; } @@ -390,7 +390,7 @@ static int wm8955_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; if (deemph > 1) @@ -597,7 +597,7 @@ static int wm8955_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int ret; int wl; @@ -646,7 +646,7 @@ static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8955_priv *priv = codec->private_data; + struct wm8955_priv *priv = snd_soc_codec_get_drvdata(codec); int div; switch (clk_id) { @@ -769,7 +769,7 @@ static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute) static int wm8955_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int ret, i; switch (level) { @@ -970,7 +970,7 @@ static int wm8955_register(struct wm8955_priv *wm8955, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8955; + snd_soc_codec_set_drvdata(codec, wm8955); codec->name = "WM8955"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index c2960d3ec6d..eba3ac01311 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -331,7 +331,7 @@ static const struct snd_soc_dapm_route audio_paths_capless[] = { static int wm8960_add_widgets(struct snd_soc_codec *codec) { struct wm8960_data *pdata = codec->dev->platform_data; - struct wm8960_priv *wm8960 = codec->private_data; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_widget *w; snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, @@ -534,7 +534,7 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8960_priv *wm8960 = codec->private_data; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int reg; switch (level) { @@ -915,7 +915,7 @@ static int wm8960_register(struct wm8960_priv *wm8960, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8960; + snd_soc_codec_set_drvdata(codec, wm8960); codec->name = "WM8960"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index d2342c5e042..bab393feb94 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -630,7 +630,7 @@ static int wm8961_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8961_priv *wm8961 = codec->private_data; + struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); int i, best, target, fs; u16 reg; @@ -721,7 +721,7 @@ static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8961_priv *wm8961 = codec->private_data; + struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); u16 reg = snd_soc_read(codec, WM8961_CLOCKING1); if (freq > 33000000) { @@ -1064,7 +1064,7 @@ static int wm8961_register(struct wm8961_priv *wm8961) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8961; + snd_soc_codec_set_drvdata(codec, wm8961); codec->name = "WM8961"; codec->owner = THIS_MODULE; codec->dai = &wm8961_dai; diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index d9540d55fc8..95c9ec3862d 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -414,7 +414,7 @@ static int wm8971_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 wm8971_priv *wm8971 = codec->private_data; + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -493,7 +493,7 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, 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 wm8971_priv *wm8971 = codec->private_data; + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0; int coeff = get_coeff(wm8971->sysclk, params_rate(params)); @@ -819,7 +819,7 @@ static int wm8971_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = wm8971; + snd_soc_codec_set_drvdata(codec, wm8971); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -829,7 +829,7 @@ static int wm8971_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); wm8971_workq = create_workqueue("wm8971"); if (wm8971_workq == NULL) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return -ENOMEM; } @@ -843,7 +843,7 @@ static int wm8971_probe(struct platform_device *pdev) if (ret != 0) { destroy_workqueue(wm8971_workq); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } @@ -866,7 +866,7 @@ static int wm8971_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8971_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index ee637af4737..1e4da32c407 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -676,7 +676,7 @@ static __devinit int wm8974_register(struct wm8974_priv *wm8974) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8974; + snd_soc_codec_set_drvdata(codec, wm8974); codec->name = "WM8974"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 28bb59ea6ea..6866a6a80fe 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -438,7 +438,7 @@ static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk, */ static int wm8978_configure_pll(struct snd_soc_codec *codec) { - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); struct wm8978_pll_div pll_div; unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk, f_256fs = wm8978->f_256fs; @@ -534,7 +534,7 @@ static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (div_id) { @@ -579,7 +579,7 @@ static int wm8978_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 wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq); @@ -691,7 +691,7 @@ static int wm8978_hw_params(struct snd_pcm_substream *substream, 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 wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); /* Word length mask = 0x60 */ u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60; /* Sampling rate mask = 0xe (for filters) */ @@ -911,7 +911,7 @@ static int wm8978_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int i; u16 *cache = codec->reg_cache; @@ -1019,7 +1019,7 @@ static __devinit int wm8978_register(struct wm8978_priv *wm8978) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8978; + snd_soc_codec_set_drvdata(codec, wm8978); codec->name = "WM8978"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 2862e4dced2..563cf898daf 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -494,7 +494,7 @@ static int wm8988_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 wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -584,7 +584,7 @@ static int wm8988_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); /* The set of sample rates that can be supported depends on the * MCLK supplied to the CODEC - enforce this. @@ -609,7 +609,7 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, 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 wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180; int coeff; @@ -832,7 +832,7 @@ static int wm8988_register(struct wm8988_priv *wm8988, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8988; + snd_soc_codec_set_drvdata(codec, wm8988); codec->name = "WM8988"; codec->owner = THIS_MODULE; codec->dai = &wm8988_dai; diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 056b787b6ee..9a9528e9044 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -1011,7 +1011,7 @@ static int wm8990_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 wm8990_priv *wm8990 = codec->private_data; + struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec); wm8990->sysclk = freq; return 0; @@ -1523,7 +1523,7 @@ static int wm8990_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = wm8990; + snd_soc_codec_set_drvdata(codec, wm8990); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -1540,7 +1540,7 @@ static int wm8990_probe(struct platform_device *pdev) #endif if (ret != 0) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } return ret; @@ -1560,7 +1560,7 @@ static int wm8990_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8990_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index bf022f68b84..e739c5008b1 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -370,7 +370,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); u16 reg1, reg4, reg5; struct _fll_div fll_div; int ret; @@ -457,7 +457,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, static int configure_clock(struct snd_soc_codec *codec) { - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); unsigned int reg; /* This should be done on init() for bypass paths */ @@ -716,7 +716,7 @@ static int class_w_put(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *codec = widget->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; /* Turn it off if we're using the main output mixer */ @@ -948,7 +948,7 @@ static void wm8993_cache_restore(struct snd_soc_codec *codec) static int wm8993_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { @@ -1046,7 +1046,7 @@ static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM8993_SYSCLK_MCLK: @@ -1066,7 +1066,7 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1); unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4); @@ -1162,7 +1162,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int clocking1, clocking3, aif1, aif4; @@ -1327,7 +1327,7 @@ static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int aif1 = 0; int aif2 = 0; @@ -1430,7 +1430,7 @@ static int wm8993_probe(struct platform_device *pdev) socdev->card->codec = wm8993_codec; codec = wm8993_codec; - wm8993 = codec->private_data; + wm8993 = snd_soc_codec_get_drvdata(codec); ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1477,7 +1477,7 @@ static int wm8993_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; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int fll_fout = wm8993->fll_fout; int fll_fref = wm8993->fll_fref; int ret; @@ -1501,7 +1501,7 @@ static int wm8993_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1570,7 +1570,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, codec->set_bias_level = wm8993_set_bias_level; codec->dai = &wm8993_dai; codec->num_dai = 1; - codec->private_data = wm8993; + snd_soc_codec_set_drvdata(codec, wm8993); wm8993->hubs_data.hp_startup_mode = 1; wm8993->hubs_data.dcs_codes = -2; diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index e00201e0820..29c4cfccd6b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1703,7 +1703,7 @@ static int wm8994_volatile(unsigned int reg) static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); BUG_ON(reg > WM8994_MAX_REGISTER); @@ -1728,7 +1728,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec, static int configure_aif_clock(struct snd_soc_codec *codec, int aif) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int rate; int reg1 = 0; int offset; @@ -1780,7 +1780,7 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif) static int configure_clock(struct snd_soc_codec *codec) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int old, new; /* Bring up the AIF clocks first */ @@ -1877,7 +1877,7 @@ static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol, static void wm8994_set_drc(struct snd_soc_codec *codec, int drc) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int base = wm8994_drc_base[drc]; int cfg = wm8994->drc_cfg[drc]; @@ -1913,7 +1913,7 @@ static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int drc = wm8994_get_drc(kcontrol->id.name); int value = ucontrol->value.integer.value[0]; @@ -1935,7 +1935,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int drc = wm8994_get_drc(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc]; @@ -1945,7 +1945,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int base = wm8994_retune_mobile_base[block]; int iface, best, best_val, save, i, cfg; @@ -2016,7 +2016,7 @@ static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int block = wm8994_get_retune_mobile_block(kcontrol->id.name); int value = ucontrol->value.integer.value[0]; @@ -2038,7 +2038,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int block = wm8994_get_retune_mobile_block(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; @@ -2819,7 +2819,7 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = dai->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int reg_offset, ret; struct fll_div fll; u16 reg, aif1, aif2; @@ -2915,7 +2915,7 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); switch (dai->id) { case 1: @@ -3181,7 +3181,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int aif1_reg; int bclk_reg; int lrclk_reg; @@ -3468,7 +3468,7 @@ static int wm8994_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; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int i, ret; for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { @@ -3489,7 +3489,7 @@ static int wm8994_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); u16 *reg_cache = codec->reg_cache; int i, ret; @@ -3684,7 +3684,7 @@ static int wm8994_probe(struct platform_device *pdev) return ret; } - wm8994_handle_pdata(codec->private_data); + wm8994_handle_pdata(snd_soc_codec_get_drvdata(codec)); wm_hubs_add_analogue_controls(codec); snd_soc_add_controls(codec, wm8994_snd_controls, @@ -3735,7 +3735,7 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_wm8994); int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int micbias, int det, int shrt) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_micdet *micdet; int reg; @@ -3830,7 +3830,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8994; + snd_soc_codec_set_drvdata(codec, wm8994); codec->control_data = dev_get_drvdata(pdev->dev.parent); codec->name = "WM8994"; codec->owner = THIS_MODULE; diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index c468497314b..87a87487d3f 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -520,7 +520,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, unsigned int Fref, unsigned int Fout) { - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); u16 reg1, reg4, reg5; struct _fll_div fll_div; int ret; @@ -606,7 +606,7 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, static int configure_clock(struct snd_soc_codec *codec) { - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); int new_sysclk, i, target; unsigned int reg; int ret = 0; @@ -701,7 +701,7 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); /* This should be done on init() for bypass paths */ switch (wm9081->sysclk_source) { @@ -872,7 +872,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2); aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | @@ -964,7 +964,7 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; @@ -1138,7 +1138,7 @@ static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM9081_SYSCLK_MCLK: @@ -1158,7 +1158,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1); aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); @@ -1241,7 +1241,7 @@ static int wm9081_probe(struct platform_device *pdev) socdev->card->codec = wm9081_codec; codec = wm9081_codec; - wm9081 = codec->private_data; + wm9081 = snd_soc_codec_get_drvdata(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -1338,7 +1338,7 @@ static int wm9081_register(struct wm9081_priv *wm9081, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm9081; + snd_soc_codec_set_drvdata(codec, wm9081); codec->name = "WM9081"; codec->owner = THIS_MODULE; codec->dai = &wm9081_dai; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index ceb86b4ddb2..d2244c93f58 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -763,7 +763,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source) static int wm9713_set_pll(struct snd_soc_codec *codec, int pll_id, unsigned int freq_in, unsigned int freq_out) { - struct wm9713_priv *wm9713 = codec->private_data; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); u16 reg, reg2; struct _pll_div pll_div; @@ -1174,7 +1174,7 @@ static int wm9713_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm9713_priv *wm9713 = codec->private_data; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); int i, ret; u16 *cache = codec->reg_cache; @@ -1227,8 +1227,9 @@ static int wm9713_soc_probe(struct platform_device *pdev) codec->reg_cache_size = sizeof(wm9713_reg); codec->reg_cache_step = 2; - codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); - if (codec->private_data == NULL) { + snd_soc_codec_set_drvdata(codec, kzalloc(sizeof(struct wm9713_priv), + GFP_KERNEL)); + if (snd_soc_codec_get_drvdata(codec) == NULL) { ret = -ENOMEM; goto priv_err; } @@ -1279,7 +1280,7 @@ pcm_err: snd_soc_free_ac97_codec(codec); codec_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); priv_err: kfree(codec->reg_cache); @@ -1301,7 +1302,7 @@ static int wm9713_soc_remove(struct platform_device *pdev) snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec->reg_cache); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index e1f225a3ac4..defdbd08e13 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -91,7 +91,7 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) */ static void calibrate_dc_servo(struct snd_soc_codec *codec) { - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); u16 reg, reg_l, reg_r, dcs_cfg; /* Set for 32 series updates */ @@ -154,7 +154,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); int ret; ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); @@ -327,7 +327,7 @@ static int hp_supply_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_PRE_PMU: -- cgit v1.2.3-18-g5258 From d4a8ca246146099e9068fc87cd2bc565896bc186 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 20 Apr 2010 08:20:31 +0200 Subject: ASoC: missing conversions to snd_soc_codec_*_drvdata() Conversions to snd_soc_codec_{get|set}_drvdata() were missing in some files in the previous commit. Signed-off-by: Takashi Iwai --- sound/soc/codecs/tlv320aic26.c | 14 +++++++------- sound/soc/codecs/twl4030.c | 2 +- sound/soc/codecs/twl6040.c | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 357b609196e..5856f7aae4d 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -48,7 +48,7 @@ struct aic26 { static unsigned int aic26_reg_read(struct snd_soc_codec *codec, unsigned int reg) { - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; u16 cmd, value; u8 buffer[2]; @@ -92,7 +92,7 @@ static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; u16 cmd; u8 buffer[4]; @@ -131,7 +131,7 @@ static int aic26_hw_params(struct snd_pcm_substream *substream, 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 aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); int fsref, divisor, wlen, pval, jval, dval, qval; u16 reg; @@ -198,7 +198,7 @@ static int aic26_hw_params(struct snd_pcm_substream *substream, static int aic26_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", @@ -217,7 +217,7 @@ static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," " freq=%i, dir=%i)\n", @@ -234,7 +234,7 @@ static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", codec_dai, fmt); @@ -430,7 +430,7 @@ static int aic26_spi_probe(struct spi_device *spi) /* Setup what we can in the codec structure so that the register * access functions will work as expected. More will be filled * out when it is probed by the SoC CODEC part of this driver */ - aic26->codec.private_data = aic26; + snd_soc_codec_set_drvdata(&aic26->codec, aic26); aic26->codec.name = "aic26"; aic26->codec.owner = THIS_MODULE; aic26->codec.dai = &aic26_dai; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 4eb5a2e59f3..2e025a3a261 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2001,7 +2001,7 @@ static int twl4030_voice_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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (freq != 26000000) { dev_err(codec->dev, "Unsupported APLL mclk: %u, the Voice" diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 108c51a513c..ead6d4b43b2 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -364,7 +364,7 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); if (SND_SOC_DAPM_EVENT_ON(event)) priv->non_lp++; @@ -378,7 +378,7 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, static irqreturn_t twl6040_naudint_handler(int irq, void *data) { struct snd_soc_codec *codec = data; - struct twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 intid; twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); @@ -636,7 +636,7 @@ static int twl6040_add_widgets(struct snd_soc_codec *codec) static int twl6040_power_up_completion(struct snd_soc_codec *codec, int naudint) { - struct twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int time_left; u8 intid; @@ -660,7 +660,7 @@ static int twl6040_power_up_completion(struct snd_soc_codec *codec, static int twl6040_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int audpwron = priv->audpwron; int naudint = priv->naudint; int ret; @@ -753,7 +753,7 @@ static int twl6040_startup(struct snd_pcm_substream *substream, 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 twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); if (!priv->sysclk) { dev_err(codec->dev, @@ -786,7 +786,7 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream, 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 twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 lppllctl; int rate; @@ -822,7 +822,7 @@ static int twl6040_trigger(struct snd_pcm_substream *substream, 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 twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -849,7 +849,7 @@ static int twl6040_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 twl6040_data *priv = codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 hppllctl, lppllctl; hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL); @@ -1095,7 +1095,7 @@ static int __devinit twl6040_codec_probe(struct platform_device *pdev) codec->read = twl6040_read_reg_cache; codec->write = twl6040_write; codec->set_bias_level = twl6040_set_bias_level; - codec->private_data = priv; + snd_soc_codec_set_drvdata(codec, priv); codec->dai = &twl6040_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(twl6040_reg); @@ -1183,7 +1183,7 @@ cache_err: static int __devexit twl6040_codec_remove(struct platform_device *pdev) { - struct twl6040_data *priv = twl6040_codec->private_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(twl6040_codec); int audpwron = priv->audpwron; int naudint = priv->naudint; -- cgit v1.2.3-18-g5258 From 4f6f22d7bef77dfb6b27eaed4240784339c546e6 Mon Sep 17 00:00:00 2001 From: Phil Carmody Date: Wed, 14 Apr 2010 17:03:13 +0300 Subject: ASoC: da7210: Fencepost error in reg cache read An index equal to the array size may not be accessed. Signed-off-by: Phil Carmody Acked-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/da7210.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 89cea32f942..b9cbaeb09e0 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -181,7 +181,7 @@ static const u8 da7210_reg[] = { static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg) { u8 *cache = codec->reg_cache; - BUG_ON(reg > ARRAY_SIZE(da7210_reg)); + BUG_ON(reg >= ARRAY_SIZE(da7210_reg)); return cache[reg]; } -- cgit v1.2.3-18-g5258 From 136ff2a272ad4bee33bf85f8c490ff8a2dd08f96 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 20 Apr 2010 12:56:18 +0900 Subject: ASoC: Support FLL input clock selection on WM8994 The WM8994 FLL can be clocked from one of four inputs, the two MCLKs and the LRCLK and BCLK of the AIF associated with the FLL. Allow all four inputs to be used rather than defaulting to MCLK1. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 17 +++++++++++++++-- sound/soc/codecs/wm8994.h | 5 +++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 29c4cfccd6b..a27b2ff769d 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -2843,6 +2843,16 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, return -EINVAL; } + switch (src) { + case WM8994_FLL_SRC_MCLK1: + case WM8994_FLL_SRC_MCLK2: + case WM8994_FLL_SRC_LRCLK: + case WM8994_FLL_SRC_BCLK: + break; + default: + return -EINVAL; + } + /* Are we changing anything? */ if (wm8994->fll[id].src == src && wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out) @@ -2883,8 +2893,10 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, fll.n << WM8994_FLL1_N_SHIFT); snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset, - WM8994_FLL1_REFCLK_DIV_MASK, - fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT); + WM8994_FLL1_REFCLK_DIV_MASK | + WM8994_FLL1_REFCLK_SRC_MASK, + (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) | + (src - 1)); /* Enable (with fractional mode if required) */ if (freq_out) { @@ -2899,6 +2911,7 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, wm8994->fll[id].in = freq_in; wm8994->fll[id].out = freq_out; + wm8994->fll[id].src = src; /* Enable any gated AIF clocks */ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 79d5915ae4b..7072dc53935 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -23,6 +23,11 @@ extern struct snd_soc_dai wm8994_dai[]; #define WM8994_FLL1 1 #define WM8994_FLL2 2 +#define WM8994_FLL_SRC_MCLK1 1 +#define WM8994_FLL_SRC_MCLK2 2 +#define WM8994_FLL_SRC_LRCLK 3 +#define WM8994_FLL_SRC_BCLK 4 + int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int micbias, int det, int shrt); -- cgit v1.2.3-18-g5258 From 7d48a6acbcf5325a2551e948bd1557a46dec4c76 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 20 Apr 2010 13:36:11 +0900 Subject: ASoC: Set full range of WM8994 FLL Fratio values Use all the available Fratio values when configuring the WM8994 FLL, not just 0 and 3, following more complete characterisation of the device performance. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index a27b2ff769d..8d3ea9b2426 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -2784,9 +2784,18 @@ static int wm8994_get_fll_config(struct fll_div *fll, if (freq_in > 1000000) { fll->fll_fratio = 0; - } else { + } else if (freq_in > 256000) { + fll->fll_fratio = 1; + freq_in *= 2; + } else if (freq_in > 128000) { + fll->fll_fratio = 2; + freq_in *= 4; + } else if (freq_in > 64000) { fll->fll_fratio = 3; freq_in *= 8; + } else { + fll->fll_fratio = 4; + freq_in *= 16; } pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in); -- cgit v1.2.3-18-g5258 From ee839a2127a4f74505c5597966919ca57413f4de Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 20 Apr 2010 13:57:08 +0900 Subject: ASoC: Tone down debugging for WM8994 class W It's a little verbose during path changes. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 8d3ea9b2426..eefa2427b10 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -2189,13 +2189,13 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) /* Only support direct DAC->headphone paths */ reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1); if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) { - dev_dbg(codec->dev, "HPL connected to output mixer\n"); + dev_vdbg(codec->dev, "HPL connected to output mixer\n"); enable = 0; } reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2); if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) { - dev_dbg(codec->dev, "HPR connected to output mixer\n"); + dev_vdbg(codec->dev, "HPR connected to output mixer\n"); enable = 0; } @@ -2203,26 +2203,26 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING); switch (reg) { case WM8994_AIF2DACL_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF2DAC\n"); + dev_vdbg(codec->dev, "Class W source AIF2DAC\n"); source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; case WM8994_AIF1DAC2L_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF1DAC2\n"); + dev_vdbg(codec->dev, "Class W source AIF1DAC2\n"); source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; case WM8994_AIF1DAC1L_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF1DAC1\n"); + dev_vdbg(codec->dev, "Class W source AIF1DAC1\n"); source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; default: - dev_dbg(codec->dev, "DAC mixer setting: %x\n", reg); + dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg); enable = 0; break; } reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING); if (reg_r != reg) { - dev_dbg(codec->dev, "Left and right DAC mixers different\n"); + dev_vdbg(codec->dev, "Left and right DAC mixers different\n"); enable = 0; } -- cgit v1.2.3-18-g5258 From 7add84aa779b4f7ca39484a82a11e01d7acd4dca Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 22 Apr 2010 02:29:01 +0900 Subject: ASoC: Allow unspecified source when stopping WM8994 FLLs Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index eefa2427b10..cdfbfb633f3 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -2853,6 +2853,11 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, } switch (src) { + case 0: + /* Allow no source specification when stopping */ + if (freq_out) + return -EINVAL; + break; case WM8994_FLL_SRC_MCLK1: case WM8994_FLL_SRC_MCLK2: case WM8994_FLL_SRC_LRCLK: -- cgit v1.2.3-18-g5258 From 719f82d3987aad4cc9f46d19c35f362672545cad Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Wed, 21 Apr 2010 18:17:39 +0200 Subject: ALSA: Add support of AudioScience ASI boards Added the support of AudioScience ASI boards. The driver has been tested for years on alsa-driver external tree, now finally got merged to the kernel. Signed-off-by: Eliot Blennerhassett Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 10 + sound/pci/Kconfig | 12 + sound/pci/Makefile | 1 + sound/pci/asihpi/Makefile | 5 + sound/pci/asihpi/asihpi.c | 3002 ++++++++++++++++++ sound/pci/asihpi/hpi.h | 2001 ++++++++++++ sound/pci/asihpi/hpi6000.c | 1841 +++++++++++ sound/pci/asihpi/hpi6000.h | 70 + sound/pci/asihpi/hpi6205.c | 2332 ++++++++++++++ sound/pci/asihpi/hpi6205.h | 93 + sound/pci/asihpi/hpi_internal.h | 1641 ++++++++++ sound/pci/asihpi/hpicmn.c | 643 ++++ sound/pci/asihpi/hpicmn.h | 64 + sound/pci/asihpi/hpidebug.c | 225 ++ sound/pci/asihpi/hpidebug.h | 385 +++ sound/pci/asihpi/hpidspcd.c | 172 + sound/pci/asihpi/hpidspcd.h | 104 + sound/pci/asihpi/hpifunc.c | 3864 +++++++++++++++++++++++ sound/pci/asihpi/hpimsginit.c | 130 + sound/pci/asihpi/hpimsginit.h | 40 + sound/pci/asihpi/hpimsgx.c | 907 ++++++ sound/pci/asihpi/hpimsgx.h | 36 + sound/pci/asihpi/hpioctl.c | 484 +++ sound/pci/asihpi/hpioctl.h | 38 + sound/pci/asihpi/hpios.c | 114 + sound/pci/asihpi/hpios.h | 178 ++ sound/pci/asihpi/hpipcida.h | 37 + 27 files changed, 18429 insertions(+) create mode 100644 sound/pci/asihpi/Makefile create mode 100644 sound/pci/asihpi/asihpi.c create mode 100644 sound/pci/asihpi/hpi.h create mode 100644 sound/pci/asihpi/hpi6000.c create mode 100644 sound/pci/asihpi/hpi6000.h create mode 100644 sound/pci/asihpi/hpi6205.c create mode 100644 sound/pci/asihpi/hpi6205.h create mode 100644 sound/pci/asihpi/hpi_internal.h create mode 100644 sound/pci/asihpi/hpicmn.c create mode 100644 sound/pci/asihpi/hpicmn.h create mode 100644 sound/pci/asihpi/hpidebug.c create mode 100644 sound/pci/asihpi/hpidebug.h create mode 100644 sound/pci/asihpi/hpidspcd.c create mode 100644 sound/pci/asihpi/hpidspcd.h create mode 100644 sound/pci/asihpi/hpifunc.c create mode 100644 sound/pci/asihpi/hpimsginit.c create mode 100644 sound/pci/asihpi/hpimsginit.h create mode 100644 sound/pci/asihpi/hpimsgx.c create mode 100644 sound/pci/asihpi/hpimsgx.h create mode 100644 sound/pci/asihpi/hpioctl.c create mode 100644 sound/pci/asihpi/hpioctl.h create mode 100644 sound/pci/asihpi/hpios.c create mode 100644 sound/pci/asihpi/hpios.h create mode 100644 sound/pci/asihpi/hpipcida.h diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index bfcbbf88c44..dc681badca6 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -227,6 +227,16 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-asihpi + ----------------- + + Module for AudioScience ASI soundcards + + enable_hpi_hwdep - enable HPI hwdep for AudioScience soundcard + + This module supports multiple cards. + The driver requires the firmware loader support on kernel. + Module snd-atiixp ----------------- diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 1298c68d6bf..517ae65ffc8 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -58,6 +58,18 @@ config SND_ALI5451 To compile this driver as a module, choose M here: the module will be called snd-ali5451. +config SND_ASIHPI + tristate "AudioScience ASIxxxx" + depends on X86 + select FW_LOADER + select SND_PCM + select SND_HWDEP + help + Say Y here to include support for AudioScience ASI sound cards. + + To compile this driver as a module, choose M here: the module + will be called snd-asihpi. + config SND_ATIIXP tristate "ATI IXP AC97 Controller" select SND_AC97_CODEC diff --git a/sound/pci/Makefile b/sound/pci/Makefile index ecfc609d2b9..9cf4348ec13 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o obj-$(CONFIG_SND) += \ ac97/ \ ali5451/ \ + asihpi/ \ au88x0/ \ aw2/ \ ctxfi/ \ diff --git a/sound/pci/asihpi/Makefile b/sound/pci/asihpi/Makefile new file mode 100644 index 00000000000..391830a4556 --- /dev/null +++ b/sound/pci/asihpi/Makefile @@ -0,0 +1,5 @@ +snd-asihpi-objs := asihpi.o hpioctl.o hpimsginit.o\ + hpicmn.o hpifunc.o hpidebug.o hpidspcd.o\ + hpios.o hpi6000.o hpi6205.o hpimsgx.o + +obj-$(CONFIG_SND_ASIHPI) += snd-asihpi.o diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c new file mode 100644 index 00000000000..f74c7372b3d --- /dev/null +++ b/sound/pci/asihpi/asihpi.c @@ -0,0 +1,3002 @@ +/* + * Asihpi soundcard + * Copyright (c) by AudioScience Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation; + * + * 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 + * + * + * The following is not a condition of use, merely a request: + * If you modify this program, particularly if you fix errors, AudioScience Inc + * would appreciate it if you grant us the right to use those modifications + * for any purpose including commercial applications. + */ +/* >0: print Hw params, timer vars. >1: print stream write/copy sizes */ +#define REALLY_VERBOSE_LOGGING 0 + +#if REALLY_VERBOSE_LOGGING +#define VPRINTK1 snd_printd +#else +#define VPRINTK1(...) +#endif + +#if REALLY_VERBOSE_LOGGING > 1 +#define VPRINTK2 snd_printd +#else +#define VPRINTK2(...) +#endif + +#ifndef ASI_STYLE_NAMES +/* not sure how ALSA style name should look */ +#define ASI_STYLE_NAMES 1 +#endif + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpioctl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("AudioScience inc. "); +MODULE_DESCRIPTION("AudioScience ALSA ASI5000 ASI6000 ASI87xx ASI89xx"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static int enable_hpi_hwdep = 1; + +module_param_array(index, int, NULL, S_IRUGO); +MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard."); + +module_param_array(id, charp, NULL, S_IRUGO); +MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard."); + +module_param_array(enable, bool, NULL, S_IRUGO); +MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard."); + +module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(enable_hpi_hwdep, + "ALSA enable HPI hwdep for AudioScience soundcard "); + +/* identify driver */ +#ifdef KERNEL_ALSA_BUILD +static char *build_info = "built using headers from kernel source"; +module_param(build_info, charp, S_IRUGO); +MODULE_PARM_DESC(build_info, "built using headers from kernel source"); +#else +static char *build_info = "built within ALSA source"; +module_param(build_info, charp, S_IRUGO); +MODULE_PARM_DESC(build_info, "built within ALSA source"); +#endif + +/* set to 1 to dump every control from adapter to log */ +static const int mixer_dump; + +#define DEFAULT_SAMPLERATE 44100 +static int adapter_fs = DEFAULT_SAMPLERATE; + +static struct hpi_hsubsys *ss; /* handle to HPI audio subsystem */ + +/* defaults */ +#define PERIODS_MIN 2 +#define PERIOD_BYTES_MIN 2304 +#define BUFFER_BYTES_MAX (512 * 1024) + +/*#define TIMER_MILLISECONDS 20 +#define FORCE_TIMER_JIFFIES ((TIMER_MILLISECONDS * HZ + 999)/1000) +*/ + +#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7) + +struct clk_source { + int source; + int index; + char *name; +}; + +struct clk_cache { + int count; + int has_local; + struct clk_source s[MAX_CLOCKSOURCES]; +}; + +/* Per card data */ +struct snd_card_asihpi { + struct snd_card *card; + struct pci_dev *pci; + u16 adapter_index; + u32 serial_number; + u16 type; + u16 version; + u16 num_outstreams; + u16 num_instreams; + + u32 h_mixer; + struct clk_cache cc; + + u16 support_mmap; + u16 support_grouping; + u16 support_mrx; + u16 update_interval_frames; + u16 in_max_chans; + u16 out_max_chans; +}; + +/* Per stream data */ +struct snd_card_asihpi_pcm { + struct timer_list timer; + unsigned int respawn_timer; + unsigned int hpi_buffer_attached; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int bytes_per_sec; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + struct snd_pcm_substream *substream; + u32 h_stream; + struct hpi_format format; +}; + +/* universal stream verbs work with out or in stream handles */ + +/* Functions to allow driver to give a buffer to HPI for busmastering */ + +static u16 hpi_stream_host_buffer_attach( + struct hpi_hsubsys *hS, + u32 h_stream, /* handle to outstream. */ + u32 size_in_bytes, /* size in bytes of bus mastering buffer */ + u32 pci_address +) +{ + struct hpi_message hm; + struct hpi_response hr; + unsigned int obj = hpi_handle_object(h_stream); + + if (!h_stream) + return HPI_ERROR_INVALID_OBJ; + hpi_init_message_response(&hm, &hr, obj, + obj == HPI_OBJ_OSTREAM ? + HPI_OSTREAM_HOSTBUFFER_ALLOC : + HPI_ISTREAM_HOSTBUFFER_ALLOC); + + hpi_handle_to_indexes(h_stream, &hm.adapter_index, + &hm.obj_index); + + hm.u.d.u.buffer.buffer_size = size_in_bytes; + hm.u.d.u.buffer.pci_address = pci_address; + hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static u16 hpi_stream_host_buffer_detach( + struct hpi_hsubsys *hS, + u32 h_stream +) +{ + struct hpi_message hm; + struct hpi_response hr; + unsigned int obj = hpi_handle_object(h_stream); + + if (!h_stream) + return HPI_ERROR_INVALID_OBJ; + + hpi_init_message_response(&hm, &hr, obj, + obj == HPI_OBJ_OSTREAM ? + HPI_OSTREAM_HOSTBUFFER_FREE : + HPI_ISTREAM_HOSTBUFFER_FREE); + + hpi_handle_to_indexes(h_stream, &hm.adapter_index, + &hm.obj_index); + hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static inline u16 hpi_stream_start(struct hpi_hsubsys *hS, u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_start(hS, h_stream); + else + return hpi_instream_start(hS, h_stream); +} + +static inline u16 hpi_stream_stop(struct hpi_hsubsys *hS, u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_stop(hS, h_stream); + else + return hpi_instream_stop(hS, h_stream); +} + +static inline u16 hpi_stream_get_info_ex( + struct hpi_hsubsys *hS, + u32 h_stream, + u16 *pw_state, + u32 *pbuffer_size, + u32 *pdata_in_buffer, + u32 *psample_count, + u32 *pauxiliary_data +) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_get_info_ex(hS, h_stream, pw_state, + pbuffer_size, pdata_in_buffer, + psample_count, pauxiliary_data); + else + return hpi_instream_get_info_ex(hS, h_stream, pw_state, + pbuffer_size, pdata_in_buffer, + psample_count, pauxiliary_data); +} + +static inline u16 hpi_stream_group_add(struct hpi_hsubsys *hS, + u32 h_master, + u32 h_stream) +{ + if (hpi_handle_object(h_master) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_add(hS, h_master, h_stream); + else + return hpi_instream_group_add(hS, h_master, h_stream); +} + +static inline u16 hpi_stream_group_reset(struct hpi_hsubsys *hS, + u32 h_stream) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_reset(hS, h_stream); + else + return hpi_instream_group_reset(hS, h_stream); +} + +static inline u16 hpi_stream_group_get_map(struct hpi_hsubsys *hS, + u32 h_stream, u32 *mo, u32 *mi) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_get_map(hS, h_stream, mo, mi); + else + return hpi_instream_group_get_map(hS, h_stream, mo, mi); +} + +static u16 handle_error(u16 err, int line, char *filename) +{ + if (err) + printk(KERN_WARNING + "in file %s, line %d: HPI error %d\n", + filename, line, err); + return err; +} + +#define hpi_handle_error(x) handle_error(x, __LINE__, __FILE__) + +/***************************** GENERAL PCM ****************/ +#if REALLY_VERBOSE_LOGGING +static void print_hwparams(struct snd_pcm_hw_params *p) +{ + snd_printd("HWPARAMS \n"); + snd_printd("samplerate %d \n", params_rate(p)); + snd_printd("channels %d \n", params_channels(p)); + snd_printd("format %d \n", params_format(p)); + snd_printd("subformat %d \n", params_subformat(p)); + snd_printd("buffer bytes %d \n", params_buffer_bytes(p)); + snd_printd("period bytes %d \n", params_period_bytes(p)); + snd_printd("access %d \n", params_access(p)); + snd_printd("period_size %d \n", params_period_size(p)); + snd_printd("periods %d \n", params_periods(p)); + snd_printd("buffer_size %d \n", params_buffer_size(p)); +} +#else +#define print_hwparams(x) +#endif + +static snd_pcm_format_t hpi_to_alsa_formats[] = { + -1, /* INVALID */ + SNDRV_PCM_FORMAT_U8, /* HPI_FORMAT_PCM8_UNSIGNED 1 */ + SNDRV_PCM_FORMAT_S16, /* HPI_FORMAT_PCM16_SIGNED 2 */ + -1, /* HPI_FORMAT_MPEG_L1 3 */ + SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L2 4 */ + SNDRV_PCM_FORMAT_MPEG, /* HPI_FORMAT_MPEG_L3 5 */ + -1, /* HPI_FORMAT_DOLBY_AC2 6 */ + -1, /* HPI_FORMAT_DOLBY_AC3 7 */ + SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN 8 */ + -1, /* HPI_FORMAT_AA_TAGIT1_HITS 9 */ + -1, /* HPI_FORMAT_AA_TAGIT1_INSERTS 10 */ + SNDRV_PCM_FORMAT_S32, /* HPI_FORMAT_PCM32_SIGNED 11 */ + -1, /* HPI_FORMAT_RAW_BITSTREAM 12 */ + -1, /* HPI_FORMAT_AA_TAGIT1_HITS_EX1 13 */ + SNDRV_PCM_FORMAT_FLOAT, /* HPI_FORMAT_PCM32_FLOAT 14 */ +#if 1 + /* ALSA can't handle 3 byte sample size together with power-of-2 + * constraint on buffer_bytes, so disable this format + */ + -1 +#else + /* SNDRV_PCM_FORMAT_S24_3LE */ /* { HPI_FORMAT_PCM24_SIGNED 15 */ +#endif +}; + + +static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsa_format, + u16 *hpi_format) +{ + u16 format; + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + if (hpi_to_alsa_formats[format] == alsa_format) { + *hpi_format = format; + return 0; + } + } + + snd_printd(KERN_WARNING "failed match for alsa format %d\n", + alsa_format); + *hpi_format = 0; + return -EINVAL; +} + +static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi, + struct snd_pcm_hardware *pcmhw) +{ + u16 err; + u32 h_control; + u32 sample_rate; + int idx; + unsigned int rate_min = 200000; + unsigned int rate_max = 0; + unsigned int rates = 0; + + if (asihpi->support_mrx) { + rates |= SNDRV_PCM_RATE_CONTINUOUS; + rates |= SNDRV_PCM_RATE_8000_96000; + rate_min = 8000; + rate_max = 100000; + } else { + /* on cards without SRC, + valid rates are determined by sampleclock */ + err = hpi_mixer_get_control(ss, asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + if (err) { + snd_printk(KERN_ERR + "no local sampleclock, err %d\n", err); + } + + for (idx = 0; idx < 100; idx++) { + if (hpi_sample_clock_query_local_rate(ss, + h_control, idx, &sample_rate)) { + if (!idx) + snd_printk(KERN_ERR + "local rate query failed\n"); + + break; + } + + rate_min = min(rate_min, sample_rate); + rate_max = max(rate_max, sample_rate); + + switch (sample_rate) { + case 5512: + rates |= SNDRV_PCM_RATE_5512; + break; + case 8000: + rates |= SNDRV_PCM_RATE_8000; + break; + case 11025: + rates |= SNDRV_PCM_RATE_11025; + break; + case 16000: + rates |= SNDRV_PCM_RATE_16000; + break; + case 22050: + rates |= SNDRV_PCM_RATE_22050; + break; + case 32000: + rates |= SNDRV_PCM_RATE_32000; + break; + case 44100: + rates |= SNDRV_PCM_RATE_44100; + break; + case 48000: + rates |= SNDRV_PCM_RATE_48000; + break; + case 64000: + rates |= SNDRV_PCM_RATE_64000; + break; + case 88200: + rates |= SNDRV_PCM_RATE_88200; + break; + case 96000: + rates |= SNDRV_PCM_RATE_96000; + break; + case 176400: + rates |= SNDRV_PCM_RATE_176400; + break; + case 192000: + rates |= SNDRV_PCM_RATE_192000; + break; + default: /* some other rate */ + rates |= SNDRV_PCM_RATE_KNOT; + } + } + } + + /* printk(KERN_INFO "Supported rates %X %d %d\n", + rates, rate_min, rate_max); */ + pcmhw->rates = rates; + pcmhw->rate_min = rate_min; + pcmhw->rate_max = rate_max; +} + +static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + int err; + u16 format; + unsigned int bytes_per_sec; + + print_hwparams(params); + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + err = snd_card_asihpi_format_alsa2hpi(params_format(params), &format); + if (err) + return err; + + VPRINTK1(KERN_INFO "format %d, %d chans, %d_hz\n", + format, params_channels(params), + params_rate(params)); + + hpi_handle_error(hpi_format_create(&dpcm->format, + params_channels(params), + format, params_rate(params), 0, 0)); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (hpi_instream_reset(ss, dpcm->h_stream) != 0) + return -EINVAL; + + if (hpi_instream_set_format(ss, + dpcm->h_stream, &dpcm->format) != 0) + return -EINVAL; + } + + dpcm->hpi_buffer_attached = 0; + if (card->support_mmap) { + + err = hpi_stream_host_buffer_attach(ss, dpcm->h_stream, + params_buffer_bytes(params), runtime->dma_addr); + if (err == 0) { + snd_printd(KERN_INFO + "stream_host_buffer_attach succeeded %u %lu\n", + params_buffer_bytes(params), + (unsigned long)runtime->dma_addr); + } else { + snd_printd(KERN_INFO + "stream_host_buffer_attach error %d\n", + err); + return -ENOMEM; + } + + err = hpi_stream_get_info_ex(ss, dpcm->h_stream, NULL, + &dpcm->hpi_buffer_attached, + NULL, NULL, NULL); + + snd_printd(KERN_INFO "stream_host_buffer_attach status 0x%x\n", + dpcm->hpi_buffer_attached); + } + bytes_per_sec = params_rate(params) * params_channels(params); + bytes_per_sec *= snd_pcm_format_width(params_format(params)); + bytes_per_sec /= 8; + if (bytes_per_sec <= 0) + return -EINVAL; + + dpcm->bytes_per_sec = bytes_per_sec; + dpcm->pcm_size = params_buffer_bytes(params); + dpcm->pcm_count = params_period_bytes(params); + snd_printd(KERN_INFO "pcm_size=%d, pcm_count=%d, bps=%d\n", + dpcm->pcm_size, dpcm->pcm_count, bytes_per_sec); + + dpcm->pcm_irq_pos = 0; + dpcm->pcm_buf_pos = 0; + return 0; +} + +static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream * + substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + int expiry; + + expiry = (dpcm->pcm_count * HZ / dpcm->bytes_per_sec); + /* wait longer the first time, for samples to propagate */ + expiry = max(expiry, 20); + dpcm->timer.expires = jiffies + expiry; + dpcm->respawn_timer = 1; + add_timer(&dpcm->timer); +} + +static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + dpcm->respawn_timer = 0; + del_timer(&dpcm->timer); +} + +static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + u16 e; + + snd_printd("trigger %dstream %d\n", + substream->stream, substream->number); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_pcm_group_for_each_entry(s, substream) { + struct snd_card_asihpi_pcm *ds; + ds = s->runtime->private_data; + + if (snd_pcm_substream_chip(s) != card) + continue; + + if ((s->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (card->support_mmap)) { + /* How do I know how much valid data is present + * in buffer? Just guessing 2 periods, but if + * buffer is bigger it may contain even more + * data?? + */ + unsigned int preload = ds->pcm_count * 2; + VPRINTK2("preload %d\n", preload); + hpi_handle_error(hpi_outstream_write_buf( + ss, ds->h_stream, + &s->runtime->dma_area[0], + preload, + &ds->format)); + } + + if (card->support_grouping) { + VPRINTK1("\t_group %dstream %d\n", s->stream, + s->number); + e = hpi_stream_group_add(ss, + dpcm->h_stream, + ds->h_stream); + if (!e) { + snd_pcm_trigger_done(s, substream); + } else { + hpi_handle_error(e); + break; + } + } else + break; + } + snd_printd("start\n"); + /* start the master stream */ + snd_card_asihpi_pcm_timer_start(substream); + hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream)); + break; + + case SNDRV_PCM_TRIGGER_STOP: + snd_card_asihpi_pcm_timer_stop(substream); + snd_pcm_group_for_each_entry(s, substream) { + if (snd_pcm_substream_chip(s) != card) + continue; + + /*? workaround linked streams don't + transition to SETUP 20070706*/ + s->runtime->status->state = SNDRV_PCM_STATE_SETUP; + + if (card->support_grouping) { + VPRINTK1("\t_group %dstream %d\n", s->stream, + s->number); + snd_pcm_trigger_done(s, substream); + } else + break; + } + snd_printd("stop\n"); + + /* _prepare and _hwparams reset the stream */ + hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream)); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + hpi_handle_error( + hpi_outstream_reset(ss, dpcm->h_stream)); + + if (card->support_grouping) + hpi_handle_error(hpi_stream_group_reset(ss, + dpcm->h_stream)); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_printd("pause release\n"); + hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream)); + snd_card_asihpi_pcm_timer_start(substream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_printd("pause\n"); + snd_card_asihpi_pcm_timer_stop(substream); + hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream)); + break; + default: + snd_printd("\tINVALID\n"); + return -EINVAL; + } + + return 0; +} + +static int +snd_card_asihpi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + if (dpcm->hpi_buffer_attached) + hpi_stream_host_buffer_detach(ss, dpcm->h_stream); + + snd_pcm_lib_free_pages(substream); + return 0; +} + +static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime) +{ + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + kfree(dpcm); +} + +/*algorithm outline + Without linking degenerates to getting single stream pos etc + Without mmap 2nd loop degenerates to snd_pcm_period_elapsed +*/ +/* +buf_pos=get_buf_pos(s); +for_each_linked_stream(s) { + buf_pos=get_buf_pos(s); + min_buf_pos = modulo_min(min_buf_pos, buf_pos, pcm_size) + new_data = min(new_data, calc_new_data(buf_pos,irq_pos) +} +timer.expires = jiffies + predict_next_period_ready(min_buf_pos); +for_each_linked_stream(s) { + s->buf_pos = min_buf_pos; + if (new_data > pcm_count) { + if (mmap) { + irq_pos = (irq_pos + pcm_count) % pcm_size; + if (playback) { + write(pcm_count); + } else { + read(pcm_count); + } + } + snd_pcm_period_elapsed(s); + } +} +*/ + +/** Minimum of 2 modulo values. Works correctly when the difference between +* the values is less than half the modulus +*/ +static inline unsigned int modulo_min(unsigned int a, unsigned int b, + unsigned long int modulus) +{ + unsigned int result; + if (((a-b) % modulus) < (modulus/2)) + result = b; + else + result = a; + + return result; +} + +/** Timer function, equivalent to interrupt service routine for cards +*/ +static void snd_card_asihpi_timer_function(unsigned long data) +{ + struct snd_card_asihpi_pcm *dpcm = (struct snd_card_asihpi_pcm *)data; + struct snd_card_asihpi *card = snd_pcm_substream_chip(dpcm->substream); + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *s; + unsigned int newdata = 0; + unsigned int buf_pos, min_buf_pos = 0; + unsigned int remdata, xfercount, next_jiffies; + int first = 1; + u16 state; + u32 buffer_size, data_avail, samples_played, aux; + + /* find minimum newdata and buffer pos in group */ + snd_pcm_group_for_each_entry(s, dpcm->substream) { + struct snd_card_asihpi_pcm *ds = s->runtime->private_data; + runtime = s->runtime; + + if (snd_pcm_substream_chip(s) != card) + continue; + + hpi_handle_error(hpi_stream_get_info_ex(ss, + ds->h_stream, &state, + &buffer_size, &data_avail, + &samples_played, &aux)); + + /* number of bytes in on-card buffer */ + runtime->delay = aux; + + if (state == HPI_STATE_DRAINED) { + snd_printd(KERN_WARNING "outstream %d drained\n", + s->number); + snd_pcm_stop(s, SNDRV_PCM_STATE_XRUN); + return; + } + + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + buf_pos = frames_to_bytes(runtime, samples_played); + } else { + buf_pos = data_avail + ds->pcm_irq_pos; + } + + if (first) { + /* can't statically init min when wrap is involved */ + min_buf_pos = buf_pos; + newdata = (buf_pos - ds->pcm_irq_pos) % ds->pcm_size; + first = 0; + } else { + min_buf_pos = + modulo_min(min_buf_pos, buf_pos, UINT_MAX+1L); + newdata = min( + (buf_pos - ds->pcm_irq_pos) % ds->pcm_size, + newdata); + } + + VPRINTK1("PB timer hw_ptr x%04lX, appl_ptr x%04lX\n", + (unsigned long)frames_to_bytes(runtime, + runtime->status->hw_ptr), + (unsigned long)frames_to_bytes(runtime, + runtime->control->appl_ptr)); + VPRINTK1("%d S=%d, irq=%04X, pos=x%04X, left=x%04X," + " aux=x%04X space=x%04X\n", s->number, + state, ds->pcm_irq_pos, buf_pos, (int)data_avail, + (int)aux, buffer_size-data_avail); + } + + remdata = newdata % dpcm->pcm_count; + xfercount = newdata - remdata; /* a multiple of pcm_count */ + next_jiffies = ((dpcm->pcm_count-remdata) * HZ / dpcm->bytes_per_sec)+1; + next_jiffies = max(next_jiffies, 2U * HZ / 1000U); + dpcm->timer.expires = jiffies + next_jiffies; + VPRINTK1("jif %d buf pos x%04X newdata x%04X xc x%04X\n", + next_jiffies, min_buf_pos, newdata, xfercount); + + snd_pcm_group_for_each_entry(s, dpcm->substream) { + struct snd_card_asihpi_pcm *ds = s->runtime->private_data; + ds->pcm_buf_pos = min_buf_pos; + + if (xfercount) { + if (card->support_mmap) { + ds->pcm_irq_pos = ds->pcm_irq_pos + xfercount; + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + VPRINTK2("write OS%d x%04x\n", + s->number, + ds->pcm_count); + hpi_handle_error( + hpi_outstream_write_buf( + ss, ds->h_stream, + &s->runtime-> + dma_area[0], + xfercount, + &ds->format)); + } else { + VPRINTK2("read IS%d x%04x\n", + s->number, + dpcm->pcm_count); + hpi_handle_error( + hpi_instream_read_buf( + ss, ds->h_stream, + NULL, xfercount)); + } + } /* else R/W will be handled by read/write callbacks */ + snd_pcm_period_elapsed(s); + } + } + + if (dpcm->respawn_timer) + add_timer(&dpcm->timer); +} + +/***************************** PLAYBACK OPS ****************/ +static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + /* snd_printd(KERN_INFO "Playback ioctl %d\n", cmd); */ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream * + substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + snd_printd(KERN_INFO "playback prepare %d\n", substream->number); + + hpi_handle_error(hpi_outstream_reset(ss, dpcm->h_stream)); + dpcm->pcm_irq_pos = 0; + dpcm->pcm_buf_pos = 0; + + return 0; +} + +static snd_pcm_uframes_t +snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + snd_pcm_uframes_t ptr; + + u32 samples_played; + u16 err; + + if (!snd_pcm_stream_linked(substream)) { + /* NOTE, can use samples played for playback position here and + * in timer fn because it LAGS the actual read pointer, and is a + * better representation of actual playout position + */ + err = hpi_outstream_get_info_ex(ss, dpcm->h_stream, NULL, + NULL, NULL, + &samples_played, NULL); + hpi_handle_error(err); + + dpcm->pcm_buf_pos = frames_to_bytes(runtime, samples_played); + } + /* else must return most conservative value found in timer func + * by looping over all streams + */ + + ptr = bytes_to_frames(runtime, dpcm->pcm_buf_pos % dpcm->pcm_size); + VPRINTK2("playback_pointer=%04ld\n", (unsigned long)ptr); + return ptr; +} + +static void snd_card_asihpi_playback_format(struct snd_card_asihpi *asihpi, + u32 h_stream, + struct snd_pcm_hardware *pcmhw) +{ + struct hpi_format hpi_format; + u16 format; + u16 err; + u32 h_control; + u32 sample_rate = 48000; + + /* on cards without SRC, must query at valid rate, + * maybe set by external sync + */ + err = hpi_mixer_get_control(ss, asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_get_sample_rate(ss, h_control, + &sample_rate); + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + err = hpi_format_create(&hpi_format, + 2, format, sample_rate, 128000, 0); + if (!err) + err = hpi_outstream_query_format(ss, h_stream, + &hpi_format); + if (!err && (hpi_to_alsa_formats[format] != -1)) + pcmhw->formats |= + (1ULL << hpi_to_alsa_formats[format]); + } +} + +static struct snd_pcm_hardware snd_card_asihpi_playback = { + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 0, +}; + +static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + int err; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (dpcm == NULL) + return -ENOMEM; + + err = + hpi_outstream_open(ss, card->adapter_index, + substream->number, &dpcm->h_stream); + hpi_handle_error(err); + if (err) + kfree(dpcm); + if (err == HPI_ERROR_OBJ_ALREADY_OPEN) + return -EBUSY; + if (err) + return -EIO; + + /*? also check ASI5000 samplerate source + If external, only support external rate. + If internal and other stream playing, cant switch + */ + + init_timer(&dpcm->timer); + dpcm->timer.data = (unsigned long) dpcm; + dpcm->timer.function = snd_card_asihpi_timer_function; + dpcm->substream = substream; + runtime->private_data = dpcm; + runtime->private_free = snd_card_asihpi_runtime_free; + + snd_card_asihpi_playback.channels_max = card->out_max_chans; + /*?snd_card_asihpi_playback.period_bytes_min = + card->out_max_chans * 4096; */ + + snd_card_asihpi_playback_format(card, dpcm->h_stream, + &snd_card_asihpi_playback); + + snd_card_asihpi_pcm_samplerates(card, &snd_card_asihpi_playback); + + snd_card_asihpi_playback.info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE; + + if (card->support_mmap) + snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (card->support_grouping) + snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_SYNC_START; + + /* struct is copied, so can create initializer dynamically */ + runtime->hw = snd_card_asihpi_playback; + + if (card->support_mmap) + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames * 4, UINT_MAX); + + snd_pcm_set_sync(substream); + + snd_printd(KERN_INFO "playback open\n"); + + return 0; +} + +static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + hpi_handle_error(hpi_outstream_close(ss, dpcm->h_stream)); + snd_printd(KERN_INFO "playback close\n"); + + return 0; +} + +static int snd_card_asihpi_playback_copy(struct snd_pcm_substream *substream, + int channel, + snd_pcm_uframes_t pos, + void __user *src, + snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + unsigned int len; + + len = frames_to_bytes(runtime, count); + + if (copy_from_user(runtime->dma_area, src, len)) + return -EFAULT; + + VPRINTK2(KERN_DEBUG "playback copy%d %u bytes\n", + substream->number, len); + + hpi_handle_error(hpi_outstream_write_buf(ss, dpcm->h_stream, + runtime->dma_area, len, &dpcm->format)); + + return 0; +} + +static int snd_card_asihpi_playback_silence(struct snd_pcm_substream * + substream, int channel, + snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + unsigned int len; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + len = frames_to_bytes(runtime, count); + snd_printd(KERN_INFO "playback silence %u bytes\n", len); + + memset(runtime->dma_area, 0, len); + hpi_handle_error(hpi_outstream_write_buf(ss, dpcm->h_stream, + runtime->dma_area, len, &dpcm->format)); + return 0; +} + +static struct snd_pcm_ops snd_card_asihpi_playback_ops = { + .open = snd_card_asihpi_playback_open, + .close = snd_card_asihpi_playback_close, + .ioctl = snd_card_asihpi_playback_ioctl, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_playback_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_playback_pointer, + .copy = snd_card_asihpi_playback_copy, + .silence = snd_card_asihpi_playback_silence, +}; + +static struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = { + .open = snd_card_asihpi_playback_open, + .close = snd_card_asihpi_playback_close, + .ioctl = snd_card_asihpi_playback_ioctl, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_playback_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_playback_pointer, +}; + +/***************************** CAPTURE OPS ****************/ +static snd_pcm_uframes_t +snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + VPRINTK2("capture pointer %d=%d\n", + substream->number, dpcm->pcm_buf_pos); + /* NOTE Unlike playback can't use actual dwSamplesPlayed + for the capture position, because those samples aren't yet in + the local buffer available for reading. + */ + return bytes_to_frames(runtime, dpcm->pcm_buf_pos % dpcm->pcm_size); +} + +static int snd_card_asihpi_capture_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + + hpi_handle_error(hpi_instream_reset(ss, dpcm->h_stream)); + dpcm->pcm_irq_pos = 0; + dpcm->pcm_buf_pos = 0; + + snd_printd("capture prepare %d\n", substream->number); + return 0; +} + + + +static void snd_card_asihpi_capture_format(struct snd_card_asihpi *asihpi, + u32 h_stream, + struct snd_pcm_hardware *pcmhw) +{ + struct hpi_format hpi_format; + u16 format; + u16 err; + u32 h_control; + u32 sample_rate = 48000; + + /* on cards without SRC, must query at valid rate, + maybe set by external sync */ + err = hpi_mixer_get_control(ss, asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_get_sample_rate(ss, h_control, + &sample_rate); + + for (format = HPI_FORMAT_PCM8_UNSIGNED; + format <= HPI_FORMAT_PCM24_SIGNED; format++) { + + err = hpi_format_create(&hpi_format, 2, format, + sample_rate, 128000, 0); + if (!err) + err = hpi_instream_query_format(ss, h_stream, + &hpi_format); + if (!err) + pcmhw->formats |= + (1ULL << hpi_to_alsa_formats[format]); + } +} + + +static struct snd_pcm_hardware snd_card_asihpi_capture = { + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 0, +}; + +static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi *card = snd_pcm_substream_chip(substream); + struct snd_card_asihpi_pcm *dpcm; + int err; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (dpcm == NULL) + return -ENOMEM; + + snd_printd("hpi_instream_open adapter %d stream %d\n", + card->adapter_index, substream->number); + + err = hpi_handle_error( + hpi_instream_open(ss, card->adapter_index, + substream->number, &dpcm->h_stream)); + if (err) + kfree(dpcm); + if (err == HPI_ERROR_OBJ_ALREADY_OPEN) + return -EBUSY; + if (err) + return -EIO; + + + init_timer(&dpcm->timer); + dpcm->timer.data = (unsigned long) dpcm; + dpcm->timer.function = snd_card_asihpi_timer_function; + dpcm->substream = substream; + runtime->private_data = dpcm; + runtime->private_free = snd_card_asihpi_runtime_free; + + snd_card_asihpi_capture.channels_max = card->in_max_chans; + snd_card_asihpi_capture_format(card, dpcm->h_stream, + &snd_card_asihpi_capture); + snd_card_asihpi_pcm_samplerates(card, &snd_card_asihpi_capture); + snd_card_asihpi_capture.info = SNDRV_PCM_INFO_INTERLEAVED; + + if (card->support_mmap) + snd_card_asihpi_capture.info |= SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + runtime->hw = snd_card_asihpi_capture; + + if (card->support_mmap) + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + card->update_interval_frames * 2, UINT_MAX); + + snd_pcm_set_sync(substream); + + return 0; +} + +static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data; + + hpi_handle_error(hpi_instream_close(ss, dpcm->h_stream)); + return 0; +} + +static int snd_card_asihpi_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *dst, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_card_asihpi_pcm *dpcm = runtime->private_data; + u32 data_size; + + data_size = frames_to_bytes(runtime, count); + + VPRINTK2("capture copy%d %d bytes\n", substream->number, data_size); + hpi_handle_error(hpi_instream_read_buf(ss, dpcm->h_stream, + runtime->dma_area, data_size)); + + /* Used by capture_pointer */ + dpcm->pcm_irq_pos = dpcm->pcm_irq_pos + data_size; + + if (copy_to_user(dst, runtime->dma_area, data_size)) + return -EFAULT; + + return 0; +} + +static struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = { + .open = snd_card_asihpi_capture_open, + .close = snd_card_asihpi_capture_close, + .ioctl = snd_card_asihpi_capture_ioctl, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_capture_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_capture_pointer, +}; + +static struct snd_pcm_ops snd_card_asihpi_capture_ops = { + .open = snd_card_asihpi_capture_open, + .close = snd_card_asihpi_capture_close, + .ioctl = snd_card_asihpi_capture_ioctl, + .hw_params = snd_card_asihpi_pcm_hw_params, + .hw_free = snd_card_asihpi_hw_free, + .prepare = snd_card_asihpi_capture_prepare, + .trigger = snd_card_asihpi_trigger, + .pointer = snd_card_asihpi_capture_pointer, + .copy = snd_card_asihpi_capture_copy +}; + +static int __devinit snd_card_asihpi_pcm_new(struct snd_card_asihpi *asihpi, + int device, int substreams) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(asihpi->card, "asihpi PCM", device, + asihpi->num_outstreams, asihpi->num_instreams, + &pcm); + if (err < 0) + return err; + /* pointer to ops struct is stored, dont change ops afterwards! */ + if (asihpi->support_mmap) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_asihpi_playback_mmap_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_asihpi_capture_mmap_ops); + } else { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_asihpi_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_asihpi_capture_ops); + } + + pcm->private_data = asihpi; + pcm->info_flags = 0; + strcpy(pcm->name, "asihpi PCM"); + + /*? do we want to emulate MMAP for non-BBM cards? + Jack doesn't work with ALSAs MMAP emulation - WHY NOT? */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(asihpi->pci), + 64*1024, BUFFER_BYTES_MAX); + + return 0; +} + +/***************************** MIXER CONTROLS ****************/ +struct hpi_control { + u32 h_control; + u16 control_type; + u16 src_node_type; + u16 src_node_index; + u16 dst_node_type; + u16 dst_node_index; + u16 band; + char name[44]; /* copied to snd_ctl_elem_id.name[44]; */ +}; + +static char *asihpi_tuner_band_names[] = +{ + "invalid", + "AM", + "FM mono", + "TV NTSC-M", + "FM stereo", + "AUX", + "TV PAL BG", + "TV PAL I", + "TV PAL DK", + "TV SECAM", +}; + +compile_time_assert( + (ARRAY_SIZE(asihpi_tuner_band_names) == + (HPI_TUNER_BAND_LAST+1)), + assert_tuner_band_names_size); + +#if ASI_STYLE_NAMES +static char *asihpi_src_names[] = +{ + "no source", + "outstream", + "line_in", + "aes_in", + "tuner", + "RF", + "clock", + "bitstr", + "mic", + "cobranet", + "analog_in", + "adapter", +}; +#else +static char *asihpi_src_names[] = +{ + "no source", + "PCM playback", + "line in", + "digital in", + "tuner", + "RF", + "clock", + "bitstream", + "mic", + "cobranet in", + "analog in", + "adapter", +}; +#endif + +compile_time_assert( + (ARRAY_SIZE(asihpi_src_names) == + (HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_BASE+1)), + assert_src_names_size); + +#if ASI_STYLE_NAMES +static char *asihpi_dst_names[] = +{ + "no destination", + "instream", + "line_out", + "aes_out", + "RF", + "speaker" , + "cobranet", + "analog_out", +}; +#else +static char *asihpi_dst_names[] = +{ + "no destination", + "PCM capture", + "line out", + "digital out", + "RF", + "speaker", + "cobranet out", + "analog out" +}; +#endif + +compile_time_assert( + (ARRAY_SIZE(asihpi_dst_names) == + (HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_BASE+1)), + assert_dst_names_size); + +static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl, + struct snd_card_asihpi *asihpi) +{ + int err; + + err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi)); + if (err < 0) + return err; + else if (mixer_dump) + snd_printk(KERN_INFO "added %s(%d)\n", ctl->name, ctl->index); + + return 0; +} + +/* Convert HPI control name and location into ALSA control name */ +static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, + struct hpi_control *hpi_ctl, + char *name) +{ + memset(snd_control, 0, sizeof(*snd_control)); + snd_control->name = hpi_ctl->name; + snd_control->private_value = hpi_ctl->h_control; + snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_control->index = 0; + + if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type) + sprintf(hpi_ctl->name, "%s%d to %s%d %s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + name); + else if (hpi_ctl->dst_node_type) { + sprintf(hpi_ctl->name, "%s%d %s", + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + name); + } else { + sprintf(hpi_ctl->name, "%s%d %s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + name); + } +} + +/*------------------------------------------------------------ + Volume controls + ------------------------------------------------------------*/ +#define VOL_STEP_mB 1 +static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + /* native gains are in millibels */ + short min_gain_mB; + short max_gain_mB; + short step_gain_mB; + + err = hpi_volume_query_range(ss, h_control, + &min_gain_mB, &max_gain_mB, &step_gain_mB); + if (err) { + max_gain_mB = 0; + min_gain_mB = -10000; + step_gain_mB = VOL_STEP_mB; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = min_gain_mB / VOL_STEP_mB; + uinfo->value.integer.max = max_gain_mB / VOL_STEP_mB; + uinfo->value.integer.step = step_gain_mB / VOL_STEP_mB; + return 0; +} + +static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + hpi_handle_error(hpi_volume_get_gain(ss, h_control, an_gain_mB)); + ucontrol->value.integer.value[0] = an_gain_mB[0] / VOL_STEP_mB; + ucontrol->value.integer.value[1] = an_gain_mB[1] / VOL_STEP_mB; + + return 0; +} + +static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + an_gain_mB[0] = + (ucontrol->value.integer.value[0]) * VOL_STEP_mB; + an_gain_mB[1] = + (ucontrol->value.integer.value[1]) * VOL_STEP_mB; + /* change = asihpi->mixer_volume[addr][0] != left || + asihpi->mixer_volume[addr][1] != right; + */ + change = 1; + hpi_handle_error(hpi_volume_set_gain(ss, h_control, an_gain_mB)); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, VOL_STEP_mB, 0); + +static int __devinit snd_asihpi_volume_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "volume"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + snd_control.info = snd_asihpi_volume_info; + snd_control.get = snd_asihpi_volume_get; + snd_control.put = snd_asihpi_volume_put; + snd_control.tlv.p = db_scale_100; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Level controls + ------------------------------------------------------------*/ +static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + short min_gain_mB; + short max_gain_mB; + short step_gain_mB; + + err = + hpi_level_query_range(ss, h_control, &min_gain_mB, + &max_gain_mB, &step_gain_mB); + if (err) { + max_gain_mB = 2400; + min_gain_mB = -1000; + step_gain_mB = 100; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = min_gain_mB / HPI_UNITS_PER_dB; + uinfo->value.integer.max = max_gain_mB / HPI_UNITS_PER_dB; + uinfo->value.integer.step = step_gain_mB / HPI_UNITS_PER_dB; + return 0; +} + +static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + hpi_handle_error(hpi_level_get_gain(ss, h_control, an_gain_mB)); + ucontrol->value.integer.value[0] = + an_gain_mB[0] / HPI_UNITS_PER_dB; + ucontrol->value.integer.value[1] = + an_gain_mB[1] / HPI_UNITS_PER_dB; + + return 0; +} + +static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS]; + + an_gain_mB[0] = + (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; + an_gain_mB[1] = + (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB; + /* change = asihpi->mixer_level[addr][0] != left || + asihpi->mixer_level[addr][1] != right; + */ + change = 1; + hpi_handle_error(hpi_level_set_gain(ss, h_control, an_gain_mB)); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0); + +static int __devinit snd_asihpi_level_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + /* can't use 'volume' cos some nodes have volume as well */ + asihpi_ctl_init(&snd_control, hpi_ctl, "level"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + snd_control.info = snd_asihpi_level_info; + snd_control.get = snd_asihpi_level_get; + snd_control.put = snd_asihpi_level_put; + snd_control.tlv.p = db_scale_level; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + AESEBU controls + ------------------------------------------------------------*/ + +/* AESEBU format */ +static char *asihpi_aesebu_format_names[] = +{ + "N/A", + "S/PDIF", + "AES/EBU", +}; + +static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + strcpy(uinfo->value.enumerated.name, + asihpi_aesebu_format_names[uinfo->value.enumerated.item]); + + return 0; +} + +static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + u16 (*func)(const struct hpi_hsubsys *, u32, u16 *)) +{ + u32 h_control = kcontrol->private_value; + u16 source, err; + + err = func(ss, h_control, &source); + + /* default to N/A */ + ucontrol->value.enumerated.item[0] = 0; + /* return success but set the control to N/A */ + if (err) + return 0; + if (source == HPI_AESEBU_FORMAT_SPDIF) + ucontrol->value.enumerated.item[0] = 1; + if (source == HPI_AESEBU_FORMAT_AESEBU) + ucontrol->value.enumerated.item[0] = 2; + + return 0; +} + +static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + u16 (*func)(const struct hpi_hsubsys *, u32, u16)) +{ + u32 h_control = kcontrol->private_value; + + /* default to S/PDIF */ + u16 source = HPI_AESEBU_FORMAT_SPDIF; + + if (ucontrol->value.enumerated.item[0] == 1) + source = HPI_AESEBU_FORMAT_SPDIF; + if (ucontrol->value.enumerated.item[0] == 2) + source = HPI_AESEBU_FORMAT_AESEBU; + + if (func(ss, h_control, source) != 0) + return -EINVAL; + + return 1; +} + +static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, + HPI_AESEBU__receiver_get_format); +} + +static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, + HPI_AESEBU__receiver_set_format); +} + +static int snd_asihpi_aesebu_rxstatus_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0X1F; + uinfo->value.integer.step = 1; + + return 0; +} + +static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + + u32 h_control = kcontrol->private_value; + u16 status; + + hpi_handle_error(HPI_AESEBU__receiver_get_error_status( + ss, h_control, &status)); + ucontrol->value.integer.value[0] = status; + return 0; +} + +static int __devinit snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "format"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_aesebu_format_info; + snd_control.get = snd_asihpi_aesebu_rx_format_get; + snd_control.put = snd_asihpi_aesebu_rx_format_put; + + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + asihpi_ctl_init(&snd_control, hpi_ctl, "status"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_aesebu_rxstatus_info; + snd_control.get = snd_asihpi_aesebu_rxstatus_get; + + return ctl_add(card, &snd_control, asihpi); +} + +static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_get(kcontrol, ucontrol, + HPI_AESEBU__transmitter_get_format); +} + +static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + return snd_asihpi_aesebu_format_put(kcontrol, ucontrol, + HPI_AESEBU__transmitter_set_format); +} + + +static int __devinit snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "format"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_aesebu_format_info; + snd_control.get = snd_asihpi_aesebu_tx_format_get; + snd_control.put = snd_asihpi_aesebu_tx_format_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Tuner controls + ------------------------------------------------------------*/ + +/* Gain */ + +static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + short idx; + u16 gain_range[3]; + + for (idx = 0; idx < 3; idx++) { + err = hpi_tuner_query_gain(ss, h_control, + idx, &gain_range[idx]); + if (err != 0) + return err; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ((int)gain_range[0]) / HPI_UNITS_PER_dB; + uinfo->value.integer.max = ((int)gain_range[1]) / HPI_UNITS_PER_dB; + uinfo->value.integer.step = ((int) gain_range[2]) / HPI_UNITS_PER_dB; + return 0; +} + +static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + short gain; + + hpi_handle_error(hpi_tuner_get_gain(ss, h_control, &gain)); + ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB; + + return 0; +} + +static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + short gain; + + gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB; + hpi_handle_error(hpi_tuner_set_gain(ss, h_control, gain)); + + return 1; +} + +/* Band */ + +static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol, + u16 *band_list, u32 len) { + u32 h_control = kcontrol->private_value; + u16 err = 0; + u32 i; + + for (i = 0; i < len; i++) { + err = hpi_tuner_query_band(ss, + h_control, i, &band_list[i]); + if (err != 0) + break; + } + + if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX)) + return -EIO; + + return i; +} + +static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + int num_bands = 0; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + if (num_bands < 0) + return num_bands; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = num_bands; + + if (num_bands > 0) { + if (uinfo->value.enumerated.item >= + uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + strcpy(uinfo->value.enumerated.name, + asihpi_tuner_band_names[ + tuner_bands[uinfo->value.enumerated.item]]); + + } + return 0; +} + +static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u16 band, idx; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + u32 num_bands = 0; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + hpi_handle_error(hpi_tuner_get_band(ss, h_control, &band)); + + ucontrol->value.enumerated.item[0] = -1; + for (idx = 0; idx < HPI_TUNER_BAND_LAST; idx++) + if (tuner_bands[idx] == band) { + ucontrol->value.enumerated.item[0] = idx; + break; + } + + return 0; +} + +static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol); + */ + u32 h_control = kcontrol->private_value; + u16 band; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + u32 num_bands = 0; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + band = tuner_bands[ucontrol->value.enumerated.item[0]]; + hpi_handle_error(hpi_tuner_set_band(ss, h_control, band)); + + return 1; +} + +/* Freq */ + +static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + u32 h_control = kcontrol->private_value; + u16 err; + u16 tuner_bands[HPI_TUNER_BAND_LAST]; + u16 num_bands = 0, band_iter, idx; + u32 freq_range[3], temp_freq_range[3]; + + num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands, + HPI_TUNER_BAND_LAST); + + freq_range[0] = INT_MAX; + freq_range[1] = 0; + freq_range[2] = INT_MAX; + + for (band_iter = 0; band_iter < num_bands; band_iter++) { + for (idx = 0; idx < 3; idx++) { + err = hpi_tuner_query_frequency(ss, h_control, + idx, tuner_bands[band_iter], + &temp_freq_range[idx]); + if (err != 0) + return err; + } + + /* skip band with bogus stepping */ + if (temp_freq_range[2] <= 0) + continue; + + if (temp_freq_range[0] < freq_range[0]) + freq_range[0] = temp_freq_range[0]; + if (temp_freq_range[1] > freq_range[1]) + freq_range[1] = temp_freq_range[1]; + if (temp_freq_range[2] < freq_range[2]) + freq_range[2] = temp_freq_range[2]; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ((int)freq_range[0]); + uinfo->value.integer.max = ((int)freq_range[1]); + uinfo->value.integer.step = ((int)freq_range[2]); + return 0; +} + +static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 freq; + + hpi_handle_error(hpi_tuner_get_frequency(ss, h_control, &freq)); + ucontrol->value.integer.value[0] = freq; + + return 0; +} + +static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 freq; + + freq = ucontrol->value.integer.value[0]; + hpi_handle_error(hpi_tuner_set_frequency(ss, h_control, freq)); + + return 1; +} + +/* Tuner control group initializer */ +static int __devinit snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + snd_control.private_value = hpi_ctl->h_control; + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + + if (!hpi_tuner_get_gain(ss, hpi_ctl->h_control, NULL)) { + asihpi_ctl_init(&snd_control, hpi_ctl, "gain"); + snd_control.info = snd_asihpi_tuner_gain_info; + snd_control.get = snd_asihpi_tuner_gain_get; + snd_control.put = snd_asihpi_tuner_gain_put; + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + } + + asihpi_ctl_init(&snd_control, hpi_ctl, "band"); + snd_control.info = snd_asihpi_tuner_band_info; + snd_control.get = snd_asihpi_tuner_band_get; + snd_control.put = snd_asihpi_tuner_band_put; + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + asihpi_ctl_init(&snd_control, hpi_ctl, "freq"); + snd_control.info = snd_asihpi_tuner_freq_info; + snd_control.get = snd_asihpi_tuner_freq_get; + snd_control.put = snd_asihpi_tuner_freq_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Meter controls + ------------------------------------------------------------*/ +static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = HPI_MAX_CHANNELS; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x7FFFFFFF; + return 0; +} + +/* linear values for 10dB steps */ +static int log2lin[] = { + 0x7FFFFFFF, /* 0dB */ + 679093956, + 214748365, + 67909396, + 21474837, + 6790940, + 2147484, /* -60dB */ + 679094, + 214748, /* -80 */ + 67909, + 21475, /* -100 */ + 6791, + 2147, + 679, + 214, + 68, + 21, + 7, + 2 +}; + +static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + short an_gain_mB[HPI_MAX_CHANNELS], i; + u16 err; + + err = hpi_meter_get_peak(ss, h_control, an_gain_mB); + + for (i = 0; i < HPI_MAX_CHANNELS; i++) { + if (err) { + ucontrol->value.integer.value[i] = 0; + } else if (an_gain_mB[i] >= 0) { + ucontrol->value.integer.value[i] = + an_gain_mB[i] << 16; + } else { + /* -ve is log value in millibels < -60dB, + * convert to (roughly!) linear, + */ + ucontrol->value.integer.value[i] = + log2lin[an_gain_mB[i] / -1000]; + } + } + return 0; +} + +static int __devinit snd_asihpi_meter_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl, int subidx) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "meter"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_meter_info; + snd_control.get = snd_asihpi_meter_get; + + snd_control.index = subidx; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Multiplexer controls + ------------------------------------------------------------*/ +static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control) +{ + u32 h_control = snd_control->private_value; + struct hpi_control hpi_ctl; + int s, err; + for (s = 0; s < 32; s++) { + err = hpi_multiplexer_query_source(ss, h_control, s, + &hpi_ctl. + src_node_type, + &hpi_ctl. + src_node_index); + if (err) + break; + } + return s; +} + +static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int err; + u16 src_node_type, src_node_index; + u32 h_control = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = + snd_card_asihpi_mux_count_sources(kcontrol); + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + err = + hpi_multiplexer_query_source(ss, h_control, + uinfo->value.enumerated.item, + &src_node_type, &src_node_index); + + sprintf(uinfo->value.enumerated.name, "%s %d", + asihpi_src_names[src_node_type - HPI_SOURCENODE_BASE], + src_node_index); + return 0; +} + +static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u16 source_type, source_index; + u16 src_node_type, src_node_index; + int s; + + hpi_handle_error(hpi_multiplexer_get_source(ss, h_control, + &source_type, &source_index)); + /* Should cache this search result! */ + for (s = 0; s < 256; s++) { + if (hpi_multiplexer_query_source(ss, h_control, s, + &src_node_type, &src_node_index)) + break; + + if ((source_type == src_node_type) + && (source_index == src_node_index)) { + ucontrol->value.enumerated.item[0] = s; + return 0; + } + } + snd_printd(KERN_WARNING + "control %x failed to match mux source %hu %hu\n", + h_control, source_type, source_index); + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + u16 source_type, source_index; + u16 e; + + change = 1; + + e = hpi_multiplexer_query_source(ss, h_control, + ucontrol->value.enumerated.item[0], + &source_type, &source_index); + if (!e) + hpi_handle_error( + hpi_multiplexer_set_source(ss, h_control, + source_type, source_index)); + return change; +} + + +static int __devinit snd_asihpi_mux_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + +#if ASI_STYLE_NAMES + asihpi_ctl_init(&snd_control, hpi_ctl, "multiplexer"); +#else + asihpi_ctl_init(&snd_control, hpi_ctl, "route"); +#endif + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_mux_info; + snd_control.get = snd_asihpi_mux_get; + snd_control.put = snd_asihpi_mux_put; + + return ctl_add(card, &snd_control, asihpi); + +} + +/*------------------------------------------------------------ + Channel mode controls + ------------------------------------------------------------*/ +static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *mode_names[HPI_CHANNEL_MODE_LAST] = { + "normal", "swap", + "from_left", "from_right", + "to_left", "to_right" + }; + + u32 h_control = kcontrol->private_value; + u16 mode; + int i; + + /* HPI channel mode values can be from 1 to 6 + Some adapters only support a contiguous subset + */ + for (i = 0; i < HPI_CHANNEL_MODE_LAST; i++) + if (hpi_channel_mode_query_mode( + ss, h_control, i, &mode)) + break; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = i; + + if (uinfo->value.enumerated.item >= i) + uinfo->value.enumerated.item = i - 1; + + strcpy(uinfo->value.enumerated.name, + mode_names[uinfo->value.enumerated.item]); + + return 0; +} + +static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u16 mode; + + if (hpi_channel_mode_get(ss, h_control, &mode)) + mode = 1; + + ucontrol->value.enumerated.item[0] = mode - 1; + + return 0; +} + +static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + + change = 1; + + hpi_handle_error(hpi_channel_mode_set(ss, h_control, + ucontrol->value.enumerated.item[0] + 1)); + return change; +} + + +static int __devinit snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + asihpi_ctl_init(&snd_control, hpi_ctl, "channel mode"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_control.info = snd_asihpi_cmode_info; + snd_control.get = snd_asihpi_cmode_get; + snd_control.put = snd_asihpi_cmode_put; + + return ctl_add(card, &snd_control, asihpi); +} + +/*------------------------------------------------------------ + Sampleclock source controls + ------------------------------------------------------------*/ + +static char *sampleclock_sources[MAX_CLOCKSOURCES] = + { "N/A", "local PLL", "AES/EBU sync", "word external", "word header", + "SMPTE", "AES/EBU in1", "auto", "network", "invalid", + "prev module", + "AES/EBU in2", "AES/EBU in3", "AES/EBU in4", "AES/EBU in5", + "AES/EBU in6", "AES/EBU in7", "AES/EBU in8"}; + + + +static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_card_asihpi *asihpi = + (struct snd_card_asihpi *)(kcontrol->private_data); + struct clk_cache *clkcache = &asihpi->cc; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = clkcache->count; + + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + strcpy(uinfo->value.enumerated.name, + clkcache->s[uinfo->value.enumerated.item].name); + return 0; +} + +static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_asihpi *asihpi = + (struct snd_card_asihpi *)(kcontrol->private_data); + struct clk_cache *clkcache = &asihpi->cc; + u32 h_control = kcontrol->private_value; + u16 source, srcindex = 0; + int i; + + ucontrol->value.enumerated.item[0] = 0; + if (hpi_sample_clock_get_source(ss, h_control, &source)) + source = 0; + + if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + if (hpi_sample_clock_get_source_index(ss, h_control, &srcindex)) + srcindex = 0; + + for (i = 0; i < clkcache->count; i++) + if ((clkcache->s[i].source == source) && + (clkcache->s[i].index == srcindex)) + break; + + ucontrol->value.enumerated.item[0] = i; + + return 0; +} + +static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_asihpi *asihpi = + (struct snd_card_asihpi *)(kcontrol->private_data); + struct clk_cache *clkcache = &asihpi->cc; + int change, item; + u32 h_control = kcontrol->private_value; + + change = 1; + item = ucontrol->value.enumerated.item[0]; + if (item >= clkcache->count) + item = clkcache->count-1; + + hpi_handle_error(hpi_sample_clock_set_source(ss, + h_control, clkcache->s[item].source)); + + if (clkcache->s[item].source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + hpi_handle_error(hpi_sample_clock_set_source_index(ss, + h_control, clkcache->s[item].index)); + return change; +} + +/*------------------------------------------------------------ + Clkrate controls + ------------------------------------------------------------*/ +/* Need to change this to enumerated control with list of rates */ +static int snd_asihpi_clklocal_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 8000; + uinfo->value.integer.max = 192000; + uinfo->value.integer.step = 100; + + return 0; +} + +static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 rate; + u16 e; + + e = hpi_sample_clock_get_local_rate(ss, h_control, &rate); + if (!e) + ucontrol->value.integer.value[0] = rate; + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + u32 h_control = kcontrol->private_value; + + /* change = asihpi->mixer_clkrate[addr][0] != left || + asihpi->mixer_clkrate[addr][1] != right; + */ + change = 1; + hpi_handle_error(hpi_sample_clock_set_local_rate(ss, h_control, + ucontrol->value.integer.value[0])); + return change; +} + +static int snd_asihpi_clkrate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 8000; + uinfo->value.integer.max = 192000; + uinfo->value.integer.step = 100; + + return 0; +} + +static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 h_control = kcontrol->private_value; + u32 rate; + u16 e; + + e = hpi_sample_clock_get_sample_rate(ss, h_control, &rate); + if (!e) + ucontrol->value.integer.value[0] = rate; + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int __devinit snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi, + struct hpi_control *hpi_ctl) +{ + struct snd_card *card = asihpi->card; + struct snd_kcontrol_new snd_control; + + struct clk_cache *clkcache = &asihpi->cc; + u32 hSC = hpi_ctl->h_control; + int has_aes_in = 0; + int i, j; + u16 source; + + snd_control.private_value = hpi_ctl->h_control; + + clkcache->has_local = 0; + + for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) { + if (hpi_sample_clock_query_source(ss, hSC, + i, &source)) + break; + clkcache->s[i].source = source; + clkcache->s[i].index = 0; + clkcache->s[i].name = sampleclock_sources[source]; + if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT) + has_aes_in = 1; + if (source == HPI_SAMPLECLOCK_SOURCE_LOCAL) + clkcache->has_local = 1; + } + if (has_aes_in) + /* already will have picked up index 0 above */ + for (j = 1; j < 8; j++) { + if (hpi_sample_clock_query_source_index(ss, hSC, + j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT, + &source)) + break; + clkcache->s[i].source = + HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT; + clkcache->s[i].index = j; + clkcache->s[i].name = sampleclock_sources[ + j+HPI_SAMPLECLOCK_SOURCE_LAST]; + i++; + } + clkcache->count = i; + + asihpi_ctl_init(&snd_control, hpi_ctl, "source"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; + snd_control.info = snd_asihpi_clksrc_info; + snd_control.get = snd_asihpi_clksrc_get; + snd_control.put = snd_asihpi_clksrc_put; + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + + + if (clkcache->has_local) { + asihpi_ctl_init(&snd_control, hpi_ctl, "local_rate"); + snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ; + snd_control.info = snd_asihpi_clklocal_info; + snd_control.get = snd_asihpi_clklocal_get; + snd_control.put = snd_asihpi_clklocal_put; + + + if (ctl_add(card, &snd_control, asihpi) < 0) + return -EINVAL; + } + + asihpi_ctl_init(&snd_control, hpi_ctl, "rate"); + snd_control.access = + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ; + snd_control.info = snd_asihpi_clkrate_info; + snd_control.get = snd_asihpi_clkrate_get; + + return ctl_add(card, &snd_control, asihpi); +} +/*------------------------------------------------------------ + Mixer + ------------------------------------------------------------*/ + +static int __devinit snd_card_asihpi_mixer_new(struct snd_card_asihpi *asihpi) +{ + struct snd_card *card = asihpi->card; + unsigned int idx = 0; + unsigned int subindex = 0; + int err; + struct hpi_control hpi_ctl, prev_ctl; + + if (snd_BUG_ON(!asihpi)) + return -EINVAL; + strcpy(card->mixername, "asihpi mixer"); + + err = + hpi_mixer_open(ss, asihpi->adapter_index, + &asihpi->h_mixer); + hpi_handle_error(err); + if (err) + return -err; + + for (idx = 0; idx < 2000; idx++) { + err = hpi_mixer_get_control_by_index( + ss, asihpi->h_mixer, + idx, + &hpi_ctl.src_node_type, + &hpi_ctl.src_node_index, + &hpi_ctl.dst_node_type, + &hpi_ctl.dst_node_index, + &hpi_ctl.control_type, + &hpi_ctl.h_control); + if (err) { + if (err == HPI_ERROR_CONTROL_DISABLED) { + if (mixer_dump) + snd_printk(KERN_INFO + "disabled HPI control(%d)\n", + idx); + continue; + } else + break; + + } + + hpi_ctl.src_node_type -= HPI_SOURCENODE_BASE; + hpi_ctl.dst_node_type -= HPI_DESTNODE_BASE; + + /* ASI50xx in SSX mode has multiple meters on the same node. + Use subindex to create distinct ALSA controls + for any duplicated controls. + */ + if ((hpi_ctl.control_type == prev_ctl.control_type) && + (hpi_ctl.src_node_type == prev_ctl.src_node_type) && + (hpi_ctl.src_node_index == prev_ctl.src_node_index) && + (hpi_ctl.dst_node_type == prev_ctl.dst_node_type) && + (hpi_ctl.dst_node_index == prev_ctl.dst_node_index)) + subindex++; + else + subindex = 0; + + prev_ctl = hpi_ctl; + + switch (hpi_ctl.control_type) { + case HPI_CONTROL_VOLUME: + err = snd_asihpi_volume_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_LEVEL: + err = snd_asihpi_level_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_MULTIPLEXER: + err = snd_asihpi_mux_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_CHANNEL_MODE: + err = snd_asihpi_cmode_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_METER: + err = snd_asihpi_meter_add(asihpi, &hpi_ctl, subindex); + break; + case HPI_CONTROL_SAMPLECLOCK: + err = snd_asihpi_sampleclock_add( + asihpi, &hpi_ctl); + break; + case HPI_CONTROL_CONNECTION: /* ignore these */ + continue; + case HPI_CONTROL_TUNER: + err = snd_asihpi_tuner_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + err = snd_asihpi_aesebu_tx_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_AESEBU_RECEIVER: + err = snd_asihpi_aesebu_rx_add(asihpi, &hpi_ctl); + break; + case HPI_CONTROL_VOX: + case HPI_CONTROL_BITSTREAM: + case HPI_CONTROL_MICROPHONE: + case HPI_CONTROL_PARAMETRIC_EQ: + case HPI_CONTROL_COMPANDER: + default: + if (mixer_dump) + snd_printk(KERN_INFO + "untranslated HPI control" + "(%d) %d %d %d %d %d\n", + idx, + hpi_ctl.control_type, + hpi_ctl.src_node_type, + hpi_ctl.src_node_index, + hpi_ctl.dst_node_type, + hpi_ctl.dst_node_index); + continue; + }; + if (err < 0) + return err; + } + if (HPI_ERROR_INVALID_OBJ_INDEX != err) + hpi_handle_error(err); + + snd_printk(KERN_INFO "%d mixer controls found\n", idx); + + return 0; +} + +/*------------------------------------------------------------ + /proc interface + ------------------------------------------------------------*/ + +static void +snd_asihpi_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_card_asihpi *asihpi = entry->private_data; + u16 version; + u32 h_control; + u32 rate = 0; + u16 source = 0; + int err; + + snd_iprintf(buffer, "ASIHPI driver proc file\n"); + snd_iprintf(buffer, + "adapter ID=%4X\n_index=%d\n" + "num_outstreams=%d\n_num_instreams=%d\n", + asihpi->type, asihpi->adapter_index, + asihpi->num_outstreams, asihpi->num_instreams); + + version = asihpi->version; + snd_iprintf(buffer, + "serial#=%d\n_hw version %c%d\nDSP code version %03d\n", + asihpi->serial_number, ((version >> 3) & 0xf) + 'A', + version & 0x7, + ((version >> 13) * 100) + ((version >> 7) & 0x3f)); + + err = hpi_mixer_get_control(ss, asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) { + err = hpi_sample_clock_get_sample_rate(ss, + h_control, &rate); + err += hpi_sample_clock_get_source(ss, h_control, &source); + + if (!err) + snd_iprintf(buffer, "sample_clock=%d_hz, source %s\n", + rate, sampleclock_sources[source]); + } + +} + + +static void __devinit snd_asihpi_proc_init(struct snd_card_asihpi *asihpi) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(asihpi->card, "info", &entry)) + snd_info_set_text_ops(entry, asihpi, snd_asihpi_proc_read); +} + +/*------------------------------------------------------------ + HWDEP + ------------------------------------------------------------*/ + +static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file) +{ + if (enable_hpi_hwdep) + return 0; + else + return -ENODEV; + +} + +static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file) +{ + if (enable_hpi_hwdep) + return asihpi_hpi_release(file); + else + return -ENODEV; +} + +static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + if (enable_hpi_hwdep) + return asihpi_hpi_ioctl(file, cmd, arg); + else + return -ENODEV; +} + + +/* results in /dev/snd/hwC#D0 file for each card with index # + also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card' +*/ +static int __devinit snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi, + int device, struct snd_hwdep **rhwdep) +{ + struct snd_hwdep *hw; + int err; + + if (rhwdep) + *rhwdep = NULL; + err = snd_hwdep_new(asihpi->card, "HPI", device, &hw); + if (err < 0) + return err; + strcpy(hw->name, "asihpi (HPI)"); + hw->iface = SNDRV_HWDEP_IFACE_LAST; + hw->ops.open = snd_asihpi_hpi_open; + hw->ops.ioctl = snd_asihpi_hpi_ioctl; + hw->ops.release = snd_asihpi_hpi_release; + hw->private_data = asihpi; + if (rhwdep) + *rhwdep = hw; + return 0; +} + +/*------------------------------------------------------------ + CARD + ------------------------------------------------------------*/ +static int __devinit snd_asihpi_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int err; + + u16 version; + int pcm_substreams; + + struct hpi_adapter *hpi_card; + struct snd_card *card; + struct snd_card_asihpi *asihpi; + + u32 h_control; + u32 h_stream; + + static int dev; + if (dev >= SNDRV_CARDS) + return -ENODEV; + + /* Should this be enable[hpi_card->index] ? */ + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = asihpi_adapter_probe(pci_dev, pci_id); + if (err < 0) + return err; + + hpi_card = pci_get_drvdata(pci_dev); + /* first try to give the card the same index as its hardware index */ + err = snd_card_create(hpi_card->index, + id[hpi_card->index], THIS_MODULE, + sizeof(struct snd_card_asihpi), + &card); + if (err < 0) { + /* if that fails, try the default index==next available */ + err = + snd_card_create(index[dev], id[dev], + THIS_MODULE, + sizeof(struct snd_card_asihpi), + &card); + if (err < 0) + return err; + snd_printk(KERN_WARNING + "**** WARNING **** adapter index %d->ALSA index %d\n", + hpi_card->index, card->number); + } + + asihpi = (struct snd_card_asihpi *) card->private_data; + asihpi->card = card; + asihpi->pci = hpi_card->pci; + asihpi->adapter_index = hpi_card->index; + hpi_handle_error(hpi_adapter_get_info(ss, + asihpi->adapter_index, + &asihpi->num_outstreams, + &asihpi->num_instreams, + &asihpi->version, + &asihpi->serial_number, &asihpi->type)); + + version = asihpi->version; + snd_printk(KERN_INFO "adapter ID=%4X index=%d num_outstreams=%d " + "num_instreams=%d S/N=%d\n" + "hw version %c%d DSP code version %03d\n", + asihpi->type, asihpi->adapter_index, + asihpi->num_outstreams, + asihpi->num_instreams, asihpi->serial_number, + ((version >> 3) & 0xf) + 'A', + version & 0x7, + ((version >> 13) * 100) + ((version >> 7) & 0x3f)); + + pcm_substreams = asihpi->num_outstreams; + if (pcm_substreams < asihpi->num_instreams) + pcm_substreams = asihpi->num_instreams; + + err = hpi_adapter_get_property(ss, asihpi->adapter_index, + HPI_ADAPTER_PROPERTY_CAPS1, + NULL, &asihpi->support_grouping); + if (err) + asihpi->support_grouping = 0; + + err = hpi_adapter_get_property(ss, asihpi->adapter_index, + HPI_ADAPTER_PROPERTY_CAPS2, + &asihpi->support_mrx, NULL); + if (err) + asihpi->support_mrx = 0; + + err = hpi_adapter_get_property(ss, asihpi->adapter_index, + HPI_ADAPTER_PROPERTY_INTERVAL, + NULL, &asihpi->update_interval_frames); + if (err) + asihpi->update_interval_frames = 512; + + hpi_handle_error(hpi_instream_open(ss, asihpi->adapter_index, + 0, &h_stream)); + + err = hpi_instream_host_buffer_free(ss, h_stream); + asihpi->support_mmap = (!err); + + hpi_handle_error(hpi_instream_close(ss, h_stream)); + + err = hpi_adapter_get_property(ss, asihpi->adapter_index, + HPI_ADAPTER_PROPERTY_CURCHANNELS, + &asihpi->in_max_chans, &asihpi->out_max_chans); + if (err) { + asihpi->in_max_chans = 2; + asihpi->out_max_chans = 2; + } + + snd_printk(KERN_INFO "supports mmap:%d grouping:%d mrx:%d\n", + asihpi->support_mmap, + asihpi->support_grouping, + asihpi->support_mrx + ); + + + err = snd_card_asihpi_pcm_new(asihpi, 0, pcm_substreams); + if (err < 0) { + snd_printk(KERN_ERR "pcm_new failed\n"); + goto __nodev; + } + err = snd_card_asihpi_mixer_new(asihpi); + if (err < 0) { + snd_printk(KERN_ERR "mixer_new failed\n"); + goto __nodev; + } + + err = hpi_mixer_get_control(ss, asihpi->h_mixer, + HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0, + HPI_CONTROL_SAMPLECLOCK, &h_control); + + if (!err) + err = hpi_sample_clock_set_local_rate( + ss, h_control, adapter_fs); + + snd_asihpi_proc_init(asihpi); + + /* always create, can be enabled or disabled dynamically + by enable_hwdep module param*/ + snd_asihpi_hpi_new(asihpi, 0, NULL); + + if (asihpi->support_mmap) + strcpy(card->driver, "ASIHPI-MMAP"); + else + strcpy(card->driver, "ASIHPI"); + + sprintf(card->shortname, "AudioScience ASI%4X", asihpi->type); + sprintf(card->longname, "%s %i", + card->shortname, asihpi->adapter_index); + err = snd_card_register(card); + if (!err) { + hpi_card->snd_card_asihpi = card; + dev++; + return 0; + } +__nodev: + snd_card_free(card); + snd_printk(KERN_ERR "snd_asihpi_probe error %d\n", err); + return err; + +} + +static void __devexit snd_asihpi_remove(struct pci_dev *pci_dev) +{ + struct hpi_adapter *hpi_card = pci_get_drvdata(pci_dev); + + snd_card_free(hpi_card->snd_card_asihpi); + hpi_card->snd_card_asihpi = NULL; + asihpi_adapter_remove(pci_dev); +} + +static DEFINE_PCI_DEVICE_TABLE(asihpi_pci_tbl) = { + {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, + (kernel_ulong_t)HPI_6205}, + {HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, + (kernel_ulong_t)HPI_6000}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl); + +static struct pci_driver driver = { + .name = "asihpi", + .id_table = asihpi_pci_tbl, + .probe = snd_asihpi_probe, + .remove = __devexit_p(snd_asihpi_remove), +#ifdef CONFIG_PM +/* .suspend = snd_asihpi_suspend, + .resume = snd_asihpi_resume, */ +#endif +}; + +static int __init snd_asihpi_init(void) +{ + asihpi_init(); + return pci_register_driver(&driver); +} + +static void __exit snd_asihpi_exit(void) +{ + + pci_unregister_driver(&driver); + asihpi_exit(); +} + +module_init(snd_asihpi_init) +module_exit(snd_asihpi_exit) + diff --git a/sound/pci/asihpi/hpi.h b/sound/pci/asihpi/hpi.h new file mode 100644 index 00000000000..99400de6c07 --- /dev/null +++ b/sound/pci/asihpi/hpi.h @@ -0,0 +1,2001 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +*/ +/** \file hpi.h + + AudioScience Hardware Programming Interface (HPI) + public API definition. + + The HPI is a low-level hardware abstraction layer to all + AudioScience digital audio adapters +*/ +/* + You must define one operating system that the HPI is to be compiled under + HPI_OS_WIN32_USER 32bit Windows + HPI_OS_DSP_C6000 DSP TI C6000 (automatically set) + HPI_OS_WDM Windows WDM kernel driver + HPI_OS_LINUX Linux userspace + HPI_OS_LINUX_KERNEL Linux kernel (automatically set) + +(C) Copyright AudioScience Inc. 1998-2010 +******************************************************************************/ +#ifndef _HPI_H_ +#define _HPI_H_ +/* HPI Version +If HPI_VER_MINOR is odd then its a development release not intended for the +public. If HPI_VER_MINOR is even then is a release version +i.e 3.05.02 is a development version +*/ +#define HPI_VERSION_CONSTRUCTOR(maj, min, rel) \ + ((maj << 16) + (min << 8) + rel) + +#define HPI_VER_MAJOR(v) ((int)(v >> 16)) +#define HPI_VER_MINOR(v) ((int)((v >> 8) & 0xFF)) +#define HPI_VER_RELEASE(v) ((int)(v & 0xFF)) + +/* Use single digits for versions less that 10 to avoid octal. */ +#define HPI_VER HPI_VERSION_CONSTRUCTOR(4L, 3, 18) + +/* Library version as documented in hpi-api-versions.txt */ +#define HPI_LIB_VER HPI_VERSION_CONSTRUCTOR(9, 0, 0) + +#include +#define HPI_EXCLUDE_DEPRECATED + +/******************************************************************************/ +/******************************************************************************/ +/******** HPI API DEFINITIONS *****/ +/******************************************************************************/ +/******************************************************************************/ +/*******************************************/ +/** Audio format types +\ingroup stream +*/ +enum HPI_FORMATS { +/** Used internally on adapter. */ + HPI_FORMAT_MIXER_NATIVE = 0, +/** 8-bit unsigned PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM8_UNSIGNED = 1, +/** 16-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM16_SIGNED = 2, +/** MPEG-1 Layer-1. */ + HPI_FORMAT_MPEG_L1 = 3, +/** MPEG-1 Layer-2. + +Windows equivalent is WAVE_FORMAT_MPEG. + +The following table shows what combinations of mode and bitrate are possible: + + + +

Bitrate (kbs)

+

Mono

+

Stereo,
Joint Stereo or
Dual Channel

+ +
32X_ +
40__ +
48X_ +
56X_ +
64XX +
80X_ +
96XX +
112XX +
128XX +
160XX +
192XX +
224_X +
256-X +
320-X +
384_X +
+*/ + HPI_FORMAT_MPEG_L2 = 4, +/** MPEG-1 Layer-3. +Windows equivalent is WAVE_FORMAT_MPEG. + +The following table shows what combinations of mode and bitrate are possible: + + + +

Bitrate (kbs)

+

Mono
Stereo @ 8,
11.025 and
12kHz*

+

Mono
Stereo @ 16,
22.050 and
24kHz*

+

Mono
Stereo @ 32,
44.1 and
48kHz

+ +
16XX_ +
24XX_ +
32XXX +
40XXX +
48XXX +
56XXX +
64XXX +
80_XX +
96_XX +
112_XX +
128_XX +
144_X_ +
160_XX +
192__X +
224__X +
256-_X +
320-_X +
+\b * Available on the ASI6000 series only +*/ + HPI_FORMAT_MPEG_L3 = 5, +/** Dolby AC-2. */ + HPI_FORMAT_DOLBY_AC2 = 6, +/** Dolbt AC-3. */ + HPI_FORMAT_DOLBY_AC3 = 7, +/** 16-bit PCM big-endian. */ + HPI_FORMAT_PCM16_BIGENDIAN = 8, +/** TAGIT-1 algorithm - hits. */ + HPI_FORMAT_AA_TAGIT1_HITS = 9, +/** TAGIT-1 algorithm - inserts. */ + HPI_FORMAT_AA_TAGIT1_INSERTS = 10, +/** 32-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. +Each sample is a 32bit word. The most significant 24 bits contain a 24-bit +sample and the least significant 8 bits are set to 0. +*/ + HPI_FORMAT_PCM32_SIGNED = 11, +/** Raw bitstream - unknown format. */ + HPI_FORMAT_RAW_BITSTREAM = 12, +/** TAGIT-1 algorithm hits - extended. */ + HPI_FORMAT_AA_TAGIT1_HITS_EX1 = 13, +/** 32-bit PCM as an IEEE float. Windows equivalent is WAVE_FORMAT_IEEE_FLOAT. +Each sample is a 32bit word in IEEE754 floating point format. +The range is +1.0 to -1.0, which corresponds to digital fullscale. +*/ + HPI_FORMAT_PCM32_FLOAT = 14, +/** 24-bit PCM signed. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM24_SIGNED = 15, +/** OEM format 1 - private. */ + HPI_FORMAT_OEM1 = 16, +/** OEM format 2 - private. */ + HPI_FORMAT_OEM2 = 17, +/** Undefined format. */ + HPI_FORMAT_UNDEFINED = 0xffff +}; + +/******************************************* in/out Stream states */ +/*******************************************/ +/** Stream States +\ingroup stream +*/ +enum HPI_STREAM_STATES { + /** State stopped - stream is stopped. */ + HPI_STATE_STOPPED = 1, + /** State playing - stream is playing audio. */ + HPI_STATE_PLAYING = 2, + /** State recording - stream is recording. */ + HPI_STATE_RECORDING = 3, + /** State drained - playing stream ran out of data to play. */ + HPI_STATE_DRAINED = 4, + /** State generate sine - to be implemented. */ + HPI_STATE_SINEGEN = 5, + /** State wait - used for inter-card sync to mean waiting for all + cards to be ready. */ + HPI_STATE_WAIT = 6 +}; +/******************************************* mixer source node types */ +/** Source node types +\ingroup mixer +*/ +enum HPI_SOURCENODES { + /** This define can be used instead of 0 to indicate + that there is no valid source node. A control that + exists on a destination node can be searched for using a source + node value of either 0, or HPI_SOURCENODE_NONE */ + HPI_SOURCENODE_NONE = 100, + /** \deprecated Use HPI_SOURCENODE_NONE instead. */ + HPI_SOURCENODE_BASE = 100, + /** Out Stream (Play) node. */ + HPI_SOURCENODE_OSTREAM = 101, + /** Line in node - could be analog, AES/EBU or network. */ + HPI_SOURCENODE_LINEIN = 102, + HPI_SOURCENODE_AESEBU_IN = 103, /**< AES/EBU input node. */ + HPI_SOURCENODE_TUNER = 104, /**< tuner node. */ + HPI_SOURCENODE_RF = 105, /**< RF input node. */ + HPI_SOURCENODE_CLOCK_SOURCE = 106, /**< clock source node. */ + HPI_SOURCENODE_RAW_BITSTREAM = 107, /**< raw bitstream node. */ + HPI_SOURCENODE_MICROPHONE = 108, /**< microphone node. */ + /** Cobranet input node - + Audio samples come from the Cobranet network and into the device. */ + HPI_SOURCENODE_COBRANET = 109, + HPI_SOURCENODE_ANALOG = 110, /**< analog input node. */ + HPI_SOURCENODE_ADAPTER = 111, /**< adapter node. */ + /* !!!Update this AND hpidebug.h if you add a new sourcenode type!!! */ + HPI_SOURCENODE_LAST_INDEX = 111 /**< largest ID */ + /* AX6 max sourcenode types = 15 */ +}; + +/******************************************* mixer dest node types */ +/** Destination node types +\ingroup mixer +*/ +enum HPI_DESTNODES { + /** This define can be used instead of 0 to indicate + that there is no valid destination node. A control that + exists on a source node can be searched for using a destination + node value of either 0, or HPI_DESTNODE_NONE */ + HPI_DESTNODE_NONE = 200, + /** \deprecated Use HPI_DESTNODE_NONE instead. */ + HPI_DESTNODE_BASE = 200, + /** In Stream (Record) node. */ + HPI_DESTNODE_ISTREAM = 201, + HPI_DESTNODE_LINEOUT = 202, /**< line out node. */ + HPI_DESTNODE_AESEBU_OUT = 203, /**< AES/EBU output node. */ + HPI_DESTNODE_RF = 204, /**< RF output node. */ + HPI_DESTNODE_SPEAKER = 205, /**< speaker output node. */ + /** Cobranet output node - + Audio samples from the device are sent out on the Cobranet network.*/ + HPI_DESTNODE_COBRANET = 206, + HPI_DESTNODE_ANALOG = 207, /**< analog output node. */ + + /* !!!Update this AND hpidebug.h if you add a new destnode type!!! */ + HPI_DESTNODE_LAST_INDEX = 207 /**< largest ID */ + /* AX6 max destnode types = 15 */ +}; + +/*******************************************/ +/** Mixer control types +\ingroup mixer +*/ +enum HPI_CONTROLS { + HPI_CONTROL_GENERIC = 0, /**< generic control. */ + HPI_CONTROL_CONNECTION = 1, /**< A connection between nodes. */ + HPI_CONTROL_VOLUME = 2, /**< volume control - works in dB_fs. */ + HPI_CONTROL_METER = 3, /**< peak meter control. */ + HPI_CONTROL_MUTE = 4, /*mute control - not used at present. */ + HPI_CONTROL_MULTIPLEXER = 5, /**< multiplexer control. */ + + HPI_CONTROL_AESEBU_TRANSMITTER = 6, /**< AES/EBU transmitter control. */ + HPI_CONTROL_AESEBUTX = HPI_CONTROL_AESEBU_TRANSMITTER, + + HPI_CONTROL_AESEBU_RECEIVER = 7, /**< AES/EBU receiver control. */ + HPI_CONTROL_AESEBURX = HPI_CONTROL_AESEBU_RECEIVER, + + HPI_CONTROL_LEVEL = 8, /**< level/trim control - works in d_bu. */ + HPI_CONTROL_TUNER = 9, /**< tuner control. */ +/* HPI_CONTROL_ONOFFSWITCH = 10 */ + HPI_CONTROL_VOX = 11, /**< vox control. */ +/* HPI_CONTROL_AES18_TRANSMITTER = 12 */ +/* HPI_CONTROL_AES18_RECEIVER = 13 */ +/* HPI_CONTROL_AES18_BLOCKGENERATOR = 14 */ + HPI_CONTROL_CHANNEL_MODE = 15, /**< channel mode control. */ + + HPI_CONTROL_BITSTREAM = 16, /**< bitstream control. */ + HPI_CONTROL_SAMPLECLOCK = 17, /**< sample clock control. */ + HPI_CONTROL_MICROPHONE = 18, /**< microphone control. */ + HPI_CONTROL_PARAMETRIC_EQ = 19, /**< parametric EQ control. */ + HPI_CONTROL_EQUALIZER = HPI_CONTROL_PARAMETRIC_EQ, + + HPI_CONTROL_COMPANDER = 20, /**< compander control. */ + HPI_CONTROL_COBRANET = 21, /**< cobranet control. */ + HPI_CONTROL_TONEDETECTOR = 22, /**< tone detector control. */ + HPI_CONTROL_SILENCEDETECTOR = 23, /**< silence detector control. */ + HPI_CONTROL_PAD = 24, /**< tuner PAD control. */ + HPI_CONTROL_SRC = 25, /**< samplerate converter control. */ + HPI_CONTROL_UNIVERSAL = 26, /**< universal control. */ + +/* !!! Update this AND hpidebug.h if you add a new control type!!!*/ + HPI_CONTROL_LAST_INDEX = 26 /** 4 line outs = 1 to 8 channel streams), + 4 lineins -> 1 instream (1 to 8 channel streams) at 0-48kHz. + For more info see the SSX Specification. +*/ + HPI_ADAPTER_MODE_MULTICHANNEL = 9, + +/** 12 outstream mode. +- ASI6514, ASI6614: 2 instreams +- ASI6540,ASI6544: 8 instreams +- ASI6640,ASI6644: 8 instreams +*/ + HPI_ADAPTER_MODE_12OSTREAM = 10, + +/** 9 outstream mode. +- ASI6044: 8 instreams +*/ + HPI_ADAPTER_MODE_9OSTREAM = 11, + +/** mono mode. +- ASI6416: 16 outstreams/instreams +- ASI5402: 2 outstreams/instreams +*/ + HPI_ADAPTER_MODE_MONO = 12, + +/** Low latency mode. +- ASI6416/ASI6316: 1 16 channel outstream and instream +*/ + HPI_ADAPTER_MODE_LOW_LATENCY = 13 +}; + +/* Note, adapters can have more than one capability - +encoding as bitfield is recommended. */ +#define HPI_CAPABILITY_NONE (0) +#define HPI_CAPABILITY_MPEG_LAYER3 (1) + +/* Set this equal to maximum capability index, +Must not be greater than 32 - see axnvdef.h */ +#define HPI_CAPABILITY_MAX 1 +/* #define HPI_CAPABILITY_AAC 2 */ + +/******************************************* STREAM ATTRIBUTES ****/ + +/** MPEG Ancillary Data modes + +The mode for the ancillary data insertion or extraction to operate in. +\ingroup stream +*/ +enum HPI_MPEG_ANC_MODES { + /** the MPEG frames have energy information stored in them (5 bytes per stereo frame, 3 per mono) */ + HPI_MPEG_ANC_HASENERGY = 0, + /** the entire ancillary data field is taken up by data from the Anc data buffer + On encode, the encoder will insert the energy bytes before filling the remainder + of the ancillary data space with data from the ancillary data buffer. + */ + HPI_MPEG_ANC_RAW = 1 +}; + +/** Ancillary Data Alignment +\ingroup instream +*/ +enum HPI_ISTREAM_MPEG_ANC_ALIGNS { + /** data is packed against the end of data, then padded to the end of frame */ + HPI_MPEG_ANC_ALIGN_LEFT = 0, + /** data is packed against the end of the frame */ + HPI_MPEG_ANC_ALIGN_RIGHT = 1 +}; + +/** MPEG modes +MPEG modes - can be used optionally for HPI_FormatCreate() +parameter dwAttributes. + +Using any mode setting other than HPI_MPEG_MODE_DEFAULT +with single channel format will return an error. +\ingroup stream +*/ +enum HPI_MPEG_MODES { +/** Causes the MPEG-1 Layer II bitstream to be recorded +in single_channel mode when the number of channels is 1 and in stereo when the +number of channels is 2. */ + HPI_MPEG_MODE_DEFAULT = 0, + /** Standard stereo without joint-stereo compression */ + HPI_MPEG_MODE_STEREO = 1, + /** Joint stereo */ + HPI_MPEG_MODE_JOINTSTEREO = 2, + /** Left and Right channels are completely independent */ + HPI_MPEG_MODE_DUALCHANNEL = 3 +}; +/******************************************* MIXER ATTRIBUTES ****/ + +/* \defgroup mixer_flags Mixer flags for HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES +{ +*/ +#define HPI_MIXER_GET_CONTROL_MULTIPLE_CHANGED (0) +#define HPI_MIXER_GET_CONTROL_MULTIPLE_RESET (1) +/*}*/ + +/** Commands used by HPI_MixerStore() +\ingroup mixer +*/ +enum HPI_MIXER_STORE_COMMAND { +/** Save all mixer control settings. */ + HPI_MIXER_STORE_SAVE = 1, +/** Restore all controls from saved. */ + HPI_MIXER_STORE_RESTORE = 2, +/** Delete saved control settings. */ + HPI_MIXER_STORE_DELETE = 3, +/** Enable auto storage of some control settings. */ + HPI_MIXER_STORE_ENABLE = 4, +/** Disable auto storage of some control settings. */ + HPI_MIXER_STORE_DISABLE = 5, +/** Save the attributes of a single control. */ + HPI_MIXER_STORE_SAVE_SINGLE = 6 +}; + +/************************************* CONTROL ATTRIBUTE VALUES ****/ +/** Used by mixer plugin enable functions + +E.g. HPI_ParametricEQ_SetState() +\ingroup mixer +*/ +enum HPI_SWITCH_STATES { + HPI_SWITCH_OFF = 0, /**< turn the mixer plugin on. */ + HPI_SWITCH_ON = 1 /**< turn the mixer plugin off. */ +}; + +/* Volume control special gain values */ +/** volumes units are 100ths of a dB +\ingroup volume +*/ +#define HPI_UNITS_PER_dB 100 +/** turns volume control OFF or MUTE +\ingroup volume +*/ +#define HPI_GAIN_OFF (-100 * HPI_UNITS_PER_dB) + +/** value returned for no signal +\ingroup meter +*/ +#define HPI_METER_MINIMUM (-150 * HPI_UNITS_PER_dB) + +/** autofade profiles +\ingroup volume +*/ +enum HPI_VOLUME_AUTOFADES { +/** log fade - dB attenuation changes linearly over time */ + HPI_VOLUME_AUTOFADE_LOG = 2, +/** linear fade - amplitude changes linearly */ + HPI_VOLUME_AUTOFADE_LINEAR = 3 +}; + +/** The physical encoding format of the AESEBU I/O. + +Used in HPI_AESEBU_Transmitter_SetFormat(), HPI_AESEBU_Receiver_SetFormat() +along with related Get and Query functions +\ingroup aestx +*/ +enum HPI_AESEBU_FORMATS { +/** AES/EBU physical format - AES/EBU balanced "professional" */ + HPI_AESEBU_FORMAT_AESEBU = 1, +/** AES/EBU physical format - S/PDIF unbalanced "consumer" */ + HPI_AESEBU_FORMAT_SPDIF = 2 +}; + +/** AES/EBU error status bits + +Returned by HPI_AESEBU_Receiver_GetErrorStatus() +\ingroup aesrx +*/ +enum HPI_AESEBU_ERRORS { +/** bit0: 1 when PLL is not locked */ + HPI_AESEBU_ERROR_NOT_LOCKED = 0x01, +/** bit1: 1 when signal quality is poor */ + HPI_AESEBU_ERROR_POOR_QUALITY = 0x02, +/** bit2: 1 when there is a parity error */ + HPI_AESEBU_ERROR_PARITY_ERROR = 0x04, +/** bit3: 1 when there is a bi-phase coding violation */ + HPI_AESEBU_ERROR_BIPHASE_VIOLATION = 0x08, +/** bit4: 1 when the validity bit is high */ + HPI_AESEBU_ERROR_VALIDITY = 0x10, +/** bit5: 1 when the CRC error bit is high */ + HPI_AESEBU_ERROR_CRC = 0x20 +}; + +/** \addtogroup pad +\{ +*/ +/** The text string containing the station/channel combination. */ +#define HPI_PAD_CHANNEL_NAME_LEN 16 +/** The text string containing the artist. */ +#define HPI_PAD_ARTIST_LEN 64 +/** The text string containing the title. */ +#define HPI_PAD_TITLE_LEN 64 +/** The text string containing the comment. */ +#define HPI_PAD_COMMENT_LEN 256 +/** The PTY when the tuner has not recieved any PTY. */ +#define HPI_PAD_PROGRAM_TYPE_INVALID 0xffff +/** \} */ + +/** Data types for PTY string translation. +\ingroup rds +*/ +enum eHPI_RDS_type { + HPI_RDS_DATATYPE_RDS = 0, /**< RDS bitstream.*/ + HPI_RDS_DATATYPE_RBDS = 1 /**< RBDS bitstream.*/ +}; + +/** Tuner bands + +Used for HPI_Tuner_SetBand(),HPI_Tuner_GetBand() +\ingroup tuner +*/ +enum HPI_TUNER_BAND { + HPI_TUNER_BAND_AM = 1, /**< AM band */ + HPI_TUNER_BAND_FM = 2, /**< FM band (mono) */ + HPI_TUNER_BAND_TV_NTSC_M = 3, /**< NTSC-M TV band*/ + HPI_TUNER_BAND_TV = 3, /* use TV_NTSC_M */ + HPI_TUNER_BAND_FM_STEREO = 4, /**< FM band (stereo) */ + HPI_TUNER_BAND_AUX = 5, /**< auxiliary input */ + HPI_TUNER_BAND_TV_PAL_BG = 6, /**< PAL-B/G TV band*/ + HPI_TUNER_BAND_TV_PAL_I = 7, /**< PAL-I TV band*/ + HPI_TUNER_BAND_TV_PAL_DK = 8, /**< PAL-D/K TV band*/ + HPI_TUNER_BAND_TV_SECAM_L = 9, /**< SECAM-L TV band*/ + HPI_TUNER_BAND_LAST = 9 /**< the index of the last tuner band. */ +}; + +/** Tuner mode attributes + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner + +*/ +enum HPI_TUNER_MODES { + HPI_TUNER_MODE_RSS = 1, /**< control RSS */ + HPI_TUNER_MODE_RDS = 2 /**< control RBDS/RDS */ +}; + +/** Tuner mode attribute values + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner +*/ +enum HPI_TUNER_MODE_VALUES { +/* RSS attribute values */ + HPI_TUNER_MODE_RSS_DISABLE = 0, /**< RSS disable */ + HPI_TUNER_MODE_RSS_ENABLE = 1, /**< RSS enable */ + +/* RDS mode attributes */ + HPI_TUNER_MODE_RDS_DISABLE = 0, /**< RDS - disabled */ + HPI_TUNER_MODE_RDS_RDS = 1, /**< RDS - RDS mode */ + HPI_TUNER_MODE_RDS_RBDS = 2 /**< RDS - RBDS mode */ +}; + +/** Tuner Level settings +\ingroup tuner +*/ +enum HPI_TUNER_LEVEL { + HPI_TUNER_LEVEL_AVERAGE = 0, + HPI_TUNER_LEVEL_RAW = 1 +}; + +/** Tuner Status Bits + +These bitfield values are returned by a call to HPI_Tuner_GetStatus(). +Multiple fields are returned from a single call. +\ingroup tuner +*/ +enum HPI_TUNER_STATUS_BITS { + HPI_TUNER_VIDEO_COLOR_PRESENT = 0x0001, /**< video color is present. */ + HPI_TUNER_VIDEO_IS_60HZ = 0x0020, /**< 60 hz video detected. */ + HPI_TUNER_VIDEO_HORZ_SYNC_MISSING = 0x0040, /**< video HSYNC is missing. */ + HPI_TUNER_VIDEO_STATUS_VALID = 0x0100, /**< video status is valid. */ + HPI_TUNER_PLL_LOCKED = 0x1000, /**< the tuner's PLL is locked. */ + HPI_TUNER_FM_STEREO = 0x2000, /**< tuner reports back FM stereo. */ + HPI_TUNER_DIGITAL = 0x0200, /**< tuner reports digital programming. */ + HPI_TUNER_MULTIPROGRAM = 0x0400 /**< tuner reports multiple programs. */ +}; + +/** Channel Modes +Used for HPI_ChannelModeSet/Get() +\ingroup channelmode +*/ +enum HPI_CHANNEL_MODES { +/** Left channel out = left channel in, Right channel out = right channel in. */ + HPI_CHANNEL_MODE_NORMAL = 1, +/** Left channel out = right channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_SWAP = 2, +/** Left channel out = left channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_LEFT_TO_STEREO = 3, +/** Left channel out = right channel in, Right channel out = right channel in.*/ + HPI_CHANNEL_MODE_RIGHT_TO_STEREO = 4, +/** Left channel out = (left channel in + right channel in)/2, + Right channel out = mute. */ + HPI_CHANNEL_MODE_STEREO_TO_LEFT = 5, +/** Left channel out = mute, + Right channel out = (right channel in + left channel in)/2. */ + HPI_CHANNEL_MODE_STEREO_TO_RIGHT = 6, + HPI_CHANNEL_MODE_LAST = 6 +}; + +/** SampleClock source values +\ingroup sampleclock +*/ +enum HPI_SAMPLECLOCK_SOURCES { +/** The sampleclock output is derived from its local samplerate generator. + The local samplerate may be set using HPI_SampleClock_SetLocalRate(). */ + HPI_SAMPLECLOCK_SOURCE_LOCAL = 1, +/** \deprecated Use HPI_SAMPLECLOCK_SOURCE_LOCAL instead */ + HPI_SAMPLECLOCK_SOURCE_ADAPTER = 1, +/** The adapter is clocked from a dedicated AES/EBU SampleClock input.*/ + HPI_SAMPLECLOCK_SOURCE_AESEBU_SYNC = 2, +/** From external wordclock connector */ + HPI_SAMPLECLOCK_SOURCE_WORD = 3, +/** Board-to-board header */ + HPI_SAMPLECLOCK_SOURCE_WORD_HEADER = 4, +/** FUTURE - SMPTE clock. */ + HPI_SAMPLECLOCK_SOURCE_SMPTE = 5, +/** One of the aesebu inputs */ + HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT = 6, +/** \deprecated The first aesebu input with a valid signal +Superseded by separate Auto enable flag +*/ + HPI_SAMPLECLOCK_SOURCE_AESEBU_AUTO = 7, +/** From a network interface e.g. Cobranet or Livewire at either 48 or 96kHz */ + HPI_SAMPLECLOCK_SOURCE_NETWORK = 8, +/** From previous adjacent module (ASI2416 only)*/ + HPI_SAMPLECLOCK_SOURCE_PREV_MODULE = 10, +/*! Update this if you add a new clock source.*/ + HPI_SAMPLECLOCK_SOURCE_LAST = 10 +}; + +/** Equalizer filter types. Used by HPI_ParametricEQ_SetBand() +\ingroup parmeq +*/ +enum HPI_FILTER_TYPE { + HPI_FILTER_TYPE_BYPASS = 0, /**< filter is turned off */ + + HPI_FILTER_TYPE_LOWSHELF = 1, /**< EQ low shelf */ + HPI_FILTER_TYPE_HIGHSHELF = 2, /**< EQ high shelf */ + HPI_FILTER_TYPE_EQ_BAND = 3, /**< EQ gain */ + + HPI_FILTER_TYPE_LOWPASS = 4, /**< standard low pass */ + HPI_FILTER_TYPE_HIGHPASS = 5, /**< standard high pass */ + HPI_FILTER_TYPE_BANDPASS = 6, /**< standard band pass */ + HPI_FILTER_TYPE_BANDSTOP = 7 /**< standard band stop/notch */ +}; + +/** Async Event sources +\ingroup async +*/ +enum ASYNC_EVENT_SOURCES { + HPI_ASYNC_EVENT_GPIO = 1, /**< GPIO event. */ + HPI_ASYNC_EVENT_SILENCE = 2, /**< silence event detected. */ + HPI_ASYNC_EVENT_TONE = 3 /**< tone event detected. */ +}; +/*******************************************/ +/** HPI Error codes + +Almost all HPI functions return an error code +A return value of zero means there was no error. +Otherwise one of these error codes is returned. +Error codes can be converted to a descriptive string using HPI_GetErrorText() + +\note When a new error code is added HPI_GetErrorText() MUST be updated. +\note Codes 1-100 are reserved for driver use +\ingroup utility +*/ +enum HPI_ERROR_CODES { + /** Message type does not exist. */ + HPI_ERROR_INVALID_TYPE = 100, + /** Object type does not exist. */ + HPI_ERROR_INVALID_OBJ = 101, + /** Function does not exist. */ + HPI_ERROR_INVALID_FUNC = 102, + /** The specified object (adapter/Stream) does not exist. */ + HPI_ERROR_INVALID_OBJ_INDEX = 103, + /** Trying to access an object that has not been opened yet. */ + HPI_ERROR_OBJ_NOT_OPEN = 104, + /** Trying to open an already open object. */ + HPI_ERROR_OBJ_ALREADY_OPEN = 105, + /** PCI, ISA resource not valid. */ + HPI_ERROR_INVALID_RESOURCE = 106, + /** GetInfo call from SubSysFindAdapters failed. */ + HPI_ERROR_SUBSYSFINDADAPTERS_GETINFO = 107, + /** Default response was never updated with actual error code. */ + HPI_ERROR_INVALID_RESPONSE = 108, + /** wSize field of response was not updated, + indicating that the message was not processed. */ + HPI_ERROR_PROCESSING_MESSAGE = 109, + /** The network did not respond in a timely manner. */ + HPI_ERROR_NETWORK_TIMEOUT = 110, + /** An HPI handle is invalid (uninitialised?). */ + HPI_ERROR_INVALID_HANDLE = 111, + /** A function or attribute has not been implemented yet. */ + HPI_ERROR_UNIMPLEMENTED = 112, + /** There are too many clients attempting to access a network resource. */ + HPI_ERROR_NETWORK_TOO_MANY_CLIENTS = 113, + /** Response buffer passed to HPI_Message was smaller than returned response */ + HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL = 114, + /** The returned response did not match the sent message */ + HPI_ERROR_RESPONSE_MISMATCH = 115, + + /** Too many adapters.*/ + HPI_ERROR_TOO_MANY_ADAPTERS = 200, + /** Bad adpater. */ + HPI_ERROR_BAD_ADAPTER = 201, + /** Adapter number out of range or not set properly. */ + HPI_ERROR_BAD_ADAPTER_NUMBER = 202, + /** 2 adapters with the same adapter number. */ + HPI_DUPLICATE_ADAPTER_NUMBER = 203, + /** DSP code failed to bootload. */ + HPI_ERROR_DSP_BOOTLOAD = 204, + /** Adapter failed DSP code self test. */ + HPI_ERROR_DSP_SELFTEST = 205, + /** Couldn't find or open the DSP code file. */ + HPI_ERROR_DSP_FILE_NOT_FOUND = 206, + /** Internal DSP hardware error. */ + HPI_ERROR_DSP_HARDWARE = 207, + /** Could not allocate memory in DOS. */ + HPI_ERROR_DOS_MEMORY_ALLOC = 208, + /** Could not allocate memory */ + HPI_ERROR_MEMORY_ALLOC = 208, + /** Failed to correctly load/config PLD .*/ + HPI_ERROR_PLD_LOAD = 209, + /** Unexpected end of file, block length too big etc. */ + HPI_ERROR_DSP_FILE_FORMAT = 210, + + /** Found but could not open DSP code file. */ + HPI_ERROR_DSP_FILE_ACCESS_DENIED = 211, + /** First DSP code section header not found in DSP file. */ + HPI_ERROR_DSP_FILE_NO_HEADER = 212, + /** File read operation on DSP code file failed. */ + HPI_ERROR_DSP_FILE_READ_ERROR = 213, + /** DSP code for adapter family not found. */ + HPI_ERROR_DSP_SECTION_NOT_FOUND = 214, + /** Other OS specific error opening DSP file. */ + HPI_ERROR_DSP_FILE_OTHER_ERROR = 215, + /** Sharing violation opening DSP code file. */ + HPI_ERROR_DSP_FILE_SHARING_VIOLATION = 216, + /** DSP code section header had size == 0. */ + HPI_ERROR_DSP_FILE_NULL_HEADER = 217, + + /** Base number for flash errors. */ + HPI_ERROR_FLASH = 220, + + /** Flash has bad checksum */ + HPI_ERROR_BAD_CHECKSUM = (HPI_ERROR_FLASH + 1), + HPI_ERROR_BAD_SEQUENCE = (HPI_ERROR_FLASH + 2), + HPI_ERROR_FLASH_ERASE = (HPI_ERROR_FLASH + 3), + HPI_ERROR_FLASH_PROGRAM = (HPI_ERROR_FLASH + 4), + HPI_ERROR_FLASH_VERIFY = (HPI_ERROR_FLASH + 5), + HPI_ERROR_FLASH_TYPE = (HPI_ERROR_FLASH + 6), + HPI_ERROR_FLASH_START = (HPI_ERROR_FLASH + 7), + + /** Reserved for OEMs. */ + HPI_ERROR_RESERVED_1 = 290, + + /** Stream does not exist. */ + HPI_ERROR_INVALID_STREAM = 300, + /** Invalid compression format. */ + HPI_ERROR_INVALID_FORMAT = 301, + /** Invalid format samplerate */ + HPI_ERROR_INVALID_SAMPLERATE = 302, + /** Invalid format number of channels. */ + HPI_ERROR_INVALID_CHANNELS = 303, + /** Invalid format bitrate. */ + HPI_ERROR_INVALID_BITRATE = 304, + /** Invalid datasize used for stream read/write. */ + HPI_ERROR_INVALID_DATASIZE = 305, + /** Stream buffer is full during stream write. */ + HPI_ERROR_BUFFER_FULL = 306, + /** Stream buffer is empty during stream read. */ + HPI_ERROR_BUFFER_EMPTY = 307, + /** Invalid datasize used for stream read/write. */ + HPI_ERROR_INVALID_DATA_TRANSFER = 308, + /** Packet ordering error for stream read/write. */ + HPI_ERROR_INVALID_PACKET_ORDER = 309, + + /** Object can't do requested operation in its current + state, eg set format, change rec mux state while recording.*/ + HPI_ERROR_INVALID_OPERATION = 310, + + /** Where an SRG is shared amongst streams, an incompatible samplerate is one + that is different to any currently playing or recording stream. */ + HPI_ERROR_INCOMPATIBLE_SAMPLERATE = 311, + /** Adapter mode is illegal.*/ + HPI_ERROR_BAD_ADAPTER_MODE = 312, + + /** There have been too many attempts to set the adapter's + capabilities (using bad keys), the card should be returned + to ASI if further capabilities updates are required */ + HPI_ERROR_TOO_MANY_CAPABILITY_CHANGE_ATTEMPTS = 313, + /** Streams on different adapters cannot be grouped. */ + HPI_ERROR_NO_INTERADAPTER_GROUPS = 314, + /** Streams on different DSPs cannot be grouped. */ + HPI_ERROR_NO_INTERDSP_GROUPS = 315, + + /** Invalid mixer node for this adapter. */ + HPI_ERROR_INVALID_NODE = 400, + /** Invalid control. */ + HPI_ERROR_INVALID_CONTROL = 401, + /** Invalid control value was passed. */ + HPI_ERROR_INVALID_CONTROL_VALUE = 402, + /** Control attribute not supported by this control. */ + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE = 403, + /** Control is disabled. */ + HPI_ERROR_CONTROL_DISABLED = 404, + /** I2C transaction failed due to a missing ACK. */ + HPI_ERROR_CONTROL_I2C_MISSING_ACK = 405, + /** Control attribute is valid, but not supported by this hardware. */ + HPI_ERROR_UNSUPPORTED_CONTROL_ATTRIBUTE = 406, + /** Control is busy, or coming out of + reset and cannot be accessed at this time. */ + HPI_ERROR_CONTROL_NOT_READY = 407, + + /** Non volatile memory */ + HPI_ERROR_NVMEM_BUSY = 450, + HPI_ERROR_NVMEM_FULL = 451, + HPI_ERROR_NVMEM_FAIL = 452, + + /** I2C */ + HPI_ERROR_I2C_MISSING_ACK = HPI_ERROR_CONTROL_I2C_MISSING_ACK, + HPI_ERROR_I2C_BAD_ADR = 460, + + /** Entity errors */ + HPI_ERROR_ENTITY_TYPE_MISMATCH = 470, + HPI_ERROR_ENTITY_ITEM_COUNT = 471, + HPI_ERROR_ENTITY_TYPE_INVALID = 472, + HPI_ERROR_ENTITY_ROLE_INVALID = 473, + + /* AES18 specific errors were 500..507 */ + + /** custom error to use for debugging */ + HPI_ERROR_CUSTOM = 600, + + /** hpioct32.c can't obtain mutex */ + HPI_ERROR_MUTEX_TIMEOUT = 700, + + /** errors from HPI backends have values >= this */ + HPI_ERROR_BACKEND_BASE = 900, + + /** indicates a cached u16 value is invalid. */ + HPI_ERROR_ILLEGAL_CACHE_VALUE = 0xffff +}; + +/** \defgroup maximums HPI maximum values +\{ +*/ +/** Maximum number of adapters per HPI sub-system + WARNING: modifying this value changes the response structure size.*/ +#define HPI_MAX_ADAPTERS 20 +/** Maximum number of in or out streams per adapter */ +#define HPI_MAX_STREAMS 16 +#define HPI_MAX_CHANNELS 2 /* per stream */ +#define HPI_MAX_NODES 8 /* per mixer ? */ +#define HPI_MAX_CONTROLS 4 /* per node ? */ +/** maximum number of ancillary bytes per MPEG frame */ +#define HPI_MAX_ANC_BYTES_PER_FRAME (64) +#define HPI_STRING_LEN 16 + +/** Velocity units */ +#define HPI_OSTREAM_VELOCITY_UNITS 4096 +/** OutStream timescale units */ +#define HPI_OSTREAM_TIMESCALE_UNITS 10000 +/** OutStream timescale passthrough - turns timescaling on in passthough mode */ +#define HPI_OSTREAM_TIMESCALE_PASSTHROUGH 99999 + +/**\}*/ + +/* ////////////////////////////////////////////////////////////////////// */ +/* STRUCTURES */ +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** Structure containing sample format information. + See also HPI_FormatCreate(). + */ +struct hpi_format { + u32 sample_rate; + /**< 11025, 32000, 44100 ... */ + u32 bit_rate; /**< for MPEG */ + u32 attributes; + /**< Stereo/JointStereo/Mono */ + u16 mode_legacy; + /**< Legacy ancillary mode or idle bit */ + u16 unused; /**< unused */ + u16 channels; /**< 1,2..., (or ancillary mode or idle bit */ + u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see #HPI_FORMATS. */ +}; + +struct hpi_anc_frame { + u32 valid_bits_in_this_frame; + u8 b_data[HPI_MAX_ANC_BYTES_PER_FRAME]; +}; + +/** An object for containing a single async event. +*/ +struct hpi_async_event { + u16 event_type; /**< type of event. \sa async_event */ + u16 sequence; /**< sequence number, allows lost event detection */ + u32 state; /**< new state */ + u32 h_object; /**< handle to the object returning the event. */ + union { + struct { + u16 index; /**< GPIO bit index. */ + } gpio; + struct { + u16 node_index; /**< what node is the control on ? */ + u16 node_type; /**< what type of node is the control on ? */ + } control; + } u; +}; + +/*/////////////////////////////////////////////////////////////////////////// */ +/* Public HPI Entity related definitions */ + +struct hpi_entity; + +enum e_entity_type { + entity_type_null, + entity_type_sequence, /* sequence of potentially heterogeneous TLV entities */ + + entity_type_reference, /* refers to a TLV entity or NULL */ + + entity_type_int, /* 32 bit */ + entity_type_float, /* ieee754 binary 32 bit encoding */ + entity_type_double, + + entity_type_cstring, + entity_type_octet, + entity_type_ip4_address, + entity_type_ip6_address, + entity_type_mac_address, + + LAST_ENTITY_TYPE +}; + +enum e_entity_role { + entity_role_null, + entity_role_value, + entity_role_classname, + + entity_role_units, + entity_role_flags, + entity_role_range, + + entity_role_mapping, + entity_role_enum, + + entity_role_instance_of, + entity_role_depends_on, + entity_role_member_of_group, + entity_role_value_constraint, + entity_role_parameter_port, + + entity_role_block, + entity_role_node_group, + entity_role_audio_port, + entity_role_clock_port, + LAST_ENTITY_ROLE +}; + +/* skip host side function declarations for + DSP compile and documentation extraction */ + +struct hpi_hsubsys { + int not_really_used; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/*////////////////////////////////////////////////////////////////////////// */ +/* HPI FUNCTIONS */ + +/*/////////////////////////// */ +/* DATA and FORMAT and STREAM */ + +u16 hpi_stream_estimate_buffer_size(struct hpi_format *pF, + u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size); + +/*/////////// */ +/* SUB SYSTEM */ +struct hpi_hsubsys *hpi_subsys_create(void + ); + +void hpi_subsys_free(const struct hpi_hsubsys *ph_subsys); + +u16 hpi_subsys_get_version(const struct hpi_hsubsys *ph_subsys, + u32 *pversion); + +u16 hpi_subsys_get_version_ex(const struct hpi_hsubsys *ph_subsys, + u32 *pversion_ex); + +u16 hpi_subsys_get_info(const struct hpi_hsubsys *ph_subsys, u32 *pversion, + u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length); + +u16 hpi_subsys_find_adapters(const struct hpi_hsubsys *ph_subsys, + u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length); + +u16 hpi_subsys_get_num_adapters(const struct hpi_hsubsys *ph_subsys, + int *pn_num_adapters); + +u16 hpi_subsys_get_adapter(const struct hpi_hsubsys *ph_subsys, int iterator, + u32 *padapter_index, u16 *pw_adapter_type); + +u16 hpi_subsys_ssx2_bypass(const struct hpi_hsubsys *ph_subsys, u16 bypass); + +u16 hpi_subsys_set_host_network_interface(const struct hpi_hsubsys *ph_subsys, + const char *sz_interface); + +/*///////// */ +/* ADAPTER */ + +u16 hpi_adapter_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index); + +u16 hpi_adapter_close(const struct hpi_hsubsys *ph_subsys, u16 adapter_index); + +u16 hpi_adapter_get_info(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *pw_num_outstreams, u16 *pw_num_instreams, + u16 *pw_version, u32 *pserial_number, u16 *pw_adapter_type); + +u16 hpi_adapter_get_module_by_index(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 module_index, u16 *pw_num_outputs, + u16 *pw_num_inputs, u16 *pw_version, u32 *pserial_number, + u16 *pw_module_type, u32 *ph_module); + +u16 hpi_adapter_set_mode(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 adapter_mode); + +u16 hpi_adapter_set_mode_ex(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 adapter_mode, u16 query_or_set); + +u16 hpi_adapter_get_mode(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 *padapter_mode); + +u16 hpi_adapter_get_assert(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *assert_present, char *psz_assert, + u16 *pw_line_number); + +u16 hpi_adapter_get_assert_ex(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *assert_present, char *psz_assert, + u32 *pline_number, u16 *pw_assert_on_dsp); + +u16 hpi_adapter_test_assert(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 assert_id); + +u16 hpi_adapter_enable_capability(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 capability, u32 key); + +u16 hpi_adapter_self_test(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index); + +u16 hpi_adapter_debug_read(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 dsp_address, char *p_bytes, int *count_bytes); + +u16 hpi_adapter_set_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 property, u16 paramter1, u16 paramter2); + +u16 hpi_adapter_get_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 property, u16 *pw_paramter1, + u16 *pw_paramter2); + +u16 hpi_adapter_enumerate_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 index, u16 what_to_enumerate, + u16 property_index, u32 *psetting); + +/*////////////// */ +/* NonVol Memory */ +u16 hpi_nv_memory_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_nv_memory, u16 *pw_size_in_bytes); + +u16 hpi_nv_memory_read_byte(const struct hpi_hsubsys *ph_subsys, + u32 h_nv_memory, u16 index, u16 *pw_data); + +u16 hpi_nv_memory_write_byte(const struct hpi_hsubsys *ph_subsys, + u32 h_nv_memory, u16 index, u16 data); + +/*////////////// */ +/* Digital I/O */ +u16 hpi_gpio_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_gpio, u16 *pw_number_input_bits, u16 *pw_number_output_bits); + +u16 hpi_gpio_read_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 bit_index, u16 *pw_bit_data); + +u16 hpi_gpio_read_all_bits(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 aw_all_bit_data[4] + ); + +u16 hpi_gpio_write_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 bit_index, u16 bit_data); + +u16 hpi_gpio_write_status(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 aw_all_bit_data[4] + ); + +/**********************/ +/* Async Event Object */ +/**********************/ +u16 hpi_async_event_open(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 *ph_async); + +u16 hpi_async_event_close(const struct hpi_hsubsys *ph_subsys, u32 h_async); + +u16 hpi_async_event_wait(const struct hpi_hsubsys *ph_subsys, u32 h_async, + u16 maximum_events, struct hpi_async_event *p_events, + u16 *pw_number_returned); + +u16 hpi_async_event_get_count(const struct hpi_hsubsys *ph_subsys, + u32 h_async, u16 *pw_count); + +u16 hpi_async_event_get(const struct hpi_hsubsys *ph_subsys, u32 h_async, + u16 maximum_events, struct hpi_async_event *p_events, + u16 *pw_number_returned); + +/*/////////// */ +/* WATCH-DOG */ +u16 hpi_watchdog_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_watchdog); + +u16 hpi_watchdog_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog, + u32 time_millisec); + +u16 hpi_watchdog_ping(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog); + +/**************/ +/* OUT STREAM */ +/**************/ +u16 hpi_outstream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u16 outstream_index, u32 *ph_outstream); + +u16 hpi_outstream_close(const struct hpi_hsubsys *ph_subsys, u32 h_outstream); + +u16 hpi_outstream_get_info_ex(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_to_play, + u32 *psamples_played, u32 *pauxiliary_data_to_play); + +u16 hpi_outstream_write_buf(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, const u8 *pb_write_buf, u32 bytes_to_write, + const struct hpi_format *p_format); + +u16 hpi_outstream_start(const struct hpi_hsubsys *ph_subsys, u32 h_outstream); + +u16 hpi_outstream_wait_start(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream); + +u16 hpi_outstream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_outstream); + +u16 hpi_outstream_sinegen(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream); + +u16 hpi_outstream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_outstream); + +u16 hpi_outstream_query_format(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_format *p_format); + +u16 hpi_outstream_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_format *p_format); + +u16 hpi_outstream_set_punch_in_out(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 punch_in_sample, u32 punch_out_sample); + +u16 hpi_outstream_set_velocity(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, short velocity); + +u16 hpi_outstream_ancillary_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u16 mode); + +u16 hpi_outstream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 *pframes_available); + +u16 hpi_outstream_ancillary_read(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_read); + +u16 hpi_outstream_set_time_scale(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 time_scaleX10000); + +u16 hpi_outstream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 size_in_bytes); + +u16 hpi_outstream_host_buffer_free(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream); + +u16 hpi_outstream_group_add(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 h_stream); + +u16 hpi_outstream_group_get_map(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 *poutstream_map, u32 *pinstream_map); + +u16 hpi_outstream_group_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream); + +/*////////// */ +/* IN_STREAM */ +u16 hpi_instream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u16 instream_index, u32 *ph_instream); + +u16 hpi_instream_close(const struct hpi_hsubsys *ph_subsys, u32 h_instream); + +u16 hpi_instream_query_format(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_format *p_format); + +u16 hpi_instream_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_format *p_format); + +u16 hpi_instream_read_buf(const struct hpi_hsubsys *ph_subsys, u32 h_instream, + u8 *pb_read_buf, u32 bytes_to_read); + +u16 hpi_instream_start(const struct hpi_hsubsys *ph_subsys, u32 h_instream); + +u16 hpi_instream_wait_start(const struct hpi_hsubsys *ph_subsys, + u32 h_instream); + +u16 hpi_instream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_instream); + +u16 hpi_instream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_instream); + +u16 hpi_instream_get_info_ex(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_recorded, + u32 *psamples_recorded, u32 *pauxiliary_data_recorded); + +u16 hpi_instream_ancillary_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u16 bytes_per_frame, u16 mode, u16 alignment, + u16 idle_bit); + +u16 hpi_instream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 *pframe_space); + +u16 hpi_instream_ancillary_write(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_write); + +u16 hpi_instream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 size_in_bytes); + +u16 hpi_instream_host_buffer_free(const struct hpi_hsubsys *ph_subsys, + u32 h_instream); + +u16 hpi_instream_group_add(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 h_stream); + +u16 hpi_instream_group_get_map(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 *poutstream_map, u32 *pinstream_map); + +u16 hpi_instream_group_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_instream); + +/*********/ +/* MIXER */ +/*********/ +u16 hpi_mixer_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_mixer); + +u16 hpi_mixer_close(const struct hpi_hsubsys *ph_subsys, u32 h_mixer); + +u16 hpi_mixer_get_control(const struct hpi_hsubsys *ph_subsys, u32 h_mixer, + u16 src_node_type, u16 src_node_type_index, u16 dst_node_type, + u16 dst_node_type_index, u16 control_type, u32 *ph_control); + +u16 hpi_mixer_get_control_by_index(const struct hpi_hsubsys *ph_subsys, + u32 h_mixer, u16 control_index, u16 *pw_src_node_type, + u16 *pw_src_node_index, u16 *pw_dst_node_type, u16 *pw_dst_node_index, + u16 *pw_control_type, u32 *ph_control); + +u16 hpi_mixer_store(const struct hpi_hsubsys *ph_subsys, u32 h_mixer, + enum HPI_MIXER_STORE_COMMAND command, u16 index); +/*************************/ +/* mixer CONTROLS */ +/*************************/ +/*************************/ +/* volume control */ +/*************************/ +u16 hpi_volume_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB[HPI_MAX_CHANNELS] + ); + +u16 hpi_volume_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB_out[HPI_MAX_CHANNELS] + ); + +#define hpi_volume_get_range hpi_volume_query_range +u16 hpi_volume_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB); + +u16 hpi_volume_query_channels(const struct hpi_hsubsys *ph_subsys, + const u32 h_volume, u32 *p_channels); + +u16 hpi_volume_auto_fade(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms); + +u16 hpi_volume_auto_fade_profile(const struct hpi_hsubsys *ph_subsys, + u32 h_control, short an_stop_gain0_01dB[HPI_MAX_CHANNELS], + u32 duration_ms, u16 profile); + +/*************************/ +/* level control */ +/*************************/ +u16 hpi_level_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB); + +u16 hpi_level_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB[HPI_MAX_CHANNELS] + ); + +u16 hpi_level_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB_out[HPI_MAX_CHANNELS] + ); + +/*************************/ +/* meter control */ +/*************************/ +u16 hpi_meter_query_channels(const struct hpi_hsubsys *ph_subsys, + const u32 h_meter, u32 *p_channels); + +u16 hpi_meter_get_peak(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_peak0_01dB_out[HPI_MAX_CHANNELS] + ); + +u16 hpi_meter_get_rms(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_peak0_01dB_out[HPI_MAX_CHANNELS] + ); + +u16 hpi_meter_set_peak_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 attack, u16 decay); + +u16 hpi_meter_set_rms_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 attack, u16 decay); + +u16 hpi_meter_get_peak_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *attack, u16 *decay); + +u16 hpi_meter_get_rms_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *attack, u16 *decay); + +/*************************/ +/* channel mode control */ +/*************************/ +u16 hpi_channel_mode_query_mode(const struct hpi_hsubsys *ph_subsys, + const u32 h_mode, const u32 index, u16 *pw_mode); + +u16 hpi_channel_mode_set(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 mode); + +u16 hpi_channel_mode_get(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *mode); + +/*************************/ +/* Tuner control */ +/*************************/ +u16 hpi_tuner_query_band(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, u16 *pw_band); + +u16 hpi_tuner_set_band(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 band); + +u16 hpi_tuner_get_band(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_band); + +u16 hpi_tuner_query_frequency(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, const u16 band, u32 *pfreq); + +u16 hpi_tuner_set_frequency(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 freq_ink_hz); + +u16 hpi_tuner_get_frequency(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pw_freq_ink_hz); + +u16 hpi_tuner_getRF_level(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *pw_level); + +u16 hpi_tuner_get_rawRF_level(const struct hpi_hsubsys *ph_subsys, + u32 h_control, short *pw_level); + +u16 hpi_tuner_query_gain(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, u16 *pw_gain); + +u16 hpi_tuner_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short gain); + +u16 hpi_tuner_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *pn_gain); + +u16 hpi_tuner_get_status(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_status_mask, u16 *pw_status); + +u16 hpi_tuner_set_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 mode, u32 value); + +u16 hpi_tuner_get_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 mode, u32 *pn_value); + +u16 hpi_tuner_getRDS(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *p_rds_data); + +u16 hpi_tuner_query_deemphasis(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, const u16 band, u32 *pdeemphasis); + +u16 hpi_tuner_set_deemphasis(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 deemphasis); +u16 hpi_tuner_get_deemphasis(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pdeemphasis); + +u16 hpi_tuner_query_program(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, u32 *pbitmap_program); + +u16 hpi_tuner_set_program(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 program); + +u16 hpi_tuner_get_program(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 *pprogram); + +u16 hpi_tuner_get_hd_radio_dsp_version(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_dsp_version, const u32 string_size); + +u16 hpi_tuner_get_hd_radio_sdk_version(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_sdk_version, const u32 string_size); + +u16 hpi_tuner_get_hd_radio_signal_quality(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pquality); + +/****************************/ +/* PADs control */ +/****************************/ + +u16 HPI_PAD__get_channel_name(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_string, const u32 string_length); + +u16 HPI_PAD__get_artist(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 string_length); + +u16 HPI_PAD__get_title(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 string_length); + +u16 HPI_PAD__get_comment(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 string_length); + +u16 HPI_PAD__get_program_type(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *ppTY); + +u16 HPI_PAD__get_rdsPI(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 *ppI); + +u16 HPI_PAD__get_program_type_string(const struct hpi_hsubsys *ph_subsys, + u32 h_control, const u32 data_type, const u32 pTY, char *psz_string, + const u32 string_length); + +/****************************/ +/* AES/EBU Receiver control */ +/****************************/ +u16 HPI_AESEBU__receiver_query_format(const struct hpi_hsubsys *ph_subsys, + const u32 h_aes_rx, const u32 index, u16 *pw_format); + +u16 HPI_AESEBU__receiver_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source); + +u16 HPI_AESEBU__receiver_get_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_source); + +u16 HPI_AESEBU__receiver_get_sample_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate); + +u16 HPI_AESEBU__receiver_get_user_data(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *pw_data); + +u16 HPI_AESEBU__receiver_get_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 *pw_data); + +u16 HPI_AESEBU__receiver_get_error_status(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_error_data); + +/*******************************/ +/* AES/EBU Transmitter control */ +/*******************************/ +u16 HPI_AESEBU__transmitter_set_sample_rate(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u32 sample_rate); + +u16 HPI_AESEBU__transmitter_set_user_data(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 data); + +u16 HPI_AESEBU__transmitter_set_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 data); + +u16 HPI_AESEBU__transmitter_get_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 *pw_data); + +u16 HPI_AESEBU__transmitter_query_format(const struct hpi_hsubsys *ph_subsys, + const u32 h_aes_tx, const u32 index, u16 *pw_format); + +u16 HPI_AESEBU__transmitter_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 output_format); + +u16 HPI_AESEBU__transmitter_get_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_output_format); + +/***********************/ +/* multiplexer control */ +/***********************/ +u16 hpi_multiplexer_set_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source_node_type, u16 source_node_index); + +u16 hpi_multiplexer_get_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *source_node_type, u16 *source_node_index); + +u16 hpi_multiplexer_query_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *source_node_type, + u16 *source_node_index); + +/***************/ +/* VOX control */ +/***************/ +u16 hpi_vox_set_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB); + +u16 hpi_vox_get_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *an_gain0_01dB); + +/*********************/ +/* Bitstream control */ +/*********************/ +u16 hpi_bitstream_set_clock_edge(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 edge_type); + +u16 hpi_bitstream_set_data_polarity(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 polarity); + +u16 hpi_bitstream_get_activity(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_clk_activity, u16 *pw_data_activity); + +/***********************/ +/* SampleClock control */ +/***********************/ + +u16 hpi_sample_clock_query_source(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, u16 *pw_source); + +u16 hpi_sample_clock_set_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source); + +u16 hpi_sample_clock_get_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_source); + +u16 hpi_sample_clock_query_source_index(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, const u32 source, + u16 *pw_source_index); + +u16 hpi_sample_clock_set_source_index(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source_index); + +u16 hpi_sample_clock_get_source_index(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_source_index); + +u16 hpi_sample_clock_get_sample_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate); + +u16 hpi_sample_clock_query_local_rate(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, u32 *psource); + +u16 hpi_sample_clock_set_local_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 sample_rate); + +u16 hpi_sample_clock_get_local_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate); + +u16 hpi_sample_clock_set_auto(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 enable); + +u16 hpi_sample_clock_get_auto(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *penable); + +u16 hpi_sample_clock_set_local_rate_lock(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 lock); + +u16 hpi_sample_clock_get_local_rate_lock(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *plock); + +/***********************/ +/* Microphone control */ +/***********************/ +u16 hpi_microphone_set_phantom_power(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 on_off); + +u16 hpi_microphone_get_phantom_power(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_on_off); + +/******************************* + Parametric Equalizer control +*******************************/ +u16 hpi_parametricEQ__get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_number_of_bands, u16 *pw_enabled); + +u16 hpi_parametricEQ__set_state(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 on_off); + +u16 hpi_parametricEQ__set_band(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 type, u32 frequency_hz, short q100, + short gain0_01dB); + +u16 hpi_parametricEQ__get_band(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *pn_type, u32 *pfrequency_hz, + short *pnQ100, short *pn_gain0_01dB); + +u16 hpi_parametricEQ__get_coeffs(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, short coeffs[5] + ); + +/******************************* + Compressor Expander control +*******************************/ + +u16 hpi_compander_set(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 attack, u16 decay, short ratio100, short threshold0_01dB, + short makeup_gain0_01dB); + +u16 hpi_compander_get(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_attack, u16 *pw_decay, short *pw_ratio100, + short *pn_threshold0_01dB, short *pn_makeup_gain0_01dB); + +/******************************* + Cobranet HMI control +*******************************/ +u16 hpi_cobranet_hmi_write(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 hmi_address, u32 byte_count, u8 *pb_data); + +u16 hpi_cobranet_hmi_read(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 hmi_address, u32 max_byte_count, u32 *pbyte_count, u8 *pb_data); + +u16 hpi_cobranet_hmi_get_status(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pstatus, u32 *preadable_size, + u32 *pwriteable_size); + +/*Read the current IP address +*/ +u16 hpi_cobranet_getI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pi_paddress); + +/* Write the current IP address +*/ +u16 hpi_cobranet_setI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 i_paddress); + +/* Read the static IP address +*/ +u16 hpi_cobranet_get_staticI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pi_paddress); + +/* Write the static IP address +*/ +u16 hpi_cobranet_set_staticI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 i_paddress); + +/* Read the MAC address +*/ +u16 hpi_cobranet_getMA_caddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pmAC_MS_bs, u32 *pmAC_LS_bs); + +/******************************* + Tone Detector control +*******************************/ +u16 hpi_tone_detector_get_state(const struct hpi_hsubsys *ph_subsys, u32 hC, + u32 *state); + +u16 hpi_tone_detector_set_enable(const struct hpi_hsubsys *ph_subsys, u32 hC, + u32 enable); + +u16 hpi_tone_detector_get_enable(const struct hpi_hsubsys *ph_subsys, u32 hC, + u32 *enable); + +u16 hpi_tone_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 event_enable); + +u16 hpi_tone_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 *event_enable); + +u16 hpi_tone_detector_set_threshold(const struct hpi_hsubsys *ph_subsys, + u32 hC, int threshold); + +u16 hpi_tone_detector_get_threshold(const struct hpi_hsubsys *ph_subsys, + u32 hC, int *threshold); + +u16 hpi_tone_detector_get_frequency(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 index, u32 *frequency); + +/******************************* + Silence Detector control +*******************************/ +u16 hpi_silence_detector_get_state(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 *state); + +u16 hpi_silence_detector_set_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 enable); + +u16 hpi_silence_detector_get_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 *enable); + +u16 hpi_silence_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 event_enable); + +u16 hpi_silence_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 *event_enable); + +u16 hpi_silence_detector_set_delay(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 delay); + +u16 hpi_silence_detector_get_delay(const struct hpi_hsubsys *ph_subsys, + u32 hC, u32 *delay); + +u16 hpi_silence_detector_set_threshold(const struct hpi_hsubsys *ph_subsys, + u32 hC, int threshold); + +u16 hpi_silence_detector_get_threshold(const struct hpi_hsubsys *ph_subsys, + u32 hC, int *threshold); + +/******************************* + Universal control +*******************************/ +u16 hpi_entity_find_next(struct hpi_entity *container_entity, + enum e_entity_type type, enum e_entity_role role, int recursive_flag, + struct hpi_entity **current_match); + +u16 hpi_entity_copy_value_from(struct hpi_entity *entity, + enum e_entity_type type, size_t item_count, void *value_dst_p); + +u16 hpi_entity_unpack(struct hpi_entity *entity, enum e_entity_type *type, + size_t *items, enum e_entity_role *role, void **value); + +u16 hpi_entity_alloc_and_pack(const enum e_entity_type type, + const size_t item_count, const enum e_entity_role role, void *value, + struct hpi_entity **entity); + +void hpi_entity_free(struct hpi_entity *entity); + +u16 hpi_universal_info(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity **info); + +u16 hpi_universal_get(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity **value); + +u16 hpi_universal_set(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity *value); + +/*/////////// */ +/* DSP CLOCK */ +/*/////////// */ +u16 hpi_clock_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_dsp_clock); + +u16 hpi_clock_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_clock, + u16 hour, u16 minute, u16 second, u16 milli_second); + +u16 hpi_clock_get_time(const struct hpi_hsubsys *ph_subsys, u32 h_clock, + u16 *pw_hour, u16 *pw_minute, u16 *pw_second, u16 *pw_milli_second); + +/*/////////// */ +/* PROFILE */ +/*/////////// */ +u16 hpi_profile_open_all(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 profile_index, u32 *ph_profile, + u16 *pw_max_profiles); + +u16 hpi_profile_get(const struct hpi_hsubsys *ph_subsys, u32 h_profile, + u16 index, u16 *pw_seconds, u32 *pmicro_seconds, u32 *pcall_count, + u32 *pmax_micro_seconds, u32 *pmin_micro_seconds); + +u16 hpi_profile_start_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile); + +u16 hpi_profile_stop_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile); + +u16 hpi_profile_get_name(const struct hpi_hsubsys *ph_subsys, u32 h_profile, + u16 index, char *sz_profile_name, u16 profile_name_length); + +u16 hpi_profile_get_utilization(const struct hpi_hsubsys *ph_subsys, + u32 h_profile, u32 *putilization); + +/*//////////////////// */ +/* UTILITY functions */ + +u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format, + u32 sample_rate, u32 bit_rate, u32 attributes); + +/* Until it's verified, this function is for Windows OSs only */ + +#endif /*_H_HPI_ */ +/* +/////////////////////////////////////////////////////////////////////////////// +// See CVS for history. Last complete set in rev 1.146 +//////////////////////////////////////////////////////////////////////////////// +*/ diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c new file mode 100644 index 00000000000..9c50931731a --- /dev/null +++ b/sound/pci/asihpi/hpi6000.c @@ -0,0 +1,1841 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + Hardware Programming Interface (HPI) for AudioScience ASI6200 series adapters. + These PCI bus adapters are based on the TI C6711 DSP. + + Exported functions: + void HPI_6000(struct hpi_message *phm, struct hpi_response *phr) + + #defines + HIDE_PCI_ASSERTS to show the PCI asserts + PROFILE_DSP2 get profile data from DSP2 if present (instead of DSP 1) + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpi6000.c" + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpi6000.h" +#include "hpidspcd.h" +#include "hpicmn.h" + +#define HPI_HIF_BASE (0x00000200) /* start of C67xx internal RAM */ +#define HPI_HIF_ADDR(member) \ + (HPI_HIF_BASE + offsetof(struct hpi_hif_6000, member)) +#define HPI_HIF_ERROR_MASK 0x4000 + +/* HPI6000 specific error codes */ + +#define HPI6000_ERROR_BASE 900 +#define HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT 901 +#define HPI6000_ERROR_MSG_RESP_SEND_MSG_ACK 902 +#define HPI6000_ERROR_MSG_RESP_GET_RESP_ACK 903 +#define HPI6000_ERROR_MSG_GET_ADR 904 +#define HPI6000_ERROR_RESP_GET_ADR 905 +#define HPI6000_ERROR_MSG_RESP_BLOCKWRITE32 906 +#define HPI6000_ERROR_MSG_RESP_BLOCKREAD32 907 +#define HPI6000_ERROR_MSG_INVALID_DSP_INDEX 908 +#define HPI6000_ERROR_CONTROL_CACHE_PARAMS 909 + +#define HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT 911 +#define HPI6000_ERROR_SEND_DATA_ACK 912 +#define HPI6000_ERROR_SEND_DATA_ADR 913 +#define HPI6000_ERROR_SEND_DATA_TIMEOUT 914 +#define HPI6000_ERROR_SEND_DATA_CMD 915 +#define HPI6000_ERROR_SEND_DATA_WRITE 916 +#define HPI6000_ERROR_SEND_DATA_IDLECMD 917 +#define HPI6000_ERROR_SEND_DATA_VERIFY 918 + +#define HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT 921 +#define HPI6000_ERROR_GET_DATA_ACK 922 +#define HPI6000_ERROR_GET_DATA_CMD 923 +#define HPI6000_ERROR_GET_DATA_READ 924 +#define HPI6000_ERROR_GET_DATA_IDLECMD 925 + +#define HPI6000_ERROR_CONTROL_CACHE_ADDRLEN 951 +#define HPI6000_ERROR_CONTROL_CACHE_READ 952 +#define HPI6000_ERROR_CONTROL_CACHE_FLUSH 953 + +#define HPI6000_ERROR_MSG_RESP_GETRESPCMD 961 +#define HPI6000_ERROR_MSG_RESP_IDLECMD 962 +#define HPI6000_ERROR_MSG_RESP_BLOCKVERIFY32 963 + +/* adapter init errors */ +#define HPI6000_ERROR_UNHANDLED_SUBSYS_ID 930 + +/* can't access PCI2040 */ +#define HPI6000_ERROR_INIT_PCI2040 931 +/* can't access DSP HPI i/f */ +#define HPI6000_ERROR_INIT_DSPHPI 932 +/* can't access internal DSP memory */ +#define HPI6000_ERROR_INIT_DSPINTMEM 933 +/* can't access SDRAM - test#1 */ +#define HPI6000_ERROR_INIT_SDRAM1 934 +/* can't access SDRAM - test#2 */ +#define HPI6000_ERROR_INIT_SDRAM2 935 + +#define HPI6000_ERROR_INIT_VERIFY 938 + +#define HPI6000_ERROR_INIT_NOACK 939 + +#define HPI6000_ERROR_INIT_PLDTEST1 941 +#define HPI6000_ERROR_INIT_PLDTEST2 942 + +/* local defines */ + +#define HIDE_PCI_ASSERTS +#define PROFILE_DSP2 + +/* for PCI2040 i/f chip */ +/* HPI CSR registers */ +/* word offsets from CSR base */ +/* use when io addresses defined as u32 * */ + +#define INTERRUPT_EVENT_SET 0 +#define INTERRUPT_EVENT_CLEAR 1 +#define INTERRUPT_MASK_SET 2 +#define INTERRUPT_MASK_CLEAR 3 +#define HPI_ERROR_REPORT 4 +#define HPI_RESET 5 +#define HPI_DATA_WIDTH 6 + +#define MAX_DSPS 2 +/* HPI registers, spaced 8K bytes = 2K words apart */ +#define DSP_SPACING 0x800 + +#define CONTROL 0x0000 +#define ADDRESS 0x0200 +#define DATA_AUTOINC 0x0400 +#define DATA 0x0600 + +#define TIMEOUT 500000 + +struct dsp_obj { + __iomem u32 *prHPI_control; + __iomem u32 *prHPI_address; + __iomem u32 *prHPI_data; + __iomem u32 *prHPI_data_auto_inc; + char c_dsp_rev; /*A, B */ + u32 control_cache_address_on_dsp; + u32 control_cache_length_on_dsp; + struct hpi_adapter_obj *pa_parent_adapter; +}; + +struct hpi_hw_obj { + __iomem u32 *dw2040_HPICSR; + __iomem u32 *dw2040_HPIDSP; + + u16 num_dsp; + struct dsp_obj ado[MAX_DSPS]; + + u32 message_buffer_address_on_dsp; + u32 response_buffer_address_on_dsp; + u32 pCI2040HPI_error_count; + + struct hpi_control_cache_single control_cache[HPI_NMIXER_CONTROLS]; + struct hpi_control_cache *p_cache; +}; + +static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *source, u32 count); +static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *dest, u32 count); + +static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code); +static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao, + u16 read_or_write); +#define H6READ 1 +#define H6WRITE 0 + +static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao, + struct hpi_message *phm); +static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao, + u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr); + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr); + +static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index, + u32 ack_value); + +static short hpi6000_send_host_command(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 host_cmd); + +static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo); + +static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr); + +static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr); + +static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data); + +static u32 hpi_read_word(struct dsp_obj *pdo, u32 address); + +static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length); + +static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length); + +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr); + +static void subsys_delete_adapter(struct hpi_message *phm, + struct hpi_response *phr); + +static void adapter_get_asserts(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static short create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +/* local globals */ + +static u16 gw_pci_read_asserts; /* used to count PCI2040 errors */ +static u16 gw_pci_write_asserts; /* used to count PCI2040 errors */ + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_SUBSYS_OPEN: + case HPI_SUBSYS_CLOSE: + case HPI_SUBSYS_GET_INFO: + case HPI_SUBSYS_DRIVER_UNLOAD: + case HPI_SUBSYS_DRIVER_LOAD: + case HPI_SUBSYS_FIND_ADAPTERS: + /* messages that should not get here */ + phr->error = HPI_ERROR_UNIMPLEMENTED; + break; + case HPI_SUBSYS_CREATE_ADAPTER: + subsys_create_adapter(phm, phr); + break; + case HPI_SUBSYS_DELETE_ADAPTER: + subsys_delete_adapter(phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void control_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_CONTROL_GET_STATE: + if (pao->has_control_cache) { + u16 err; + err = hpi6000_update_control_cache(pao, phm); + + if (err) { + phr->error = err; + break; + } + + if (hpi_check_control_cache(((struct hpi_hw_obj *) + pao->priv)->p_cache, phm, + phr)) + break; + } + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_GET_INFO: + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_SET_STATE: + hw_message(pao, phm, phr); + hpi_sync_control_cache(((struct hpi_hw_obj *)pao->priv)-> + p_cache, phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void adapter_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_ADAPTER_GET_INFO: + hw_message(pao, phm, phr); + break; + case HPI_ADAPTER_GET_ASSERT: + adapter_get_asserts(pao, phm, phr); + break; + case HPI_ADAPTER_OPEN: + case HPI_ADAPTER_CLOSE: + case HPI_ADAPTER_TEST_ASSERT: + case HPI_ADAPTER_SELFTEST: + case HPI_ADAPTER_GET_MODE: + case HPI_ADAPTER_SET_MODE: + case HPI_ADAPTER_FIND_OBJECT: + case HPI_ADAPTER_GET_PROPERTY: + case HPI_ADAPTER_SET_PROPERTY: + case HPI_ADAPTER_ENUM_PROPERTY: + hw_message(pao, phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void outstream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_OSTREAM_HOSTBUFFER_ALLOC: + case HPI_OSTREAM_HOSTBUFFER_FREE: + /* Don't let these messages go to the HW function because + * they're called without allocating the spinlock. + * For the HPI6000 adapters the HW would return + * HPI_ERROR_INVALID_FUNC anyway. + */ + phr->error = HPI_ERROR_INVALID_FUNC; + break; + default: + hw_message(pao, phm, phr); + return; + } +} + +static void instream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_ISTREAM_HOSTBUFFER_ALLOC: + case HPI_ISTREAM_HOSTBUFFER_FREE: + /* Don't let these messages go to the HW function because + * they're called without allocating the spinlock. + * For the HPI6000 adapters the HW would return + * HPI_ERROR_INVALID_FUNC anyway. + */ + phr->error = HPI_ERROR_INVALID_FUNC; + break; + default: + hw_message(pao, phm, phr); + return; + } +} + +/************************************************************************/ +/** HPI_6000() + * Entry point from HPIMAN + * All calls to the HPI start here + */ +void HPI_6000(struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao = NULL; + + /* subsytem messages get executed by every HPI. */ + /* All other messages are ignored unless the adapter index matches */ + /* an adapter in the HPI */ + HPI_DEBUG_LOG(DEBUG, "O %d,F %x\n", phm->object, phm->function); + + /* if Dsp has crashed then do not communicate with it any more */ + if (phm->object != HPI_OBJ_SUBSYSTEM) { + pao = hpi_find_adapter(phm->adapter_index); + if (!pao) { + HPI_DEBUG_LOG(DEBUG, + " %d,%d refused, for another HPI?\n", + phm->object, phm->function); + return; + } + + if (pao->dsp_crashed >= 10) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_DSP_HARDWARE); + HPI_DEBUG_LOG(DEBUG, " %d,%d dsp crashed.\n", + phm->object, phm->function); + return; + } + } + /* Init default response including the size field */ + if (phm->function != HPI_SUBSYS_CREATE_ADAPTER) + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_PROCESSING_MESSAGE); + + switch (phm->type) { + case HPI_TYPE_MESSAGE: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr); + break; + + case HPI_OBJ_ADAPTER: + phr->size = + sizeof(struct hpi_response_header) + + sizeof(struct hpi_adapter_res); + adapter_message(pao, phm, phr); + break; + + case HPI_OBJ_CONTROL: + control_message(pao, phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(pao, phm, phr); + break; + + case HPI_OBJ_ISTREAM: + instream_message(pao, phm, phr); + break; + + default: + hw_message(pao, phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} + +/************************************************************************/ +/* SUBSYSTEM */ + +/* create an adapter object and initialise it based on resource information + * passed in in the message + * NOTE - you cannot use this function AND the FindAdapters function at the + * same time, the application must use only one of them to get the adapters + */ +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + /* create temp adapter obj, because we don't know what index yet */ + struct hpi_adapter_obj ao; + struct hpi_adapter_obj *pao; + u32 os_error_code; + short error = 0; + u32 dsp_index = 0; + + HPI_DEBUG_LOG(VERBOSE, "subsys_create_adapter\n"); + + memset(&ao, 0, sizeof(ao)); + + /* this HPI only creates adapters for TI/PCI2040 based devices */ + if (phm->u.s.resource.bus_type != HPI_BUS_PCI) + return; + if (phm->u.s.resource.r.pci->vendor_id != HPI_PCI_VENDOR_ID_TI) + return; + if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_PCI2040) + return; + + ao.priv = kmalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); + if (!ao.priv) { + HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n"); + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + + memset(ao.priv, 0, sizeof(struct hpi_hw_obj)); + /* create the adapter object based on the resource information */ + /*? memcpy(&ao.Pci,&phm->u.s.Resource.r.Pci,sizeof(ao.Pci)); */ + ao.pci = *phm->u.s.resource.r.pci; + + error = create_adapter_obj(&ao, &os_error_code); + if (!error) + error = hpi_add_adapter(&ao); + if (error) { + phr->u.s.data = os_error_code; + kfree(ao.priv); + phr->error = error; + return; + } + /* need to update paParentAdapter */ + pao = hpi_find_adapter(ao.index); + if (!pao) { + /* We just added this adapter, why can't we find it!? */ + HPI_DEBUG_LOG(ERROR, "lost adapter after boot\n"); + phr->error = 950; + return; + } + + for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) { + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + phw->ado[dsp_index].pa_parent_adapter = pao; + } + + phr->u.s.aw_adapter_list[ao.index] = ao.adapter_type; + phr->u.s.adapter_index = ao.index; + phr->u.s.num_adapters++; + phr->error = 0; +} + +static void subsys_delete_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao = NULL; + struct hpi_hw_obj *phw; + + pao = hpi_find_adapter(phm->adapter_index); + if (!pao) + return; + + phw = (struct hpi_hw_obj *)pao->priv; + + if (pao->has_control_cache) + hpi_free_control_cache(phw->p_cache); + + hpi_delete_adapter(pao); + kfree(phw); + + phr->error = 0; +} + +/* this routine is called from SubSysFindAdapter and SubSysCreateAdapter */ +static short create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + short boot_error = 0; + u32 dsp_index = 0; + u32 control_cache_size = 0; + u32 control_cache_count = 0; + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + + /* init error reporting */ + pao->dsp_crashed = 0; + + /* The PCI2040 has the following address map */ + /* BAR0 - 4K = HPI control and status registers on PCI2040 (HPI CSR) */ + /* BAR1 - 32K = HPI registers on DSP */ + phw->dw2040_HPICSR = pao->pci.ap_mem_base[0]; + phw->dw2040_HPIDSP = pao->pci.ap_mem_base[1]; + HPI_DEBUG_LOG(VERBOSE, "csr %p, dsp %p\n", phw->dw2040_HPICSR, + phw->dw2040_HPIDSP); + + /* set addresses for the possible DSP HPI interfaces */ + for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) { + phw->ado[dsp_index].prHPI_control = + phw->dw2040_HPIDSP + (CONTROL + + DSP_SPACING * dsp_index); + + phw->ado[dsp_index].prHPI_address = + phw->dw2040_HPIDSP + (ADDRESS + + DSP_SPACING * dsp_index); + phw->ado[dsp_index].prHPI_data = + phw->dw2040_HPIDSP + (DATA + DSP_SPACING * dsp_index); + + phw->ado[dsp_index].prHPI_data_auto_inc = + phw->dw2040_HPIDSP + (DATA_AUTOINC + + DSP_SPACING * dsp_index); + + HPI_DEBUG_LOG(VERBOSE, "ctl %p, adr %p, dat %p, dat++ %p\n", + phw->ado[dsp_index].prHPI_control, + phw->ado[dsp_index].prHPI_address, + phw->ado[dsp_index].prHPI_data, + phw->ado[dsp_index].prHPI_data_auto_inc); + + phw->ado[dsp_index].pa_parent_adapter = pao; + } + + phw->pCI2040HPI_error_count = 0; + pao->has_control_cache = 0; + + /* Set the default number of DSPs on this card */ + /* This is (conditionally) adjusted after bootloading */ + /* of the first DSP in the bootload section. */ + phw->num_dsp = 1; + + boot_error = hpi6000_adapter_boot_load_dsp(pao, pos_error_code); + if (boot_error) + return boot_error; + + HPI_DEBUG_LOG(INFO, "bootload DSP OK\n"); + + phw->message_buffer_address_on_dsp = 0L; + phw->response_buffer_address_on_dsp = 0L; + + /* get info about the adapter by asking the adapter */ + /* send a HPI_ADAPTER_GET_INFO message */ + { + struct hpi_message hM; + struct hpi_response hR0; /* response from DSP 0 */ + struct hpi_response hR1; /* response from DSP 1 */ + u16 error = 0; + + HPI_DEBUG_LOG(VERBOSE, "send ADAPTER_GET_INFO\n"); + memset(&hM, 0, sizeof(hM)); + hM.type = HPI_TYPE_MESSAGE; + hM.size = sizeof(struct hpi_message); + hM.object = HPI_OBJ_ADAPTER; + hM.function = HPI_ADAPTER_GET_INFO; + hM.adapter_index = 0; + memset(&hR0, 0, sizeof(hR0)); + memset(&hR1, 0, sizeof(hR1)); + hR0.size = sizeof(hR0); + hR1.size = sizeof(hR1); + + error = hpi6000_message_response_sequence(pao, 0, &hM, &hR0); + if (hR0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hR0.error); + return hR0.error; + } + if (phw->num_dsp == 2) { + error = hpi6000_message_response_sequence(pao, 1, &hM, + &hR1); + if (error) + return error; + } + pao->adapter_type = hR0.u.a.adapter_type; + pao->index = hR0.u.a.adapter_index; + } + + memset(&phw->control_cache[0], 0, + sizeof(struct hpi_control_cache_single) * + HPI_NMIXER_CONTROLS); + /* Read the control cache length to figure out if it is turned on */ + control_cache_size = + hpi_read_word(&phw->ado[0], + HPI_HIF_ADDR(control_cache_size_in_bytes)); + if (control_cache_size) { + control_cache_count = + hpi_read_word(&phw->ado[0], + HPI_HIF_ADDR(control_cache_count)); + pao->has_control_cache = 1; + + phw->p_cache = + hpi_alloc_control_cache(control_cache_count, + control_cache_size, (struct hpi_control_cache_info *) + &phw->control_cache[0] + ); + } else + pao->has_control_cache = 0; + + HPI_DEBUG_LOG(DEBUG, "get adapter info ASI%04X index %d\n", + pao->adapter_type, pao->index); + pao->open = 0; /* upon creation the adapter is closed */ + return 0; +} + +/************************************************************************/ +/* ADAPTER */ + +static void adapter_get_asserts(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ +#ifndef HIDE_PCI_ASSERTS + /* if we have PCI2040 asserts then collect them */ + if ((gw_pci_read_asserts > 0) || (gw_pci_write_asserts > 0)) { + phr->u.a.serial_number = + gw_pci_read_asserts * 100 + gw_pci_write_asserts; + phr->u.a.adapter_index = 1; /* assert count */ + phr->u.a.adapter_type = -1; /* "dsp index" */ + strcpy(phr->u.a.sz_adapter_assert, "PCI2040 error"); + gw_pci_read_asserts = 0; + gw_pci_write_asserts = 0; + phr->error = 0; + } else +#endif + hw_message(pao, phm, phr); /*get DSP asserts */ + + return; +} + +/************************************************************************/ +/* LOW-LEVEL */ + +static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + short error; + u32 timeout; + u32 read = 0; + u32 i = 0; + u32 data = 0; + u32 j = 0; + u32 test_addr = 0x80000000; + u32 test_data = 0x00000001; + u32 dw2040_reset = 0; + u32 dsp_index = 0; + u32 endian = 0; + u32 adapter_info = 0; + u32 delay = 0; + + struct dsp_code dsp_code; + u16 boot_load_family = 0; + + /* NOTE don't use wAdapterType in this routine. It is not setup yet */ + + switch (pao->pci.subsys_device_id) { + case 0x5100: + case 0x5110: /* ASI5100 revB or higher with C6711D */ + case 0x6100: + case 0x6200: + boot_load_family = HPI_ADAPTER_FAMILY_ASI(0x6200); + break; + case 0x8800: + boot_load_family = HPI_ADAPTER_FAMILY_ASI(0x8800); + break; + default: + return HPI6000_ERROR_UNHANDLED_SUBSYS_ID; + } + + /* reset all DSPs, indicate two DSPs are present + * set RST3-=1 to disconnect HAD8 to set DSP in little endian mode + */ + endian = 0; + dw2040_reset = 0x0003000F; + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + /* read back register to make sure PCI2040 chip is functioning + * note that bits 4..15 are read-only and so should always return zero, + * even though we wrote 1 to them + */ + for (i = 0; i < 1000; i++) + delay = ioread32(phw->dw2040_HPICSR + HPI_RESET); + if (delay != dw2040_reset) { + HPI_DEBUG_LOG(ERROR, "INIT_PCI2040 %x %x\n", dw2040_reset, + delay); + return HPI6000_ERROR_INIT_PCI2040; + } + + /* Indicate that DSP#0,1 is a C6X */ + iowrite32(0x00000003, phw->dw2040_HPICSR + HPI_DATA_WIDTH); + /* set Bit30 and 29 - which will prevent Target aborts from being + * issued upon HPI or GP error + */ + iowrite32(0x60000000, phw->dw2040_HPICSR + INTERRUPT_MASK_SET); + + /* isolate DSP HAD8 line from PCI2040 so that + * Little endian can be set by pullup + */ + dw2040_reset = dw2040_reset & (~(endian << 3)); + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + phw->ado[0].c_dsp_rev = 'B'; /* revB */ + phw->ado[1].c_dsp_rev = 'B'; /* revB */ + + /*Take both DSPs out of reset, setting HAD8 to the correct Endian */ + dw2040_reset = dw2040_reset & (~0x00000001); /* start DSP 0 */ + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + dw2040_reset = dw2040_reset & (~0x00000002); /* start DSP 1 */ + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + + /* set HAD8 back to PCI2040, now that DSP set to little endian mode */ + dw2040_reset = dw2040_reset & (~0x00000008); + iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET); + /*delay to allow DSP to get going */ + for (i = 0; i < 100; i++) + delay = ioread32(phw->dw2040_HPICSR + HPI_RESET); + + /* loop through all DSPs, downloading DSP code */ + for (dsp_index = 0; dsp_index < phw->num_dsp; dsp_index++) { + struct dsp_obj *pdo = &phw->ado[dsp_index]; + + /* configure DSP so that we download code into the SRAM */ + /* set control reg for little endian, HWOB=1 */ + iowrite32(0x00010001, pdo->prHPI_control); + + /* test access to the HPI address register (HPIA) */ + test_data = 0x00000001; + for (j = 0; j < 32; j++) { + iowrite32(test_data, pdo->prHPI_address); + data = ioread32(pdo->prHPI_address); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, "INIT_DSPHPI %x %x %x\n", + test_data, data, dsp_index); + return HPI6000_ERROR_INIT_DSPHPI; + } + test_data = test_data << 1; + } + +/* if C6713 the setup PLL to generate 225MHz from 25MHz. +* Since the PLLDIV1 read is sometimes wrong, even on a C6713, +* we're going to do this unconditionally +*/ +/* PLLDIV1 should have a value of 8000 after reset */ +/* + if (HpiReadWord(pdo,0x01B7C118) == 0x8000) +*/ + { + /* C6713 datasheet says we cannot program PLL from HPI, + * and indeed if we try to set the PLL multiply from the + * HPI, the PLL does not seem to lock, + * so we enable the PLL and use the default of x 7 + */ + /* bypass PLL */ + hpi_write_word(pdo, 0x01B7C100, 0x0000); + for (i = 0; i < 100; i++) + delay = ioread32(phw->dw2040_HPICSR + + HPI_RESET); + + /* ** use default of PLL x7 ** */ + /* EMIF = 225/3=75MHz */ + hpi_write_word(pdo, 0x01B7C120, 0x8002); + /* peri = 225/2 */ + hpi_write_word(pdo, 0x01B7C11C, 0x8001); + /* cpu = 225/1 */ + hpi_write_word(pdo, 0x01B7C118, 0x8000); + /* ~200us delay */ + for (i = 0; i < 2000; i++) + delay = ioread32(phw->dw2040_HPICSR + + HPI_RESET); + /* PLL not bypassed */ + hpi_write_word(pdo, 0x01B7C100, 0x0001); + /* ~200us delay */ + for (i = 0; i < 2000; i++) + delay = ioread32(phw->dw2040_HPICSR + + HPI_RESET); + } + + /* test r/w to internal DSP memory + * C6711 has L2 cache mapped to 0x0 when reset + * + * revB - because of bug 3.0.1 last HPI read + * (before HPI address issued) must be non-autoinc + */ + /* test each bit in the 32bit word */ + for (i = 0; i < 100; i++) { + test_addr = 0x00000000; + test_data = 0x00000001; + for (j = 0; j < 32; j++) { + hpi_write_word(pdo, test_addr + i, test_data); + data = hpi_read_word(pdo, test_addr + i); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP mem %x %x %x %x\n", + test_addr + i, test_data, + data, dsp_index); + + return HPI6000_ERROR_INIT_DSPINTMEM; + } + test_data = test_data << 1; + } + } + + /* memory map of ASI6200 + 00000000-0000FFFF 16Kx32 internal program + 01800000-019FFFFF Internal peripheral + 80000000-807FFFFF CE0 2Mx32 SDRAM running @ 100MHz + 90000000-9000FFFF CE1 Async peripherals: + + EMIF config + ------------ + Global EMIF control + 0 - + 1 - + 2 - + 3 CLK2EN = 1 CLKOUT2 enabled + 4 CLK1EN = 0 CLKOUT1 disabled + 5 EKEN = 1 <--!! C6713 specific, enables ECLKOUT + 6 - + 7 NOHOLD = 1 external HOLD disabled + 8 HOLDA = 0 HOLDA output is low + 9 HOLD = 0 HOLD input is low + 10 ARDY = 1 ARDY input is high + 11 BUSREQ = 0 BUSREQ output is low + 12,13 Reserved = 1 + */ + hpi_write_word(pdo, 0x01800000, 0x34A8); + + /* EMIF CE0 setup - 2Mx32 Sync DRAM + 31..28 Wr setup + 27..22 Wr strobe + 21..20 Wr hold + 19..16 Rd setup + 15..14 - + 13..8 Rd strobe + 7..4 MTYPE 0011 Sync DRAM 32bits + 3 Wr hold MSB + 2..0 Rd hold + */ + hpi_write_word(pdo, 0x01800008, 0x00000030); + + /* EMIF SDRAM Extension + 31-21 0 + 20 WR2RD = 0 + 19-18 WR2DEAC = 1 + 17 WR2WR = 0 + 16-15 R2WDQM = 2 + 14-12 RD2WR = 4 + 11-10 RD2DEAC = 1 + 9 RD2RD = 1 + 8-7 THZP = 10b + 6-5 TWR = 2-1 = 01b (tWR = 10ns) + 4 TRRD = 0b = 2 ECLK (tRRD = 14ns) + 3-1 TRAS = 5-1 = 100b (Tras=42ns = 5 ECLK) + 1 CAS latency = 3 ECLK + (for Micron 2M32-7 operating at 100Mhz) + */ + + /* need to use this else DSP code crashes */ + hpi_write_word(pdo, 0x01800020, 0x001BDF29); + + /* EMIF SDRAM control - set up for a 2Mx32 SDRAM (512x32x4 bank) + 31 - - + 30 SDBSZ 1 4 bank + 29..28 SDRSZ 00 11 row address pins + 27..26 SDCSZ 01 8 column address pins + 25 RFEN 1 refersh enabled + 24 INIT 1 init SDRAM + 23..20 TRCD 0001 + 19..16 TRP 0001 + 15..12 TRC 0110 + 11..0 - - + */ + /* need to use this else DSP code crashes */ + hpi_write_word(pdo, 0x01800018, 0x47117000); + + /* EMIF SDRAM Refresh Timing */ + hpi_write_word(pdo, 0x0180001C, 0x00000410); + + /*MIF CE1 setup - Async peripherals + @100MHz bus speed, each cycle is 10ns, + 31..28 Wr setup = 1 + 27..22 Wr strobe = 3 30ns + 21..20 Wr hold = 1 + 19..16 Rd setup =1 + 15..14 Ta = 2 + 13..8 Rd strobe = 3 30ns + 7..4 MTYPE 0010 Async 32bits + 3 Wr hold MSB =0 + 2..0 Rd hold = 1 + */ + { + u32 cE1 = + (1L << 28) | (3L << 22) | (1L << 20) | (1L << + 16) | (2L << 14) | (3L << 8) | (2L << 4) | 1L; + hpi_write_word(pdo, 0x01800004, cE1); + } + + /* delay a little to allow SDRAM and DSP to "get going" */ + + for (i = 0; i < 1000; i++) + delay = ioread32(phw->dw2040_HPICSR + HPI_RESET); + + /* test access to SDRAM */ + { + test_addr = 0x80000000; + test_data = 0x00000001; + /* test each bit in the 32bit word */ + for (j = 0; j < 32; j++) { + hpi_write_word(pdo, test_addr, test_data); + data = hpi_read_word(pdo, test_addr); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP dram %x %x %x %x\n", + test_addr, test_data, data, + dsp_index); + + return HPI6000_ERROR_INIT_SDRAM1; + } + test_data = test_data << 1; + } + /* test every Nth address in the DRAM */ +#define DRAM_SIZE_WORDS 0x200000 /*2_mx32 */ +#define DRAM_INC 1024 + test_addr = 0x80000000; + test_data = 0x0; + for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) { + hpi_write_word(pdo, test_addr + i, test_data); + test_data++; + } + test_addr = 0x80000000; + test_data = 0x0; + for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) { + data = hpi_read_word(pdo, test_addr + i); + if (data != test_data) { + HPI_DEBUG_LOG(ERROR, + "DSP dram %x %x %x %x\n", + test_addr + i, test_data, + data, dsp_index); + return HPI6000_ERROR_INIT_SDRAM2; + } + test_data++; + } + + } + + /* write the DSP code down into the DSPs memory */ + /*HpiDspCode_Open(nBootLoadFamily,&DspCode,pdwOsErrorCode); */ + dsp_code.ps_dev = pao->pci.p_os_data; + + error = hpi_dsp_code_open(boot_load_family, &dsp_code, + pos_error_code); + + if (error) + return error; + + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + error = hpi_dsp_code_read_word(&dsp_code, &length); + if (error) + break; + if (length == 0xFFFFFFFF) + break; /* end of code */ + + error = hpi_dsp_code_read_word(&dsp_code, &address); + if (error) + break; + error = hpi_dsp_code_read_word(&dsp_code, &type); + if (error) + break; + error = hpi_dsp_code_read_block(length, &dsp_code, + &pcode); + if (error) + break; + error = hpi6000_dsp_block_write32(pao, (u16)dsp_index, + address, pcode, length); + if (error) + break; + } + + if (error) { + hpi_dsp_code_close(&dsp_code); + return error; + } + /* verify that code was written correctly */ + /* this time through, assume no errors in DSP code file/array */ + hpi_dsp_code_rewind(&dsp_code); + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + hpi_dsp_code_read_word(&dsp_code, &length); + if (length == 0xFFFFFFFF) + break; /* end of code */ + + hpi_dsp_code_read_word(&dsp_code, &address); + hpi_dsp_code_read_word(&dsp_code, &type); + hpi_dsp_code_read_block(length, &dsp_code, &pcode); + + for (i = 0; i < length; i++) { + data = hpi_read_word(pdo, address); + if (data != *pcode) { + error = HPI6000_ERROR_INIT_VERIFY; + HPI_DEBUG_LOG(ERROR, + "DSP verify %x %x %x %x\n", + address, *pcode, data, + dsp_index); + break; + } + pcode++; + address += 4; + } + if (error) + break; + } + hpi_dsp_code_close(&dsp_code); + if (error) + return error; + + /* zero out the hostmailbox */ + { + u32 address = HPI_HIF_ADDR(host_cmd); + for (i = 0; i < 4; i++) { + hpi_write_word(pdo, address, 0); + address += 4; + } + } + /* write the DSP number into the hostmailbox */ + /* structure before starting the DSP */ + hpi_write_word(pdo, HPI_HIF_ADDR(dsp_number), dsp_index); + + /* write the DSP adapter Info into the */ + /* hostmailbox before starting the DSP */ + if (dsp_index > 0) + hpi_write_word(pdo, HPI_HIF_ADDR(adapter_info), + adapter_info); + + /* step 3. Start code by sending interrupt */ + iowrite32(0x00030003, pdo->prHPI_control); + for (i = 0; i < 10000; i++) + delay = ioread32(phw->dw2040_HPICSR + HPI_RESET); + + /* wait for a non-zero value in hostcmd - + * indicating initialization is complete + * + * Init could take a while if DSP checks SDRAM memory + * Was 200000. Increased to 2000000 for ASI8801 so we + * don't get 938 errors. + */ + timeout = 2000000; + while (timeout) { + do { + read = hpi_read_word(pdo, + HPI_HIF_ADDR(host_cmd)); + } while (--timeout + && hpi6000_check_PCI2040_error_flag(pao, + H6READ)); + + if (read) + break; + /* The following is a workaround for bug #94: + * Bluescreen on install and subsequent boots on a + * DELL PowerEdge 600SC PC with 1.8GHz P4 and + * ServerWorks chipset. Without this delay the system + * locks up with a bluescreen (NOT GPF or pagefault). + */ + else + hpios_delay_micro_seconds(1000); + } + if (timeout == 0) + return HPI6000_ERROR_INIT_NOACK; + + /* read the DSP adapter Info from the */ + /* hostmailbox structure after starting the DSP */ + if (dsp_index == 0) { + /*u32 dwTestData=0; */ + u32 mask = 0; + + adapter_info = + hpi_read_word(pdo, + HPI_HIF_ADDR(adapter_info)); + if (HPI_ADAPTER_FAMILY_ASI + (HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER + (adapter_info)) == + HPI_ADAPTER_FAMILY_ASI(0x6200)) + /* all 6200 cards have this many DSPs */ + phw->num_dsp = 2; + + /* test that the PLD is programmed */ + /* and we can read/write 24bits */ +#define PLD_BASE_ADDRESS 0x90000000L /*for ASI6100/6200/8800 */ + + switch (boot_load_family) { + case HPI_ADAPTER_FAMILY_ASI(0x6200): + /* ASI6100/6200 has 24bit path to FPGA */ + mask = 0xFFFFFF00L; + /* ASI5100 uses AX6 code, */ + /* but has no PLD r/w register to test */ + if (HPI_ADAPTER_FAMILY_ASI(pao->pci. + subsys_device_id) == + HPI_ADAPTER_FAMILY_ASI(0x5100)) + mask = 0x00000000L; + break; + case HPI_ADAPTER_FAMILY_ASI(0x8800): + /* ASI8800 has 16bit path to FPGA */ + mask = 0xFFFF0000L; + break; + } + test_data = 0xAAAAAA00L & mask; + /* write to 24 bit Debug register (D31-D8) */ + hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data); + read = hpi_read_word(pdo, + PLD_BASE_ADDRESS + 4L) & mask; + if (read != test_data) { + HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data, + read); + return HPI6000_ERROR_INIT_PLDTEST1; + } + test_data = 0x55555500L & mask; + hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data); + read = hpi_read_word(pdo, + PLD_BASE_ADDRESS + 4L) & mask; + if (read != test_data) { + HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data, + read); + return HPI6000_ERROR_INIT_PLDTEST2; + } + } + } /* for numDSP */ + return 0; +} + +#define PCI_TIMEOUT 100 + +static int hpi_set_address(struct dsp_obj *pdo, u32 address) +{ + u32 timeout = PCI_TIMEOUT; + + do { + iowrite32(address, pdo->prHPI_address); + } while (hpi6000_check_PCI2040_error_flag(pdo->pa_parent_adapter, + H6WRITE) + && --timeout); + + if (timeout) + return 0; + + return 1; +} + +/* write one word to the HPI port */ +static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data) +{ + if (hpi_set_address(pdo, address)) + return; + iowrite32(data, pdo->prHPI_data); +} + +/* read one word from the HPI port */ +static u32 hpi_read_word(struct dsp_obj *pdo, u32 address) +{ + u32 data = 0; + + if (hpi_set_address(pdo, address)) + return 0; /*? no way to return error */ + + /* take care of errata in revB DSP (2.0.1) */ + data = ioread32(pdo->prHPI_data); + return data; +} + +/* write a block of 32bit words to the DSP HPI port using auto-inc mode */ +static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length) +{ + u16 length16 = length - 1; + + if (length == 0) + return; + + if (hpi_set_address(pdo, address)) + return; + + iowrite32_rep(pdo->prHPI_data_auto_inc, pdata, length16); + + /* take care of errata in revB DSP (2.0.1) */ + /* must end with non auto-inc */ + iowrite32(*(pdata + length - 1), pdo->prHPI_data); +} + +/** read a block of 32bit words from the DSP HPI port using auto-inc mode + */ +static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata, + u32 length) +{ + u16 length16 = length - 1; + + if (length == 0) + return; + + if (hpi_set_address(pdo, address)) + return; + + ioread32_rep(pdo->prHPI_data_auto_inc, pdata, length16); + + /* take care of errata in revB DSP (2.0.1) */ + /* must end with non auto-inc */ + *(pdata + length - 1) = ioread32(pdo->prHPI_data); +} + +static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *source, u32 count) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 time_out = PCI_TIMEOUT; + int c6711_burst_size = 128; + u32 local_hpi_address = hpi_address; + int local_count = count; + int xfer_size; + u32 *pdata = source; + + while (local_count) { + if (local_count > c6711_burst_size) + xfer_size = c6711_burst_size; + else + xfer_size = local_count; + + time_out = PCI_TIMEOUT; + do { + hpi_write_block(pdo, local_hpi_address, pdata, + xfer_size); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) + && --time_out); + + if (!time_out) + break; + pdata += xfer_size; + local_hpi_address += sizeof(u32) * xfer_size; + local_count -= xfer_size; + } + + if (time_out) + return 0; + else + return 1; +} + +static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 hpi_address, u32 *dest, u32 count) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 time_out = PCI_TIMEOUT; + int c6711_burst_size = 16; + u32 local_hpi_address = hpi_address; + int local_count = count; + int xfer_size; + u32 *pdata = dest; + u32 loop_count = 0; + + while (local_count) { + if (local_count > c6711_burst_size) + xfer_size = c6711_burst_size; + else + xfer_size = local_count; + + time_out = PCI_TIMEOUT; + do { + hpi_read_block(pdo, local_hpi_address, pdata, + xfer_size); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --time_out); + if (!time_out) + break; + + pdata += xfer_size; + local_hpi_address += sizeof(u32) * xfer_size; + local_count -= xfer_size; + loop_count++; + } + + if (time_out) + return 0; + else + return 1; +} + +static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao, + u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 timeout; + u16 ack; + u32 address; + u32 length; + u32 *p_data; + u16 error = 0; + + /* does the DSP we are referencing exist? */ + if (dsp_index >= phw->num_dsp) + return HPI6000_ERROR_MSG_INVALID_DSP_INDEX; + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) { + pao->dsp_crashed++; + return HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + pao->dsp_crashed = 0; + + /* send the message */ + + /* get the address and size */ + if (phw->message_buffer_address_on_dsp == 0) { + timeout = TIMEOUT; + do { + address = + hpi_read_word(pdo, + HPI_HIF_ADDR(message_buffer_address)); + phw->message_buffer_address_on_dsp = address; + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + if (!timeout) + return HPI6000_ERROR_MSG_GET_ADR; + } else + address = phw->message_buffer_address_on_dsp; + + /* dwLength = sizeof(struct hpi_message); */ + length = phm->size; + + /* send it */ + p_data = (u32 *)phm; + if (hpi6000_dsp_block_write32(pao, dsp_index, address, p_data, + (u16)length / 4)) + return HPI6000_ERROR_MSG_RESP_BLOCKWRITE32; + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_GET_RESP)) + return HPI6000_ERROR_MSG_RESP_GETRESPCMD; + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_RESP); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_MSG_RESP_GET_RESP_ACK; + + /* get the address and size */ + if (phw->response_buffer_address_on_dsp == 0) { + timeout = TIMEOUT; + do { + address = + hpi_read_word(pdo, + HPI_HIF_ADDR(response_buffer_address)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + phw->response_buffer_address_on_dsp = address; + + if (!timeout) + return HPI6000_ERROR_RESP_GET_ADR; + } else + address = phw->response_buffer_address_on_dsp; + + /* read the length of the response back from the DSP */ + timeout = TIMEOUT; + do { + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout); + if (!timeout) + length = sizeof(struct hpi_response); + + /* get it */ + p_data = (u32 *)phr; + if (hpi6000_dsp_block_read32(pao, dsp_index, address, p_data, + (u16)length / 4)) + return HPI6000_ERROR_MSG_RESP_BLOCKREAD32; + + /* set i/f back to idle */ + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_MSG_RESP_IDLECMD; + hpi6000_send_dsp_interrupt(pdo); + + error = hpi_validate_response(phm, phr); + return error; +} + +/* have to set up the below defines to match stuff in the MAP file */ + +#define MSG_ADDRESS (HPI_HIF_BASE+0x18) +#define MSG_LENGTH 11 +#define RESP_ADDRESS (HPI_HIF_BASE+0x44) +#define RESP_LENGTH 16 +#define QUEUE_START (HPI_HIF_BASE+0x88) +#define QUEUE_SIZE 0x8000 + +static short hpi6000_send_data_check_adr(u32 address, u32 length_in_dwords) +{ +/*#define CHECKING // comment this line in to enable checking */ +#ifdef CHECKING + if (address < (u32)MSG_ADDRESS) + return 0; + if (address > (u32)(QUEUE_START + QUEUE_SIZE)) + return 0; + if ((address + (length_in_dwords << 2)) > + (u32)(QUEUE_START + QUEUE_SIZE)) + return 0; +#else + (void)address; + (void)length_in_dwords; + return 1; +#endif +} + +static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 data_sent = 0; + u16 ack; + u32 length, address; + u32 *p_data = (u32 *)phm->u.d.u.data.pb_data; + u16 time_out = 8; + + (void)phr; + + /* round dwDataSize down to nearest 4 bytes */ + while ((data_sent < (phm->u.d.u.data.data_size & ~3L)) + && --time_out) { + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT; + + if (hpi6000_send_host_command(pao, dsp_index, + HPI_HIF_SEND_DATA)) + return HPI6000_ERROR_SEND_DATA_CMD; + + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_SEND_DATA); + + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_SEND_DATA_ACK; + + do { + /* get the address and size */ + address = hpi_read_word(pdo, HPI_HIF_ADDR(address)); + /* DSP returns number of DWORDS */ + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ)); + + if (!hpi6000_send_data_check_adr(address, length)) + return HPI6000_ERROR_SEND_DATA_ADR; + + /* send the data. break data into 512 DWORD blocks (2K bytes) + * and send using block write. 2Kbytes is the max as this is the + * memory window given to the HPI data register by the PCI2040 + */ + + { + u32 len = length; + u32 blk_len = 512; + while (len) { + if (len < blk_len) + blk_len = len; + if (hpi6000_dsp_block_write32(pao, dsp_index, + address, p_data, blk_len)) + return HPI6000_ERROR_SEND_DATA_WRITE; + address += blk_len * 4; + p_data += blk_len; + len -= blk_len; + } + } + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_SEND_DATA_IDLECMD; + + hpi6000_send_dsp_interrupt(pdo); + + data_sent += length * 4; + } + if (!time_out) + return HPI6000_ERROR_SEND_DATA_TIMEOUT; + return 0; +} + +static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 data_got = 0; + u16 ack; + u32 length, address; + u32 *p_data = (u32 *)phm->u.d.u.data.pb_data; + + (void)phr; /* this parameter not used! */ + + /* round dwDataSize down to nearest 4 bytes */ + while (data_got < (phm->u.d.u.data.data_size & ~3L)) { + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE); + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT; + + if (hpi6000_send_host_command(pao, dsp_index, + HPI_HIF_GET_DATA)) + return HPI6000_ERROR_GET_DATA_CMD; + hpi6000_send_dsp_interrupt(pdo); + + ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_DATA); + + if (ack & HPI_HIF_ERROR_MASK) + return HPI6000_ERROR_GET_DATA_ACK; + + /* get the address and size */ + do { + address = hpi_read_word(pdo, HPI_HIF_ADDR(address)); + length = hpi_read_word(pdo, HPI_HIF_ADDR(length)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ)); + + /* read the data */ + { + u32 len = length; + u32 blk_len = 512; + while (len) { + if (len < blk_len) + blk_len = len; + if (hpi6000_dsp_block_read32(pao, dsp_index, + address, p_data, blk_len)) + return HPI6000_ERROR_GET_DATA_READ; + address += blk_len * 4; + p_data += blk_len; + len -= blk_len; + } + } + + if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE)) + return HPI6000_ERROR_GET_DATA_IDLECMD; + hpi6000_send_dsp_interrupt(pdo); + + data_got += length * 4; + } + return 0; +} + +static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo) +{ + iowrite32(0x00030003, pdo->prHPI_control); /* DSPINT */ +} + +static short hpi6000_send_host_command(struct hpi_adapter_obj *pao, + u16 dsp_index, u32 host_cmd) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 timeout = TIMEOUT; + + /* set command */ + do { + hpi_write_word(pdo, HPI_HIF_ADDR(host_cmd), host_cmd); + /* flush the FIFO */ + hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) && --timeout); + + /* reset the interrupt bit */ + iowrite32(0x00040004, pdo->prHPI_control); + + if (timeout) + return 0; + else + return 1; +} + +/* if the PCI2040 has recorded an HPI timeout, reset the error and return 1 */ +static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao, + u16 read_or_write) +{ + u32 hPI_error; + + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + + /* read the error bits from the PCI2040 */ + hPI_error = ioread32(phw->dw2040_HPICSR + HPI_ERROR_REPORT); + if (hPI_error) { + /* reset the error flag */ + iowrite32(0L, phw->dw2040_HPICSR + HPI_ERROR_REPORT); + phw->pCI2040HPI_error_count++; + if (read_or_write == 1) + gw_pci_read_asserts++; /************* inc global */ + else + gw_pci_write_asserts++; + return 1; + } else + return 0; +} + +static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index, + u32 ack_value) +{ + struct dsp_obj *pdo = + &(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index]; + u32 ack = 0L; + u32 timeout; + u32 hPIC = 0L; + + /* wait for host interrupt to signal ack is ready */ + timeout = TIMEOUT; + while (--timeout) { + hPIC = ioread32(pdo->prHPI_control); + if (hPIC & 0x04) /* 0x04 = HINT from DSP */ + break; + } + if (timeout == 0) + return HPI_HIF_ERROR_MASK; + + /* wait for dwAckValue */ + timeout = TIMEOUT; + while (--timeout) { + /* read the ack mailbox */ + ack = hpi_read_word(pdo, HPI_HIF_ADDR(dsp_ack)); + if (ack == ack_value) + break; + if ((ack & HPI_HIF_ERROR_MASK) + && !hpi6000_check_PCI2040_error_flag(pao, H6READ)) + break; + /*for (i=0;i<1000;i++) */ + /* dwPause=i+1; */ + } + if (ack & HPI_HIF_ERROR_MASK) + /* indicates bad read from DSP - + typically 0xffffff is read for some reason */ + ack = HPI_HIF_ERROR_MASK; + + if (timeout == 0) + ack = HPI_HIF_ERROR_MASK; + return (short)ack; +} + +static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao, + struct hpi_message *phm) +{ + const u16 dsp_index = 0; + struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv; + struct dsp_obj *pdo = &phw->ado[dsp_index]; + u32 timeout; + u32 cache_dirty_flag; + u16 err; + + hpios_dsplock_lock(pao); + + timeout = TIMEOUT; + do { + cache_dirty_flag = + hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_is_dirty)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_PARAMS; + goto unlock; + } + + if (cache_dirty_flag) { + /* read the cached controls */ + u32 address; + u32 length; + + timeout = TIMEOUT; + if (pdo->control_cache_address_on_dsp == 0) { + do { + address = + hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_address)); + + length = hpi_read_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR + (control_cache_size_in_bytes)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6READ) + && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_ADDRLEN; + goto unlock; + } + pdo->control_cache_address_on_dsp = address; + pdo->control_cache_length_on_dsp = length; + } else { + address = pdo->control_cache_address_on_dsp; + length = pdo->control_cache_length_on_dsp; + } + + if (hpi6000_dsp_block_read32(pao, dsp_index, address, + (u32 *)&phw->control_cache[0], + length / sizeof(u32))) { + err = HPI6000_ERROR_CONTROL_CACHE_READ; + goto unlock; + } + do { + hpi_write_word((struct dsp_obj *)pdo, + HPI_HIF_ADDR(control_cache_is_dirty), 0); + /* flush the FIFO */ + hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd)); + } while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) + && --timeout); + if (!timeout) { + err = HPI6000_ERROR_CONTROL_CACHE_FLUSH; + goto unlock; + } + + } + err = 0; + +unlock: + hpios_dsplock_unlock(pao); + return err; +} + +/** Get dsp index for multi DSP adapters only */ +static u16 get_dsp_index(struct hpi_adapter_obj *pao, struct hpi_message *phm) +{ + u16 ret = 0; + switch (phm->object) { + case HPI_OBJ_ISTREAM: + if (phm->obj_index < 2) + ret = 1; + break; + case HPI_OBJ_PROFILE: + ret = phm->obj_index; + break; + default: + break; + } + return ret; +} + +/** Complete transaction with DSP + +Send message, get response, send or get stream data if any. +*/ +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + u16 error = 0; + u16 dsp_index = 0; + u16 num_dsp = ((struct hpi_hw_obj *)pao->priv)->num_dsp; + hpios_dsplock_lock(pao); + + if (num_dsp < 2) + dsp_index = 0; + else { + dsp_index = get_dsp_index(pao, phm); + + /* is this checked on the DSP anyway? */ + if ((phm->function == HPI_ISTREAM_GROUP_ADD) + || (phm->function == HPI_OSTREAM_GROUP_ADD)) { + struct hpi_message hm; + u16 add_index; + hm.obj_index = phm->u.d.u.stream.stream_index; + hm.object = phm->u.d.u.stream.object_type; + add_index = get_dsp_index(pao, &hm); + if (add_index != dsp_index) { + phr->error = HPI_ERROR_NO_INTERDSP_GROUPS; + return; + } + } + } + error = hpi6000_message_response_sequence(pao, dsp_index, phm, phr); + + /* maybe an error response */ + if (error) { + /* something failed in the HPI/DSP interface */ + phr->error = error; + /* just the header of the response is valid */ + phr->size = sizeof(struct hpi_response_header); + goto err; + } + + if (phr->error != 0) /* something failed in the DSP */ + goto err; + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_ANC_WRITE: + error = hpi6000_send_data(pao, dsp_index, phm, phr); + break; + case HPI_ISTREAM_READ: + case HPI_OSTREAM_ANC_READ: + error = hpi6000_get_data(pao, dsp_index, phm, phr); + break; + case HPI_ADAPTER_GET_ASSERT: + phr->u.a.adapter_index = 0; /* dsp 0 default */ + if (num_dsp == 2) { + if (!phr->u.a.adapter_type) { + /* no assert from dsp 0, check dsp 1 */ + error = hpi6000_message_response_sequence(pao, + 1, phm, phr); + phr->u.a.adapter_index = 1; + } + } + } + + if (error) + phr->error = error; + +err: + hpios_dsplock_unlock(pao); + return; +} diff --git a/sound/pci/asihpi/hpi6000.h b/sound/pci/asihpi/hpi6000.h new file mode 100644 index 00000000000..4c7d507c0ec --- /dev/null +++ b/sound/pci/asihpi/hpi6000.h @@ -0,0 +1,70 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Public declarations for DSP Proramming Interface to TI C6701 + +Shared between hpi6000.c and DSP code + +(C) Copyright AudioScience Inc. 1998-2003 +******************************************************************************/ + +#ifndef _HPI6000_H_ +#define _HPI6000_H_ + +#define HPI_NMIXER_CONTROLS 200 + +/* + * Control caching is always supported in the HPI code. + * The DSP should make sure that dwControlCacheSizeInBytes is initialized to 0 + * during boot to make it in-active. + */ +struct hpi_hif_6000 { + u32 host_cmd; + u32 dsp_ack; + u32 address; + u32 length; + u32 message_buffer_address; + u32 response_buffer_address; + u32 dsp_number; + u32 adapter_info; + u32 control_cache_is_dirty; + u32 control_cache_address; + u32 control_cache_size_in_bytes; + u32 control_cache_count; +}; + +#define HPI_HIF_PACK_ADAPTER_INFO(adapter, version_major, version_minor) \ + ((adapter << 16) | (version_major << 8) | version_minor) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER(adapterinfo) \ + ((adapterinfo >> 16) & 0xffff) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MAJOR(adapterinfo) \ + ((adapterinfo >> 8) & 0xff) +#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MINOR(adapterinfo) \ + (adapterinfo & 0xff) + +/* Command/status exchanged between host and DSP */ +#define HPI_HIF_IDLE 0 +#define HPI_HIF_SEND_MSG 1 +#define HPI_HIF_GET_RESP 2 +#define HPI_HIF_DATA_MASK 0x10 +#define HPI_HIF_SEND_DATA 0x13 +#define HPI_HIF_GET_DATA 0x14 +#define HPI_HIF_SEND_DONE 5 +#define HPI_HIF_RESET 9 + +#endif /* _HPI6000_H_ */ diff --git a/sound/pci/asihpi/hpi6205.c b/sound/pci/asihpi/hpi6205.c new file mode 100644 index 00000000000..8df2ff76d94 --- /dev/null +++ b/sound/pci/asihpi/hpi6205.c @@ -0,0 +1,2332 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + Hardware Programming Interface (HPI) for AudioScience + ASI50xx, AS51xx, ASI6xxx, ASI87xx ASI89xx series adapters. + These PCI and PCIe bus adapters are based on a + TMS320C6205 PCI bus mastering DSP, + and (except ASI50xx) TI TMS320C6xxx floating point DSP + + Exported function: + void HPI_6205(struct hpi_message *phm, struct hpi_response *phr) + +(C) Copyright AudioScience Inc. 1998-2010 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpi6205.c" + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpi6205.h" +#include "hpidspcd.h" +#include "hpicmn.h" + +/*****************************************************************************/ +/* HPI6205 specific error codes */ +#define HPI6205_ERROR_BASE 1000 +/*#define HPI6205_ERROR_MEM_ALLOC 1001 */ +#define HPI6205_ERROR_6205_NO_IRQ 1002 +#define HPI6205_ERROR_6205_INIT_FAILED 1003 +/*#define HPI6205_ERROR_MISSING_DSPCODE 1004 */ +#define HPI6205_ERROR_UNKNOWN_PCI_DEVICE 1005 +#define HPI6205_ERROR_6205_REG 1006 +#define HPI6205_ERROR_6205_DSPPAGE 1007 +#define HPI6205_ERROR_BAD_DSPINDEX 1008 +#define HPI6205_ERROR_C6713_HPIC 1009 +#define HPI6205_ERROR_C6713_HPIA 1010 +#define HPI6205_ERROR_C6713_PLL 1011 +#define HPI6205_ERROR_DSP_INTMEM 1012 +#define HPI6205_ERROR_DSP_EXTMEM 1013 +#define HPI6205_ERROR_DSP_PLD 1014 +#define HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT 1015 +#define HPI6205_ERROR_MSG_RESP_TIMEOUT 1016 +#define HPI6205_ERROR_6205_EEPROM 1017 +#define HPI6205_ERROR_DSP_EMIF 1018 + +#define hpi6205_error(dsp_index, err) (err) +/*****************************************************************************/ +/* for C6205 PCI i/f */ +/* Host Status Register (HSR) bitfields */ +#define C6205_HSR_INTSRC 0x01 +#define C6205_HSR_INTAVAL 0x02 +#define C6205_HSR_INTAM 0x04 +#define C6205_HSR_CFGERR 0x08 +#define C6205_HSR_EEREAD 0x10 +/* Host-to-DSP Control Register (HDCR) bitfields */ +#define C6205_HDCR_WARMRESET 0x01 +#define C6205_HDCR_DSPINT 0x02 +#define C6205_HDCR_PCIBOOT 0x04 +/* DSP Page Register (DSPP) bitfields, */ +/* defines 4 Mbyte page that BAR0 points to */ +#define C6205_DSPP_MAP1 0x400 + +/* BAR0 maps to prefetchable 4 Mbyte memory block set by DSPP. + * BAR1 maps to non-prefetchable 8 Mbyte memory block + * of DSP memory mapped registers (starting at 0x01800000). + * 0x01800000 is hardcoded in the PCI i/f, so that only the offset from this + * needs to be added to the BAR1 base address set in the PCI config reg + */ +#define C6205_BAR1_PCI_IO_OFFSET (0x027FFF0L) +#define C6205_BAR1_HSR (C6205_BAR1_PCI_IO_OFFSET) +#define C6205_BAR1_HDCR (C6205_BAR1_PCI_IO_OFFSET+4) +#define C6205_BAR1_DSPP (C6205_BAR1_PCI_IO_OFFSET+8) + +/* used to control LED (revA) and reset C6713 (revB) */ +#define C6205_BAR0_TIMER1_CTL (0x01980000L) + +/* For first 6713 in CE1 space, using DA17,16,2 */ +#define HPICL_ADDR 0x01400000L +#define HPICH_ADDR 0x01400004L +#define HPIAL_ADDR 0x01410000L +#define HPIAH_ADDR 0x01410004L +#define HPIDIL_ADDR 0x01420000L +#define HPIDIH_ADDR 0x01420004L +#define HPIDL_ADDR 0x01430000L +#define HPIDH_ADDR 0x01430004L + +#define C6713_EMIF_GCTL 0x01800000 +#define C6713_EMIF_CE1 0x01800004 +#define C6713_EMIF_CE0 0x01800008 +#define C6713_EMIF_CE2 0x01800010 +#define C6713_EMIF_CE3 0x01800014 +#define C6713_EMIF_SDRAMCTL 0x01800018 +#define C6713_EMIF_SDRAMTIMING 0x0180001C +#define C6713_EMIF_SDRAMEXT 0x01800020 + +struct hpi_hw_obj { + /* PCI registers */ + __iomem u32 *prHSR; + __iomem u32 *prHDCR; + __iomem u32 *prDSPP; + + u32 dsp_page; + + struct consistent_dma_area h_locked_mem; + struct bus_master_interface *p_interface_buffer; + + u16 flag_outstream_just_reset[HPI_MAX_STREAMS]; + /* a non-NULL handle means there is an HPI allocated buffer */ + struct consistent_dma_area instream_host_buffers[HPI_MAX_STREAMS]; + struct consistent_dma_area outstream_host_buffers[HPI_MAX_STREAMS]; + /* non-zero size means a buffer exists, may be external */ + u32 instream_host_buffer_size[HPI_MAX_STREAMS]; + u32 outstream_host_buffer_size[HPI_MAX_STREAMS]; + + struct consistent_dma_area h_control_cache; + struct consistent_dma_area h_async_event_buffer; +/* struct hpi_control_cache_single *pControlCache; */ + struct hpi_async_event *p_async_event_buffer; + struct hpi_control_cache *p_cache; +}; + +/*****************************************************************************/ +/* local prototypes */ + +#define check_before_bbm_copy(status, p_bbm_data, l_first_write, l_second_write) + +static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us); + +static void send_dsp_command(struct hpi_hw_obj *phw, int cmd); + +static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +static u16 message_response_sequence(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr); + +#define HPI6205_TIMEOUT 1000000 + +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr); +static void subsys_delete_adapter(struct hpi_message *phm, + struct hpi_response *phr); + +static u16 create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code); + +static void delete_adapter_obj(struct hpi_adapter_obj *pao); + +static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); +static void outstream_write(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_open(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_reset(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_read(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static void instream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr); + +static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address); + +static u16 boot_loader_write_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address, u32 data); + +static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index, + u32 address, u32 length); + +static u16 boot_loader_test_internal_memory(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_external_memory(struct hpi_adapter_obj *pao, + int dsp_index); + +static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index); + +/*****************************************************************************/ + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_SUBSYS_OPEN: + case HPI_SUBSYS_CLOSE: + case HPI_SUBSYS_GET_INFO: + case HPI_SUBSYS_DRIVER_UNLOAD: + case HPI_SUBSYS_DRIVER_LOAD: + case HPI_SUBSYS_FIND_ADAPTERS: + /* messages that should not get here */ + phr->error = HPI_ERROR_UNIMPLEMENTED; + break; + case HPI_SUBSYS_CREATE_ADAPTER: + subsys_create_adapter(phm, phr); + break; + case HPI_SUBSYS_DELETE_ADAPTER: + subsys_delete_adapter(phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void control_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + struct hpi_hw_obj *phw = pao->priv; + + switch (phm->function) { + case HPI_CONTROL_GET_STATE: + if (pao->has_control_cache) { + rmb(); /* make sure we see updates DM_aed from DSP */ + if (hpi_check_control_cache(phw->p_cache, phm, phr)) + break; + } + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_GET_INFO: + hw_message(pao, phm, phr); + break; + case HPI_CONTROL_SET_STATE: + hw_message(pao, phm, phr); + if (pao->has_control_cache) + hpi_sync_control_cache(phw->p_cache, phm, phr); + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +static void adapter_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + default: + hw_message(pao, phm, phr); + break; + } +} + +static void outstream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + if (phm->obj_index >= HPI_MAX_STREAMS) { + phr->error = HPI_ERROR_INVALID_STREAM; + HPI_DEBUG_LOG(WARNING, + "message referencing invalid stream %d " + "on adapter index %d\n", phm->obj_index, + phm->adapter_index); + return; + } + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + outstream_write(pao, phm, phr); + break; + case HPI_OSTREAM_GET_INFO: + outstream_get_info(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_ALLOC: + outstream_host_buffer_allocate(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_GET_INFO: + outstream_host_buffer_get_info(pao, phm, phr); + break; + case HPI_OSTREAM_HOSTBUFFER_FREE: + outstream_host_buffer_free(pao, phm, phr); + break; + case HPI_OSTREAM_START: + outstream_start(pao, phm, phr); + break; + case HPI_OSTREAM_OPEN: + outstream_open(pao, phm, phr); + break; + case HPI_OSTREAM_RESET: + outstream_reset(pao, phm, phr); + break; + default: + hw_message(pao, phm, phr); + break; + } +} + +static void instream_message(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + + if (phm->obj_index >= HPI_MAX_STREAMS) { + phr->error = HPI_ERROR_INVALID_STREAM; + HPI_DEBUG_LOG(WARNING, + "message referencing invalid stream %d " + "on adapter index %d\n", phm->obj_index, + phm->adapter_index); + return; + } + + switch (phm->function) { + case HPI_ISTREAM_READ: + instream_read(pao, phm, phr); + break; + case HPI_ISTREAM_GET_INFO: + instream_get_info(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_ALLOC: + instream_host_buffer_allocate(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_GET_INFO: + instream_host_buffer_get_info(pao, phm, phr); + break; + case HPI_ISTREAM_HOSTBUFFER_FREE: + instream_host_buffer_free(pao, phm, phr); + break; + case HPI_ISTREAM_START: + instream_start(pao, phm, phr); + break; + default: + hw_message(pao, phm, phr); + break; + } +} + +/*****************************************************************************/ +/** Entry point to this HPI backend + * All calls to the HPI start here + */ +void HPI_6205(struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao = NULL; + + /* subsytem messages are processed by every HPI. + * All other messages are ignored unless the adapter index matches + * an adapter in the HPI + */ + HPI_DEBUG_LOG(DEBUG, "HPI obj=%d, func=%d\n", phm->object, + phm->function); + + /* if Dsp has crashed then do not communicate with it any more */ + if (phm->object != HPI_OBJ_SUBSYSTEM) { + pao = hpi_find_adapter(phm->adapter_index); + if (!pao) { + HPI_DEBUG_LOG(DEBUG, + " %d,%d refused, for another HPI?\n", + phm->object, phm->function); + return; + } + + if ((pao->dsp_crashed >= 10) + && (phm->function != HPI_ADAPTER_DEBUG_READ)) { + /* allow last resort debug read even after crash */ + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_DSP_HARDWARE); + HPI_DEBUG_LOG(WARNING, " %d,%d dsp crashed.\n", + phm->object, phm->function); + return; + } + } + + /* Init default response */ + if (phm->function != HPI_SUBSYS_CREATE_ADAPTER) + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_PROCESSING_MESSAGE); + + HPI_DEBUG_LOG(VERBOSE, "start of switch\n"); + switch (phm->type) { + case HPI_TYPE_MESSAGE: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr); + break; + + case HPI_OBJ_ADAPTER: + phr->size = + sizeof(struct hpi_response_header) + + sizeof(struct hpi_adapter_res); + adapter_message(pao, phm, phr); + break; + + case HPI_OBJ_CONTROLEX: + case HPI_OBJ_CONTROL: + control_message(pao, phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(pao, phm, phr); + break; + + case HPI_OBJ_ISTREAM: + instream_message(pao, phm, phr); + break; + + default: + hw_message(pao, phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} + +/*****************************************************************************/ +/* SUBSYSTEM */ + +/** Create an adapter object and initialise it based on resource information + * passed in in the message + * *** NOTE - you cannot use this function AND the FindAdapters function at the + * same time, the application must use only one of them to get the adapters *** + */ +static void subsys_create_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + /* create temp adapter obj, because we don't know what index yet */ + struct hpi_adapter_obj ao; + u32 os_error_code; + u16 err; + + HPI_DEBUG_LOG(DEBUG, " subsys_create_adapter\n"); + + memset(&ao, 0, sizeof(ao)); + + /* this HPI only creates adapters for TI/PCI devices */ + if (phm->u.s.resource.bus_type != HPI_BUS_PCI) + return; + if (phm->u.s.resource.r.pci->vendor_id != HPI_PCI_VENDOR_ID_TI) + return; + if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_DSP6205) + return; + + ao.priv = kmalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); + if (!ao.priv) { + HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n"); + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + memset(ao.priv, 0, sizeof(struct hpi_hw_obj)); + + ao.pci = *phm->u.s.resource.r.pci; + err = create_adapter_obj(&ao, &os_error_code); + if (!err) + err = hpi_add_adapter(&ao); + if (err) { + phr->u.s.data = os_error_code; + delete_adapter_obj(&ao); + phr->error = err; + return; + } + + phr->u.s.aw_adapter_list[ao.index] = ao.adapter_type; + phr->u.s.adapter_index = ao.index; + phr->u.s.num_adapters++; + phr->error = 0; +} + +/** delete an adapter - required by WDM driver */ +static void subsys_delete_adapter(struct hpi_message *phm, + struct hpi_response *phr) +{ + struct hpi_adapter_obj *pao; + struct hpi_hw_obj *phw; + + pao = hpi_find_adapter(phm->adapter_index); + if (!pao) { + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + return; + } + phw = (struct hpi_hw_obj *)pao->priv; + /* reset adapter h/w */ + /* Reset C6713 #1 */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0); + /* reset C6205 */ + iowrite32(C6205_HDCR_WARMRESET, phw->prHDCR); + + delete_adapter_obj(pao); + phr->error = 0; +} + +/** Create adapter object + allocate buffers, bootload DSPs, initialise control cache +*/ +static u16 create_adapter_obj(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface; + u32 phys_addr; +#ifndef HPI6205_NO_HSR_POLL + u32 time_out = HPI6205_TIMEOUT; + u32 temp1; +#endif + int i; + u16 err; + + /* init error reporting */ + pao->dsp_crashed = 0; + + for (i = 0; i < HPI_MAX_STREAMS; i++) + phw->flag_outstream_just_reset[i] = 1; + + /* The C6205 memory area 1 is 8Mbyte window into DSP registers */ + phw->prHSR = + pao->pci.ap_mem_base[1] + + C6205_BAR1_HSR / sizeof(*pao->pci.ap_mem_base[1]); + phw->prHDCR = + pao->pci.ap_mem_base[1] + + C6205_BAR1_HDCR / sizeof(*pao->pci.ap_mem_base[1]); + phw->prDSPP = + pao->pci.ap_mem_base[1] + + C6205_BAR1_DSPP / sizeof(*pao->pci.ap_mem_base[1]); + + pao->has_control_cache = 0; + + if (hpios_locked_mem_alloc(&phw->h_locked_mem, + sizeof(struct bus_master_interface), + pao->pci.p_os_data)) + phw->p_interface_buffer = NULL; + else if (hpios_locked_mem_get_virt_addr(&phw->h_locked_mem, + (void *)&phw->p_interface_buffer)) + phw->p_interface_buffer = NULL; + + HPI_DEBUG_LOG(DEBUG, "interface buffer address %p\n", + phw->p_interface_buffer); + + if (phw->p_interface_buffer) { + memset((void *)phw->p_interface_buffer, 0, + sizeof(struct bus_master_interface)); + phw->p_interface_buffer->dsp_ack = H620_HIF_UNKNOWN; + } + + err = adapter_boot_load_dsp(pao, pos_error_code); + if (err) + /* no need to clean up as SubSysCreateAdapter */ + /* calls DeleteAdapter on error. */ + return err; + + HPI_DEBUG_LOG(INFO, "load DSP code OK\n"); + + /* allow boot load even if mem alloc wont work */ + if (!phw->p_interface_buffer) + return hpi6205_error(0, HPI_ERROR_MEMORY_ALLOC); + + interface = phw->p_interface_buffer; + +#ifndef HPI6205_NO_HSR_POLL + /* wait for first interrupt indicating the DSP init is done */ + time_out = HPI6205_TIMEOUT * 10; + temp1 = 0; + while (((temp1 & C6205_HSR_INTSRC) == 0) && --time_out) + temp1 = ioread32(phw->prHSR); + + if (temp1 & C6205_HSR_INTSRC) + HPI_DEBUG_LOG(INFO, + "interrupt confirming DSP code running OK\n"); + else { + HPI_DEBUG_LOG(ERROR, + "timed out waiting for interrupt " + "confirming DSP code running\n"); + return hpi6205_error(0, HPI6205_ERROR_6205_NO_IRQ); + } + + /* reset the interrupt */ + iowrite32(C6205_HSR_INTSRC, phw->prHSR); +#endif + + /* make sure the DSP has started ok */ + if (!wait_dsp_ack(phw, H620_HIF_RESET, HPI6205_TIMEOUT * 10)) { + HPI_DEBUG_LOG(ERROR, "timed out waiting reset state \n"); + return hpi6205_error(0, HPI6205_ERROR_6205_INIT_FAILED); + } + /* Note that *pao, *phw are zeroed after allocation, + * so pointers and flags are NULL by default. + * Allocate bus mastering control cache buffer and tell the DSP about it + */ + if (interface->control_cache.number_of_controls) { + void *p_control_cache_virtual; + + err = hpios_locked_mem_alloc(&phw->h_control_cache, + interface->control_cache.size_in_bytes, + pao->pci.p_os_data); + if (!err) + err = hpios_locked_mem_get_virt_addr(&phw-> + h_control_cache, &p_control_cache_virtual); + if (!err) { + memset(p_control_cache_virtual, 0, + interface->control_cache.size_in_bytes); + + phw->p_cache = + hpi_alloc_control_cache(interface-> + control_cache.number_of_controls, + interface->control_cache.size_in_bytes, + (struct hpi_control_cache_info *) + p_control_cache_virtual); + } + if (!err) { + err = hpios_locked_mem_get_phys_addr(&phw-> + h_control_cache, &phys_addr); + interface->control_cache.physical_address32 = + phys_addr; + } + + if (!err) + pao->has_control_cache = 1; + else { + if (hpios_locked_mem_valid(&phw->h_control_cache)) + hpios_locked_mem_free(&phw->h_control_cache); + pao->has_control_cache = 0; + } + } + /* allocate bus mastering async buffer and tell the DSP about it */ + if (interface->async_buffer.b.size) { + err = hpios_locked_mem_alloc(&phw->h_async_event_buffer, + interface->async_buffer.b.size * + sizeof(struct hpi_async_event), pao->pci.p_os_data); + if (!err) + err = hpios_locked_mem_get_virt_addr + (&phw->h_async_event_buffer, (void *) + &phw->p_async_event_buffer); + if (!err) + memset((void *)phw->p_async_event_buffer, 0, + interface->async_buffer.b.size * + sizeof(struct hpi_async_event)); + if (!err) { + err = hpios_locked_mem_get_phys_addr + (&phw->h_async_event_buffer, &phys_addr); + interface->async_buffer.physical_address32 = + phys_addr; + } + if (err) { + if (hpios_locked_mem_valid(&phw-> + h_async_event_buffer)) { + hpios_locked_mem_free + (&phw->h_async_event_buffer); + phw->p_async_event_buffer = NULL; + } + } + } + send_dsp_command(phw, H620_HIF_IDLE); + + { + struct hpi_message hM; + struct hpi_response hR; + u32 max_streams; + + HPI_DEBUG_LOG(VERBOSE, "init ADAPTER_GET_INFO\n"); + memset(&hM, 0, sizeof(hM)); + hM.type = HPI_TYPE_MESSAGE; + hM.size = sizeof(hM); + hM.object = HPI_OBJ_ADAPTER; + hM.function = HPI_ADAPTER_GET_INFO; + hM.adapter_index = 0; + memset(&hR, 0, sizeof(hR)); + hR.size = sizeof(hR); + + err = message_response_sequence(pao, &hM, &hR); + if (err) { + HPI_DEBUG_LOG(ERROR, "message transport error %d\n", + err); + return err; + } + if (hR.error) + return hR.error; + + pao->adapter_type = hR.u.a.adapter_type; + pao->index = hR.u.a.adapter_index; + + max_streams = hR.u.a.num_outstreams + hR.u.a.num_instreams; + + hpios_locked_mem_prepare((max_streams * 6) / 10, max_streams, + 65536, pao->pci.p_os_data); + + HPI_DEBUG_LOG(VERBOSE, + "got adapter info type %x index %d serial %d\n", + hR.u.a.adapter_type, hR.u.a.adapter_index, + hR.u.a.serial_number); + } + + pao->open = 0; /* upon creation the adapter is closed */ + + HPI_DEBUG_LOG(INFO, "bootload DSP OK\n"); + return 0; +} + +/** Free memory areas allocated by adapter + * this routine is called from SubSysDeleteAdapter, + * and SubSysCreateAdapter if duplicate index +*/ +static void delete_adapter_obj(struct hpi_adapter_obj *pao) +{ + struct hpi_hw_obj *phw; + int i; + + phw = pao->priv; + + if (hpios_locked_mem_valid(&phw->h_async_event_buffer)) { + hpios_locked_mem_free(&phw->h_async_event_buffer); + phw->p_async_event_buffer = NULL; + } + + if (hpios_locked_mem_valid(&phw->h_control_cache)) { + hpios_locked_mem_free(&phw->h_control_cache); + hpi_free_control_cache(phw->p_cache); + } + + if (hpios_locked_mem_valid(&phw->h_locked_mem)) { + hpios_locked_mem_free(&phw->h_locked_mem); + phw->p_interface_buffer = NULL; + } + + for (i = 0; i < HPI_MAX_STREAMS; i++) + if (hpios_locked_mem_valid(&phw->instream_host_buffers[i])) { + hpios_locked_mem_free(&phw->instream_host_buffers[i]); + /*?phw->InStreamHostBuffers[i] = NULL; */ + phw->instream_host_buffer_size[i] = 0; + } + + for (i = 0; i < HPI_MAX_STREAMS; i++) + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[i])) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [i]); + phw->outstream_host_buffer_size[i] = 0; + } + + hpios_locked_mem_unprepare(pao->pci.p_os_data); + + hpi_delete_adapter(pao); + kfree(phw); +} + +/*****************************************************************************/ +/* OutStream Host buffer functions */ + +/** Allocate or attach buffer for busmastering +*/ +static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u16 err = 0; + u32 command = phm->u.d.u.buffer.command; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + + hpi_init_response(phr, phm->object, phm->function, 0); + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_ALLOC) { + /* ALLOC phase, allocate a buffer with power of 2 size, + get its bus address for PCI bus mastering + */ + phm->u.d.u.buffer.buffer_size = + roundup_pow_of_two(phm->u.d.u.buffer.buffer_size); + /* return old size and allocated size, + so caller can detect change */ + phr->u.d.u.stream_info.data_available = + phw->outstream_host_buffer_size[phm->obj_index]; + phr->u.d.u.stream_info.buffer_size = + phm->u.d.u.buffer.buffer_size; + + if (phw->outstream_host_buffer_size[phm->obj_index] == + phm->u.d.u.buffer.buffer_size) { + /* Same size, no action required */ + return; + } + + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + + err = hpios_locked_mem_alloc(&phw->outstream_host_buffers + [phm->obj_index], phm->u.d.u.buffer.buffer_size, + pao->pci.p_os_data); + + if (err) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + phw->outstream_host_buffer_size[phm->obj_index] = 0; + return; + } + + err = hpios_locked_mem_get_phys_addr + (&phw->outstream_host_buffers[phm->obj_index], + &phm->u.d.u.buffer.pci_address); + /* get the phys addr into msg for single call alloc caller + * needs to do this for split alloc (or use the same message) + * return the phy address for split alloc in the respose too + */ + phr->u.d.u.stream_info.auxiliary_data_available = + phm->u.d.u.buffer.pci_address; + + if (err) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + phw->outstream_host_buffer_size[phm->obj_index] = 0; + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) { + /* GRANT phase. Set up the BBM status, tell the DSP about + the buffer so it can start using BBM. + */ + struct hpi_hostbuffer_status *status; + + if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer. + buffer_size - 1)) { + HPI_DEBUG_LOG(ERROR, + "buffer size must be 2^N not %d\n", + phm->u.d.u.buffer.buffer_size); + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + phw->outstream_host_buffer_size[phm->obj_index] = + phm->u.d.u.buffer.buffer_size; + status = &interface->outstream_host_buffer_status[phm-> + obj_index]; + status->samples_processed = 0; + status->stream_state = HPI_STATE_STOPPED; + status->dSP_index = 0; + status->host_index = status->dSP_index; + status->size_in_bytes = phm->u.d.u.buffer.buffer_size; + + hw_message(pao, phm, phr); + + if (phr->error + && hpios_locked_mem_valid(&phw-> + outstream_host_buffers[phm->obj_index])) { + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + phw->outstream_host_buffer_size[phm->obj_index] = 0; + } + } +} + +static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u8 *p_bbm_data; + + if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + outstream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + status = &interface->outstream_host_buffer_status[phm-> + obj_index]; + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO, 0); + phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data; + phr->u.d.u.hostbuffer_info.p_status = status; + } else { + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO, + HPI_ERROR_INVALID_OPERATION); + } +} + +static void outstream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 command = phm->u.d.u.buffer.command; + + if (phw->outstream_host_buffer_size[phm->obj_index]) { + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) { + phw->outstream_host_buffer_size[phm->obj_index] = 0; + hw_message(pao, phm, phr); + /* Tell adapter to stop using the host buffer. */ + } + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_FREE) + hpios_locked_mem_free(&phw->outstream_host_buffers + [phm->obj_index]); + } + /* Should HPI_ERROR_INVALID_OPERATION be returned + if no host buffer is allocated? */ + else + hpi_init_response(phr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE, 0); + +} + +static long outstream_get_space_available(struct hpi_hostbuffer_status + *status) +{ + return status->size_in_bytes - ((long)(status->host_index) - + (long)(status->dSP_index)); +} + +static void outstream_write(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + long space_available; + + if (!phw->outstream_host_buffer_size[phm->obj_index]) { + /* there is no BBM buffer, write via message */ + hw_message(pao, phm, phr); + return; + } + + hpi_init_response(phr, phm->object, phm->function, 0); + status = &interface->outstream_host_buffer_status[phm->obj_index]; + + if (phw->flag_outstream_just_reset[phm->obj_index]) { + /* Format can only change after reset. Must tell DSP. */ + u16 function = phm->function; + phw->flag_outstream_just_reset[phm->obj_index] = 0; + phm->function = HPI_OSTREAM_SET_FORMAT; + hw_message(pao, phm, phr); /* send the format to the DSP */ + phm->function = function; + if (phr->error) + return; + } +#if 1 + if (phw->flag_outstream_just_reset[phm->obj_index]) { + /* First OutStremWrite() call following reset will write data to the + adapter's buffers, reducing delay before stream can start + */ + int partial_write = 0; + unsigned int original_size = 0; + + /* Send the first buffer to the DSP the old way. */ + /* Limit size of first transfer - */ + /* expect that this will not usually be triggered. */ + if (phm->u.d.u.data.data_size > HPI6205_SIZEOF_DATA) { + partial_write = 1; + original_size = phm->u.d.u.data.data_size; + phm->u.d.u.data.data_size = HPI6205_SIZEOF_DATA; + } + /* write it */ + phm->function = HPI_OSTREAM_WRITE; + hw_message(pao, phm, phr); + /* update status information that the DSP would typically + * update (and will update next time the DSP + * buffer update task reads data from the host BBM buffer) + */ + status->auxiliary_data_available = phm->u.d.u.data.data_size; + status->host_index += phm->u.d.u.data.data_size; + status->dSP_index += phm->u.d.u.data.data_size; + + /* if we did a full write, we can return from here. */ + if (!partial_write) + return; + + /* tweak buffer parameters and let the rest of the */ + /* buffer land in internal BBM buffer */ + phm->u.d.u.data.data_size = + original_size - HPI6205_SIZEOF_DATA; + phm->u.d.u.data.pb_data += HPI6205_SIZEOF_DATA; + } +#endif + + space_available = outstream_get_space_available(status); + if (space_available < (long)phm->u.d.u.data.data_size) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + /* HostBuffers is used to indicate host buffer is internally allocated. + otherwise, assumed external, data written externally */ + if (phm->u.d.u.data.pb_data + && hpios_locked_mem_valid(&phw->outstream_host_buffers[phm-> + obj_index])) { + u8 *p_bbm_data; + long l_first_write; + u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data; + + if (hpios_locked_mem_get_virt_addr(&phw-> + outstream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + + /* either all data, + or enough to fit from current to end of BBM buffer */ + l_first_write = + min(phm->u.d.u.data.data_size, + status->size_in_bytes - + (status->host_index & (status->size_in_bytes - 1))); + + memcpy(p_bbm_data + + (status->host_index & (status->size_in_bytes - 1)), + p_app_data, l_first_write); + /* remaining data if any */ + memcpy(p_bbm_data, p_app_data + l_first_write, + phm->u.d.u.data.data_size - l_first_write); + } + status->host_index += phm->u.d.u.data.data_size; +} + +static void outstream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + + if (!phw->outstream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + + hpi_init_response(phr, phm->object, phm->function, 0); + + status = &interface->outstream_host_buffer_status[phm->obj_index]; + + phr->u.d.u.stream_info.state = (u16)status->stream_state; + phr->u.d.u.stream_info.samples_transferred = + status->samples_processed; + phr->u.d.u.stream_info.buffer_size = status->size_in_bytes; + phr->u.d.u.stream_info.data_available = + status->size_in_bytes - outstream_get_space_available(status); + phr->u.d.u.stream_info.auxiliary_data_available = + status->auxiliary_data_available; +} + +static void outstream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + hw_message(pao, phm, phr); +} + +static void outstream_reset(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + phw->flag_outstream_just_reset[phm->obj_index] = 1; + hw_message(pao, phm, phr); +} + +static void outstream_open(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + outstream_reset(pao, phm, phr); +} + +/*****************************************************************************/ +/* InStream Host buffer functions */ + +static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u16 err = 0; + u32 command = phm->u.d.u.buffer.command; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + + hpi_init_response(phr, phm->object, phm->function, 0); + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_ALLOC) { + + phm->u.d.u.buffer.buffer_size = + roundup_pow_of_two(phm->u.d.u.buffer.buffer_size); + phr->u.d.u.stream_info.data_available = + phw->instream_host_buffer_size[phm->obj_index]; + phr->u.d.u.stream_info.buffer_size = + phm->u.d.u.buffer.buffer_size; + + if (phw->instream_host_buffer_size[phm->obj_index] == + phm->u.d.u.buffer.buffer_size) { + /* Same size, no action required */ + return; + } + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + + err = hpios_locked_mem_alloc(&phw->instream_host_buffers[phm-> + obj_index], phm->u.d.u.buffer.buffer_size, + pao->pci.p_os_data); + + if (err) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + phw->instream_host_buffer_size[phm->obj_index] = 0; + return; + } + + err = hpios_locked_mem_get_phys_addr + (&phw->instream_host_buffers[phm->obj_index], + &phm->u.d.u.buffer.pci_address); + /* get the phys addr into msg for single call alloc. Caller + needs to do this for split alloc so return the phy address */ + phr->u.d.u.stream_info.auxiliary_data_available = + phm->u.d.u.buffer.pci_address; + if (err) { + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + phw->instream_host_buffer_size[phm->obj_index] = 0; + phr->error = HPI_ERROR_MEMORY_ALLOC; + return; + } + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) { + struct hpi_hostbuffer_status *status; + + if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer. + buffer_size - 1)) { + HPI_DEBUG_LOG(ERROR, + "buffer size must be 2^N not %d\n", + phm->u.d.u.buffer.buffer_size); + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + phw->instream_host_buffer_size[phm->obj_index] = + phm->u.d.u.buffer.buffer_size; + status = &interface->instream_host_buffer_status[phm-> + obj_index]; + status->samples_processed = 0; + status->stream_state = HPI_STATE_STOPPED; + status->dSP_index = 0; + status->host_index = status->dSP_index; + status->size_in_bytes = phm->u.d.u.buffer.buffer_size; + + hw_message(pao, phm, phr); + if (phr->error + && hpios_locked_mem_valid(&phw-> + instream_host_buffers[phm->obj_index])) { + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + phw->instream_host_buffer_size[phm->obj_index] = 0; + } + } +} + +static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + u8 *p_bbm_data; + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + instream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + status = &interface->instream_host_buffer_status[phm-> + obj_index]; + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO, 0); + phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data; + phr->u.d.u.hostbuffer_info.p_status = status; + } else { + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO, + HPI_ERROR_INVALID_OPERATION); + } +} + +static void instream_host_buffer_free(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 command = phm->u.d.u.buffer.command; + + if (phw->instream_host_buffer_size[phm->obj_index]) { + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) { + phw->instream_host_buffer_size[phm->obj_index] = 0; + hw_message(pao, phm, phr); + } + + if (command == HPI_BUFFER_CMD_EXTERNAL + || command == HPI_BUFFER_CMD_INTERNAL_FREE) + hpios_locked_mem_free(&phw->instream_host_buffers + [phm->obj_index]); + + } else { + /* Should HPI_ERROR_INVALID_OPERATION be returned + if no host buffer is allocated? */ + hpi_init_response(phr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE, 0); + + } + +} + +static void instream_start(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + hw_message(pao, phm, phr); +} + +static long instream_get_bytes_available(struct hpi_hostbuffer_status *status) +{ + return (long)(status->dSP_index) - (long)(status->host_index); +} + +static void instream_read(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + long data_available; + u8 *p_bbm_data; + long l_first_read; + u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data; + + if (!phw->instream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + hpi_init_response(phr, phm->object, phm->function, 0); + + status = &interface->instream_host_buffer_status[phm->obj_index]; + data_available = instream_get_bytes_available(status); + if (data_available < (long)phm->u.d.u.data.data_size) { + phr->error = HPI_ERROR_INVALID_DATASIZE; + return; + } + + if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm-> + obj_index])) { + if (hpios_locked_mem_get_virt_addr(&phw-> + instream_host_buffers[phm->obj_index], + (void *)&p_bbm_data)) { + phr->error = HPI_ERROR_INVALID_OPERATION; + return; + } + + /* either all data, + or enough to fit from current to end of BBM buffer */ + l_first_read = + min(phm->u.d.u.data.data_size, + status->size_in_bytes - + (status->host_index & (status->size_in_bytes - 1))); + + memcpy(p_app_data, + p_bbm_data + + (status->host_index & (status->size_in_bytes - 1)), + l_first_read); + /* remaining data if any */ + memcpy(p_app_data + l_first_read, p_bbm_data, + phm->u.d.u.data.data_size - l_first_read); + } + status->host_index += phm->u.d.u.data.data_size; +} + +static void instream_get_info(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + struct hpi_hostbuffer_status *status; + if (!phw->instream_host_buffer_size[phm->obj_index]) { + hw_message(pao, phm, phr); + return; + } + + status = &interface->instream_host_buffer_status[phm->obj_index]; + + hpi_init_response(phr, phm->object, phm->function, 0); + + phr->u.d.u.stream_info.state = (u16)status->stream_state; + phr->u.d.u.stream_info.samples_transferred = + status->samples_processed; + phr->u.d.u.stream_info.buffer_size = status->size_in_bytes; + phr->u.d.u.stream_info.data_available = + instream_get_bytes_available(status); + phr->u.d.u.stream_info.auxiliary_data_available = + status->auxiliary_data_available; +} + +/*****************************************************************************/ +/* LOW-LEVEL */ +#define HPI6205_MAX_FILES_TO_LOAD 2 + +static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao, + u32 *pos_error_code) +{ + struct hpi_hw_obj *phw = pao->priv; + struct dsp_code dsp_code; + u16 boot_code_id[HPI6205_MAX_FILES_TO_LOAD]; + u16 firmware_id = pao->pci.subsys_device_id; + u32 temp; + int dsp = 0, i = 0; + u16 err = 0; + + boot_code_id[0] = HPI_ADAPTER_ASI(0x6205); + + /* special cases where firmware_id != subsys ID */ + switch (firmware_id) { + case HPI_ADAPTER_FAMILY_ASI(0x5000): + boot_code_id[0] = firmware_id; + firmware_id = 0; + break; + case HPI_ADAPTER_FAMILY_ASI(0x5300): + case HPI_ADAPTER_FAMILY_ASI(0x5400): + case HPI_ADAPTER_FAMILY_ASI(0x6300): + firmware_id = HPI_ADAPTER_FAMILY_ASI(0x6400); + break; + case HPI_ADAPTER_FAMILY_ASI(0x5600): + case HPI_ADAPTER_FAMILY_ASI(0x6500): + firmware_id = HPI_ADAPTER_FAMILY_ASI(0x6600); + break; + } + boot_code_id[1] = firmware_id; + + /* reset DSP by writing a 1 to the WARMRESET bit */ + temp = C6205_HDCR_WARMRESET; + iowrite32(temp, phw->prHDCR); + hpios_delay_micro_seconds(1000); + + /* check that PCI i/f was configured by EEPROM */ + temp = ioread32(phw->prHSR); + if ((temp & (C6205_HSR_CFGERR | C6205_HSR_EEREAD)) != + C6205_HSR_EEREAD) + return hpi6205_error(0, HPI6205_ERROR_6205_EEPROM); + temp |= 0x04; + /* disable PINTA interrupt */ + iowrite32(temp, phw->prHSR); + + /* check control register reports PCI boot mode */ + temp = ioread32(phw->prHDCR); + if (!(temp & C6205_HDCR_PCIBOOT)) + return hpi6205_error(0, HPI6205_ERROR_6205_REG); + + /* try writing a couple of numbers to the DSP page register */ + /* and reading them back. */ + temp = 1; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE); + temp = 2; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE); + temp = 3; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE); + /* reset DSP page to the correct number */ + temp = 0; + iowrite32(temp, phw->prDSPP); + if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP)) + return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE); + phw->dsp_page = 0; + + /* release 6713 from reset before 6205 is bootloaded. + This ensures that the EMIF is inactive, + and the 6713 HPI gets the correct bootmode etc + */ + if (boot_code_id[1] != 0) { + /* DSP 1 is a C6713 */ + /* CLKX0 <- '1' release the C6205 bootmode pulldowns */ + boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002202); + hpios_delay_micro_seconds(100); + /* Reset the 6713 #1 - revB */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0); + + /* dummy read every 4 words for 6205 advisory 1.4.4 */ + boot_loader_read_mem32(pao, 0, 0); + + hpios_delay_micro_seconds(100); + /* Release C6713 from reset - revB */ + boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 4); + hpios_delay_micro_seconds(100); + } + + for (dsp = 0; dsp < HPI6205_MAX_FILES_TO_LOAD; dsp++) { + /* is there a DSP to load? */ + if (boot_code_id[dsp] == 0) + continue; + + err = boot_loader_config_emif(pao, dsp); + if (err) + return err; + + err = boot_loader_test_internal_memory(pao, dsp); + if (err) + return err; + + err = boot_loader_test_external_memory(pao, dsp); + if (err) + return err; + + err = boot_loader_test_pld(pao, dsp); + if (err) + return err; + + /* write the DSP code down into the DSPs memory */ + dsp_code.ps_dev = pao->pci.p_os_data; + err = hpi_dsp_code_open(boot_code_id[dsp], &dsp_code, + pos_error_code); + if (err) + return err; + + while (1) { + u32 length; + u32 address; + u32 type; + u32 *pcode; + + err = hpi_dsp_code_read_word(&dsp_code, &length); + if (err) + break; + if (length == 0xFFFFFFFF) + break; /* end of code */ + + err = hpi_dsp_code_read_word(&dsp_code, &address); + if (err) + break; + err = hpi_dsp_code_read_word(&dsp_code, &type); + if (err) + break; + err = hpi_dsp_code_read_block(length, &dsp_code, + &pcode); + if (err) + break; + for (i = 0; i < (int)length; i++) { + err = boot_loader_write_mem32(pao, dsp, + address, *pcode); + if (err) + break; + /* dummy read every 4 words */ + /* for 6205 advisory 1.4.4 */ + if (i % 4 == 0) + boot_loader_read_mem32(pao, dsp, + address); + pcode++; + address += 4; + } + + } + if (err) { + hpi_dsp_code_close(&dsp_code); + return err; + } + + /* verify code */ + hpi_dsp_code_rewind(&dsp_code); + while (1) { + u32 length = 0; + u32 address = 0; + u32 type = 0; + u32 *pcode = NULL; + u32 data = 0; + + hpi_dsp_code_read_word(&dsp_code, &length); + if (length == 0xFFFFFFFF) + break; /* end of code */ + + hpi_dsp_code_read_word(&dsp_code, &address); + hpi_dsp_code_read_word(&dsp_code, &type); + hpi_dsp_code_read_block(length, &dsp_code, &pcode); + + for (i = 0; i < (int)length; i++) { + data = boot_loader_read_mem32(pao, dsp, + address); + if (data != *pcode) { + err = 0; + break; + } + pcode++; + address += 4; + } + if (err) + break; + } + hpi_dsp_code_close(&dsp_code); + if (err) + return err; + } + + /* After bootloading all DSPs, start DSP0 running + * The DSP0 code will handle starting and synchronizing with its slaves + */ + if (phw->p_interface_buffer) { + /* we need to tell the card the physical PCI address */ + u32 physicalPC_iaddress; + struct bus_master_interface *interface = + phw->p_interface_buffer; + u32 host_mailbox_address_on_dsp; + u32 physicalPC_iaddress_verify = 0; + int time_out = 10; + /* set ack so we know when DSP is ready to go */ + /* (dwDspAck will be changed to HIF_RESET) */ + interface->dsp_ack = H620_HIF_UNKNOWN; + wmb(); /* ensure ack is written before dsp writes back */ + + err = hpios_locked_mem_get_phys_addr(&phw->h_locked_mem, + &physicalPC_iaddress); + + /* locate the host mailbox on the DSP. */ + host_mailbox_address_on_dsp = 0x80000000; + while ((physicalPC_iaddress != physicalPC_iaddress_verify) + && time_out--) { + err = boot_loader_write_mem32(pao, 0, + host_mailbox_address_on_dsp, + physicalPC_iaddress); + physicalPC_iaddress_verify = + boot_loader_read_mem32(pao, 0, + host_mailbox_address_on_dsp); + } + } + HPI_DEBUG_LOG(DEBUG, "starting DS_ps running\n"); + /* enable interrupts */ + temp = ioread32(phw->prHSR); + temp &= ~(u32)C6205_HSR_INTAM; + iowrite32(temp, phw->prHSR); + + /* start code running... */ + temp = ioread32(phw->prHDCR); + temp |= (u32)C6205_HDCR_DSPINT; + iowrite32(temp, phw->prHDCR); + + /* give the DSP 10ms to start up */ + hpios_delay_micro_seconds(10000); + return err; + +} + +/*****************************************************************************/ +/* Bootloader utility functions */ + +static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 data = 0; + __iomem u32 *p_data; + + if (dsp_index == 0) { + /* DSP 0 is always C6205 */ + if ((address >= 0x01800000) & (address < 0x02000000)) { + /* BAR1 register access */ + p_data = pao->pci.ap_mem_base[1] + + (address & 0x007fffff) / + sizeof(*pao->pci.ap_mem_base[1]); + /* HPI_DEBUG_LOG(WARNING, + "BAR1 access %08x\n", dwAddress); */ + } else { + u32 dw4M_page = address >> 22L; + if (dw4M_page != phw->dsp_page) { + phw->dsp_page = dw4M_page; + /* *INDENT OFF* */ + iowrite32(phw->dsp_page, phw->prDSPP); + /* *INDENT-ON* */ + } + address &= 0x3fffff; /* address within 4M page */ + /* BAR0 memory access */ + p_data = pao->pci.ap_mem_base[0] + + address / sizeof(u32); + } + data = ioread32(p_data); + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + u32 lsb; + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16); + lsb = boot_loader_read_mem32(pao, 0, HPIDL_ADDR); + data = boot_loader_read_mem32(pao, 0, HPIDH_ADDR); + data = (data << 16) | (lsb & 0xFFFF); + } + return data; +} + +static u16 boot_loader_write_mem32(struct hpi_adapter_obj *pao, int dsp_index, + u32 address, u32 data) +{ + struct hpi_hw_obj *phw = pao->priv; + u16 err = 0; + __iomem u32 *p_data; + /* u32 dwVerifyData=0; */ + + if (dsp_index == 0) { + /* DSP 0 is always C6205 */ + if ((address >= 0x01800000) & (address < 0x02000000)) { + /* BAR1 - DSP register access using */ + /* Non-prefetchable PCI access */ + p_data = pao->pci.ap_mem_base[1] + + (address & 0x007fffff) / + sizeof(*pao->pci.ap_mem_base[1]); + } else { + /* BAR0 access - all of DSP memory using */ + /* pre-fetchable PCI access */ + u32 dw4M_page = address >> 22L; + if (dw4M_page != phw->dsp_page) { + phw->dsp_page = dw4M_page; + /* *INDENT-OFF* */ + iowrite32(phw->dsp_page, phw->prDSPP); + /* *INDENT-ON* */ + } + address &= 0x3fffff; /* address within 4M page */ + p_data = pao->pci.ap_mem_base[0] + + address / sizeof(u32); + } + iowrite32(data, p_data); + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16); + + /* dummy read every 4 words for 6205 advisory 1.4.4 */ + boot_loader_read_mem32(pao, 0, 0); + + boot_loader_write_mem32(pao, 0, HPIDL_ADDR, data); + boot_loader_write_mem32(pao, 0, HPIDH_ADDR, data >> 16); + + /* dummy read every 4 words for 6205 advisory 1.4.4 */ + boot_loader_read_mem32(pao, 0, 0); + } else + err = hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX); + return err; +} + +static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao, int dsp_index) +{ + u16 err = 0; + + if (dsp_index == 0) { + u32 setting; + + /* DSP 0 is always C6205 */ + + /* Set the EMIF */ + /* memory map of C6205 */ + /* 00000000-0000FFFF 16Kx32 internal program */ + /* 00400000-00BFFFFF CE0 2Mx32 SDRAM running @ 100MHz */ + + /* EMIF config */ + /*------------ */ + /* Global EMIF control */ + boot_loader_write_mem32(pao, dsp_index, 0x01800000, 0x3779); +#define WS_OFS 28 +#define WST_OFS 22 +#define WH_OFS 20 +#define RS_OFS 16 +#define RST_OFS 8 +#define MTYPE_OFS 4 +#define RH_OFS 0 + + /* EMIF CE0 setup - 2Mx32 Sync DRAM on ASI5000 cards only */ + setting = 0x00000030; + boot_loader_write_mem32(pao, dsp_index, 0x01800008, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800008)) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_EMIF); + + /* EMIF CE1 setup - 32 bit async. This is 6713 #1 HPI, */ + /* which occupies D15..0. 6713 starts at 27MHz, so need */ + /* plenty of wait states. See dsn8701.rtf, and 6713 errata. */ + /* WST should be 71, but 63 is max possible */ + setting = + (1L << WS_OFS) | (63L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800004, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800004)) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_EMIF); + + /* EMIF CE2 setup - 32 bit async. This is 6713 #2 HPI, */ + /* which occupies D15..0. 6713 starts at 27MHz, so need */ + /* plenty of wait states */ + setting = + (1L << WS_OFS) | (28L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800010, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800010)) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_EMIF); + + /* EMIF CE3 setup - 32 bit async. */ + /* This is the PLD on the ASI5000 cards only */ + setting = + (1L << WS_OFS) | (10L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (10L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS); + boot_loader_write_mem32(pao, dsp_index, 0x01800014, setting); + if (setting != boot_loader_read_mem32(pao, dsp_index, + 0x01800014)) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_EMIF); + + /* set EMIF SDRAM control for 2Mx32 SDRAM (512x32x4 bank) */ + /* need to use this else DSP code crashes? */ + boot_loader_write_mem32(pao, dsp_index, 0x01800018, + 0x07117000); + + /* EMIF SDRAM Refresh Timing */ + /* EMIF SDRAM timing (orig = 0x410, emulator = 0x61a) */ + boot_loader_write_mem32(pao, dsp_index, 0x0180001C, + 0x00000410); + + } else if (dsp_index == 1) { + /* test access to the C6713s HPI registers */ + u32 write_data = 0, read_data = 0, i = 0; + + /* Set up HPIC for little endian, by setiing HPIC:HWOB=1 */ + write_data = 1; + boot_loader_write_mem32(pao, 0, HPICL_ADDR, write_data); + boot_loader_write_mem32(pao, 0, HPICH_ADDR, write_data); + /* C67 HPI is on lower 16bits of 32bit EMIF */ + read_data = + 0xFFF7 & boot_loader_read_mem32(pao, 0, HPICL_ADDR); + if (write_data != read_data) { + err = hpi6205_error(dsp_index, + HPI6205_ERROR_C6713_HPIC); + HPI_DEBUG_LOG(ERROR, "HPICL %x %x\n", write_data, + read_data); + + return err; + } + /* HPIA - walking ones test */ + write_data = 1; + for (i = 0; i < 32; i++) { + boot_loader_write_mem32(pao, 0, HPIAL_ADDR, + write_data); + boot_loader_write_mem32(pao, 0, HPIAH_ADDR, + (write_data >> 16)); + read_data = + 0xFFFF & boot_loader_read_mem32(pao, 0, + HPIAL_ADDR); + read_data = + read_data | ((0xFFFF & + boot_loader_read_mem32(pao, 0, + HPIAH_ADDR)) + << 16); + if (read_data != write_data) { + err = hpi6205_error(dsp_index, + HPI6205_ERROR_C6713_HPIA); + HPI_DEBUG_LOG(ERROR, "HPIA %x %x\n", + write_data, read_data); + return err; + } + write_data = write_data << 1; + } + + /* setup C67x PLL + * ** C6713 datasheet says we cannot program PLL from HPI, + * and indeed if we try to set the PLL multiply from the HPI, + * the PLL does not seem to lock, so we enable the PLL and + * use the default multiply of x 7, which for a 27MHz clock + * gives a DSP speed of 189MHz + */ + /* bypass PLL */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0000); + hpios_delay_micro_seconds(1000); + /* EMIF = 189/3=63MHz */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C120, 0x8002); + /* peri = 189/2 */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C11C, 0x8001); + /* cpu = 189/1 */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C118, 0x8000); + hpios_delay_micro_seconds(1000); + /* ** SGT test to take GPO3 high when we start the PLL */ + /* and low when the delay is completed */ + /* FSX0 <- '1' (GPO3) */ + boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A0A); + /* PLL not bypassed */ + boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0001); + hpios_delay_micro_seconds(1000); + /* FSX0 <- '0' (GPO3) */ + boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A02); + + /* 6205 EMIF CE1 resetup - 32 bit async. */ + /* Now 6713 #1 is running at 189MHz can reduce waitstates */ + boot_loader_write_mem32(pao, 0, 0x01800004, /* CE1 */ + (1L << WS_OFS) | (8L << WST_OFS) | (1L << WH_OFS) | + (1L << RS_OFS) | (12L << RST_OFS) | (1L << RH_OFS) | + (2L << MTYPE_OFS)); + + hpios_delay_micro_seconds(1000); + + /* check that we can read one of the PLL registers */ + /* PLL should not be bypassed! */ + if ((boot_loader_read_mem32(pao, dsp_index, 0x01B7C100) & 0xF) + != 0x0001) { + err = hpi6205_error(dsp_index, + HPI6205_ERROR_C6713_PLL); + return err; + } + /* setup C67x EMIF (note this is the only use of + BAR1 via BootLoader_WriteMem32) */ + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_GCTL, + 0x000034A8); + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_CE0, + 0x00000030); + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMEXT, + 0x001BDF29); + boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMCTL, + 0x47117000); + boot_loader_write_mem32(pao, dsp_index, + C6713_EMIF_SDRAMTIMING, 0x00000410); + + hpios_delay_micro_seconds(1000); + } else if (dsp_index == 2) { + /* DSP 2 is a C6713 */ + + } else + err = hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX); + return err; +} + +static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index, + u32 start_address, u32 length) +{ + u32 i = 0, j = 0; + u32 test_addr = 0; + u32 test_data = 0, data = 0; + + length = 1000; + + /* for 1st word, test each bit in the 32bit word, */ + /* dwLength specifies number of 32bit words to test */ + /*for(i=0; ipci.subsys_device_id == 0x5000) { + /* DSP 0 is always C6205 */ + dRAM_start_address = 0x00400000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } else + return 0; + } else if ((dsp_index == 1) || (dsp_index == 2)) { + /* DSP 1 is a C6713 */ + dRAM_start_address = 0x80000000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } else + return hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX); + + if (boot_loader_test_memory(pao, dsp_index, dRAM_start_address, + dRAM_size)) + return hpi6205_error(dsp_index, HPI6205_ERROR_DSP_EXTMEM); + return 0; +} + +static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index) +{ + u32 data = 0; + if (dsp_index == 0) { + /* only test for DSP0 PLD on ASI5000 card */ + if (pao->pci.subsys_device_id == 0x5000) { + /* PLD is located at CE3=0x03000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x03000008); + if ((data & 0xF) != 0x5) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_PLD); + data = boot_loader_read_mem32(pao, dsp_index, + 0x0300000C); + if ((data & 0xF) != 0xA) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_PLD); + } + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + if (pao->pci.subsys_device_id == 0x8700) { + /* PLD is located at CE1=0x90000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x90000010); + if ((data & 0xFF) != 0xAA) + return hpi6205_error(dsp_index, + HPI6205_ERROR_DSP_PLD); + /* 8713 - LED on */ + boot_loader_write_mem32(pao, dsp_index, 0x90000000, + 0x02); + } + } + return 0; +} + +/** Transfer data to or from DSP + nOperation = H620_H620_HIF_SEND_DATA or H620_HIF_GET_DATA +*/ +static short hpi6205_transfer_data(struct hpi_adapter_obj *pao, u8 *p_data, + u32 data_size, int operation) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 data_transferred = 0; + u16 err = 0; +#ifndef HPI6205_NO_HSR_POLL + u32 time_out; +#endif + u32 temp2; + struct bus_master_interface *interface = phw->p_interface_buffer; + + if (!p_data) + return HPI_ERROR_INVALID_DATA_TRANSFER; + + data_size &= ~3L; /* round data_size down to nearest 4 bytes */ + + /* make sure state is IDLE */ + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) + return HPI_ERROR_DSP_HARDWARE; + + while (data_transferred < data_size) { + u32 this_copy = data_size - data_transferred; + + if (this_copy > HPI6205_SIZEOF_DATA) + this_copy = HPI6205_SIZEOF_DATA; + + if (operation == H620_HIF_SEND_DATA) + memcpy((void *)&interface->u.b_data[0], + &p_data[data_transferred], this_copy); + + interface->transfer_size_in_bytes = this_copy; + +#ifdef HPI6205_NO_HSR_POLL + /* DSP must change this back to nOperation */ + interface->dsp_ack = H620_HIF_IDLE; +#endif + + send_dsp_command(phw, operation); + +#ifdef HPI6205_NO_HSR_POLL + temp2 = wait_dsp_ack(phw, operation, HPI6205_TIMEOUT); + HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n", + HPI6205_TIMEOUT - temp2, this_copy); + + if (!temp2) { + /* timed out */ + HPI_DEBUG_LOG(ERROR, + "timed out waiting for " "state %d got %d\n", + operation, interface->dsp_ack); + + break; + } +#else + /* spin waiting on the result */ + time_out = HPI6205_TIMEOUT; + temp2 = 0; + while ((temp2 == 0) && time_out--) { + /* give 16k bus mastering transfer time to happen */ + /*(16k / 132Mbytes/s = 122usec) */ + hpios_delay_micro_seconds(20); + temp2 = ioread32(phw->prHSR); + temp2 &= C6205_HSR_INTSRC; + } + HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n", + HPI6205_TIMEOUT - time_out, this_copy); + if (temp2 == C6205_HSR_INTSRC) { + HPI_DEBUG_LOG(VERBOSE, + "interrupt from HIF OK\n"); + /* + if(interface->dwDspAck != nOperation) { + HPI_DEBUG_LOG(DEBUG("interface->dwDspAck=%d, + expected %d \n", + interface->dwDspAck,nOperation); + } + */ + } +/* need to handle this differently... */ + else { + HPI_DEBUG_LOG(ERROR, + "interrupt from HIF BAD\n"); + err = HPI_ERROR_DSP_HARDWARE; + } + + /* reset the interrupt from the DSP */ + iowrite32(C6205_HSR_INTSRC, phw->prHSR); +#endif + if (operation == H620_HIF_GET_DATA) + memcpy(&p_data[data_transferred], + (void *)&interface->u.b_data[0], this_copy); + + data_transferred += this_copy; + } + if (interface->dsp_ack != operation) + HPI_DEBUG_LOG(DEBUG, "interface->dsp_ack=%d, expected %d\n", + interface->dsp_ack, operation); + /* err=HPI_ERROR_DSP_HARDWARE; */ + + send_dsp_command(phw, H620_HIF_IDLE); + + return err; +} + +/* wait for up to timeout_us microseconds for the DSP + to signal state by DMA into dwDspAck +*/ +static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + int t = timeout_us / 4; + + rmb(); /* ensure interface->dsp_ack is up to date */ + while ((interface->dsp_ack != state) && --t) { + hpios_delay_micro_seconds(4); + rmb(); /* DSP changes dsp_ack by DMA */ + } + + /*HPI_DEBUG_LOG(VERBOSE, "Spun %d for %d\n", timeout_us/4-t, state); */ + return t * 4; +} + +/* set the busmaster interface to cmd, then interrupt the DSP */ +static void send_dsp_command(struct hpi_hw_obj *phw, int cmd) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + + u32 r; + + interface->host_cmd = cmd; + wmb(); /* DSP gets state by DMA, make sure it is written to memory */ + /* before we interrupt the DSP */ + r = ioread32(phw->prHDCR); + r |= (u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); + r &= ~(u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); +} + +static unsigned int message_count; + +static u16 message_response_sequence(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ +#ifndef HPI6205_NO_HSR_POLL + u32 temp2; +#endif + u32 time_out, time_out2; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + u16 err = 0; + + message_count++; + /* Assume buffer of type struct bus_master_interface + is allocated "noncacheable" */ + + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, "timeout waiting for idle\n"); + return hpi6205_error(0, HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT); + } + interface->u.message_buffer = *phm; + /* signal we want a response */ + send_dsp_command(phw, H620_HIF_GET_RESP); + + time_out2 = wait_dsp_ack(phw, H620_HIF_GET_RESP, HPI6205_TIMEOUT); + + if (time_out2 == 0) { + HPI_DEBUG_LOG(ERROR, + "(%u) timed out waiting for " "GET_RESP state [%x]\n", + message_count, interface->dsp_ack); + } else { + HPI_DEBUG_LOG(VERBOSE, + "(%u) transition to GET_RESP after %u\n", + message_count, HPI6205_TIMEOUT - time_out2); + } + /* spin waiting on HIF interrupt flag (end of msg process) */ + time_out = HPI6205_TIMEOUT; + +#ifndef HPI6205_NO_HSR_POLL + temp2 = 0; + while ((temp2 == 0) && --time_out) { + temp2 = ioread32(phw->prHSR); + temp2 &= C6205_HSR_INTSRC; + hpios_delay_micro_seconds(1); + } + if (temp2 == C6205_HSR_INTSRC) { + rmb(); /* ensure we see latest value for dsp_ack */ + if ((interface->dsp_ack != H620_HIF_GET_RESP)) { + HPI_DEBUG_LOG(DEBUG, + "(%u)interface->dsp_ack(0x%x) != " + "H620_HIF_GET_RESP, t=%u\n", message_count, + interface->dsp_ack, + HPI6205_TIMEOUT - time_out); + } else { + HPI_DEBUG_LOG(VERBOSE, + "(%u)int with GET_RESP after %u\n", + message_count, HPI6205_TIMEOUT - time_out); + } + + } else { + /* can we do anything else in response to the error ? */ + HPI_DEBUG_LOG(ERROR, + "interrupt from HIF module BAD (function %x)\n", + phm->function); + } + + /* reset the interrupt from the DSP */ + iowrite32(C6205_HSR_INTSRC, phw->prHSR); +#endif + + /* read the result */ + if (time_out != 0) + *phr = interface->u.response_buffer; + + /* set interface back to idle */ + send_dsp_command(phw, H620_HIF_IDLE); + + if ((time_out == 0) || (time_out2 == 0)) { + HPI_DEBUG_LOG(DEBUG, "something timed out!\n"); + return hpi6205_error(0, HPI6205_ERROR_MSG_RESP_TIMEOUT); + } + /* special case for adapter close - */ + /* wait for the DSP to indicate it is idle */ + if (phm->function == HPI_ADAPTER_CLOSE) { + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, + "timeout waiting for idle " + "(on adapter_close)\n"); + return hpi6205_error(0, + HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT); + } + } + err = hpi_validate_response(phm, phr); + return err; +} + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + + u16 err = 0; + + hpios_dsplock_lock(pao); + + err = message_response_sequence(pao, phm, phr); + + /* maybe an error response */ + if (err) { + /* something failed in the HPI/DSP interface */ + phr->error = err; + pao->dsp_crashed++; + + /* just the header of the response is valid */ + phr->size = sizeof(struct hpi_response_header); + goto err; + } else + pao->dsp_crashed = 0; + + if (phr->error != 0) /* something failed in the DSP */ + goto err; + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_ANC_WRITE: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_SEND_DATA); + break; + + case HPI_ISTREAM_READ: + case HPI_OSTREAM_ANC_READ: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_GET_DATA); + break; + + case HPI_CONTROL_SET_STATE: + if (phm->object == HPI_OBJ_CONTROLEX + && phm->u.cx.attribute == HPI_COBRANET_SET_DATA) + err = hpi6205_transfer_data(pao, + phm->u.cx.u.cobranet_bigdata.pb_data, + phm->u.cx.u.cobranet_bigdata.byte_count, + H620_HIF_SEND_DATA); + break; + + case HPI_CONTROL_GET_STATE: + if (phm->object == HPI_OBJ_CONTROLEX + && phm->u.cx.attribute == HPI_COBRANET_GET_DATA) + err = hpi6205_transfer_data(pao, + phm->u.cx.u.cobranet_bigdata.pb_data, + phr->u.cx.u.cobranet_data.byte_count, + H620_HIF_GET_DATA); + break; + } + phr->error = err; + +err: + hpios_dsplock_unlock(pao); + + return; +} diff --git a/sound/pci/asihpi/hpi6205.h b/sound/pci/asihpi/hpi6205.h new file mode 100644 index 00000000000..1adae0857cd --- /dev/null +++ b/sound/pci/asihpi/hpi6205.h @@ -0,0 +1,93 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Host Interface module for an ASI6205 based +bus mastering PCI adapter. + +Copyright AudioScience, Inc., 2003 +******************************************************************************/ + +#ifndef _HPI6205_H_ +#define _HPI6205_H_ + +/* transitional conditional compile shared between host and DSP */ +/* #define HPI6205_NO_HSR_POLL */ + +#include "hpi_internal.h" + +/*********************************************************** + Defines used for basic messaging +************************************************************/ +#define H620_HIF_RESET 0 +#define H620_HIF_IDLE 1 +#define H620_HIF_GET_RESP 2 +#define H620_HIF_DATA_DONE 3 +#define H620_HIF_DATA_MASK 0x10 +#define H620_HIF_SEND_DATA 0x14 +#define H620_HIF_GET_DATA 0x15 +#define H620_HIF_UNKNOWN 0x0000ffff + +/*********************************************************** + Types used for mixer control caching +************************************************************/ + +#define H620_MAX_ISTREAMS 32 +#define H620_MAX_OSTREAMS 32 +#define HPI_NMIXER_CONTROLS 2048 + +/********************************************************************* +This is used for dynamic control cache allocation +**********************************************************************/ +struct controlcache_6205 { + u32 number_of_controls; + u32 physical_address32; + u32 size_in_bytes; +}; + +/********************************************************************* +This is used for dynamic allocation of async event array +**********************************************************************/ +struct async_event_buffer_6205 { + u32 physical_address32; + u32 spare; + struct hpi_fifo_buffer b; +}; + +/*********************************************************** +The Host located memory buffer that the 6205 will bus master +in and out of. +************************************************************/ +#define HPI6205_SIZEOF_DATA (16*1024) +struct bus_master_interface { + u32 host_cmd; + u32 dsp_ack; + u32 transfer_size_in_bytes; + union { + struct hpi_message message_buffer; + struct hpi_response response_buffer; + u8 b_data[HPI6205_SIZEOF_DATA]; + } u; + struct controlcache_6205 control_cache; + struct async_event_buffer_6205 async_buffer; + struct hpi_hostbuffer_status + instream_host_buffer_status[H620_MAX_ISTREAMS]; + struct hpi_hostbuffer_status + outstream_host_buffer_status[H620_MAX_OSTREAMS]; +}; + +#endif diff --git a/sound/pci/asihpi/hpi_internal.h b/sound/pci/asihpi/hpi_internal.h new file mode 100644 index 00000000000..f1cd6f1a0d4 --- /dev/null +++ b/sound/pci/asihpi/hpi_internal.h @@ -0,0 +1,1641 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +HPI internal definitions + +(C) Copyright AudioScience Inc. 1996-2009 +******************************************************************************/ + +#ifndef _HPI_INTERNAL_H_ +#define _HPI_INTERNAL_H_ + +#include "hpi.h" +/** maximum number of memory regions mapped to an adapter */ +#define HPI_MAX_ADAPTER_MEM_SPACES (2) + +/* Each OS needs its own hpios.h, or specific define as above */ +#include "hpios.h" + +/* physical memory allocation */ +void hpios_locked_mem_init(void + ); +void hpios_locked_mem_free_all(void + ); +#define hpios_locked_mem_prepare(a, b, c, d); +#define hpios_locked_mem_unprepare(a) + +/** Allocate and map an area of locked memory for bus master DMA operations. + +On success, *pLockedMemeHandle is a valid handle, and 0 is returned +On error *pLockedMemHandle marked invalid, non-zero returned. + +If this function succeeds, then HpiOs_LockedMem_GetVirtAddr() and +HpiOs_LockedMem_GetPyhsAddr() will always succed on the returned handle. +*/ +u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_locked_mem_handle, + /**< memory handle */ + u32 size, /**< size in bytes to allocate */ + struct pci_dev *p_os_reference + /**< OS specific data required for memory allocation */ + ); + +/** Free mapping and memory represented by LockedMemHandle + +Frees any resources, then invalidates the handle. +Returns 0 on success, 1 if handle is invalid. + +*/ +u16 hpios_locked_mem_free(struct consistent_dma_area *locked_mem_handle); + +/** Get the physical PCI address of memory represented by LockedMemHandle. + +If handle is invalid *pPhysicalAddr is set to zero and return 1 +*/ +u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area + *locked_mem_handle, u32 *p_physical_addr); + +/** Get the CPU address of of memory represented by LockedMemHandle. + +If handle is NULL *ppvVirtualAddr is set to NULL and return 1 +*/ +u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area + *locked_mem_handle, void **ppv_virtual_addr); + +/** Check that handle is valid +i.e it represents a valid memory area +*/ +u16 hpios_locked_mem_valid(struct consistent_dma_area *locked_mem_handle); + +/* timing/delay */ +void hpios_delay_micro_seconds(u32 num_micro_sec); + +struct hpi_message; +struct hpi_response; + +typedef void hpi_handler_func(struct hpi_message *, struct hpi_response *); + +/* If the assert fails, compiler complains + something like size of array `msg' is negative. + Unlike linux BUILD_BUG_ON, this works outside function scope. +*/ +#define compile_time_assert(cond, msg) \ + typedef char ASSERT_##msg[(cond) ? 1 : -1] + +/*/////////////////////////////////////////////////////////////////////////// */ +/* Private HPI Entity related definitions */ + +#define STR_SIZE_FIELD_MAX 65535U +#define STR_TYPE_FIELD_MAX 255U +#define STR_ROLE_FIELD_MAX 255U + +struct hpi_entity_str { + uint16_t size; + uint8_t type; + uint8_t role; +}; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4200) +#endif + +struct hpi_entity { + struct hpi_entity_str header; +#if ! defined(HPI_OS_DSP_C6000) || (defined(HPI_OS_DSP_C6000) && (__TI_COMPILER_VERSION__ > 6000008)) + /* DSP C6000 compiler v6.0.8 and lower + do not support flexible array member */ + uint8_t value[]; +#else + /* NOTE! Using sizeof(struct hpi_entity) will give erroneous results */ +#define HPI_INTERNAL_WARN_ABOUT_ENTITY_VALUE + uint8_t value[1]; +#endif +}; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/******************************************* bus types */ +enum HPI_BUSES { + HPI_BUS_ISAPNP = 1, + HPI_BUS_PCI = 2, + HPI_BUS_USB = 3, + HPI_BUS_NET = 4 +}; + +/******************************************* CONTROL ATTRIBUTES ****/ +/* (in order of control type ID */ + + /* This allows for 255 control types, 256 unique attributes each */ +#define HPI_CTL_ATTR(ctl, ai) (HPI_CONTROL_##ctl * 0x100 + ai) + +/* Get the sub-index of the attribute for a control type */ +#define HPI_CTL_ATTR_INDEX(i) (i&0xff) + +/* Generic control attributes. */ + +/** Enable a control. +0=disable, 1=enable +\note generic to all mixer plugins? +*/ +#define HPI_GENERIC_ENABLE HPI_CTL_ATTR(GENERIC, 1) + +/** Enable event generation for a control. +0=disable, 1=enable +\note generic to all controls that can generate events +*/ +#define HPI_GENERIC_EVENT_ENABLE HPI_CTL_ATTR(GENERIC, 2) + +/* Volume Control attributes */ +#define HPI_VOLUME_GAIN HPI_CTL_ATTR(VOLUME, 1) +#define HPI_VOLUME_AUTOFADE HPI_CTL_ATTR(VOLUME, 2) + +/** For HPI_ControlQuery() to get the number of channels of a volume control*/ +#define HPI_VOLUME_NUM_CHANNELS HPI_CTL_ATTR(VOLUME, 6) +#define HPI_VOLUME_RANGE HPI_CTL_ATTR(VOLUME, 10) + +/** Level Control attributes */ +#define HPI_LEVEL_GAIN HPI_CTL_ATTR(LEVEL, 1) +#define HPI_LEVEL_RANGE HPI_CTL_ATTR(LEVEL, 10) + +/* Meter Control attributes */ +/** return RMS signal level */ +#define HPI_METER_RMS HPI_CTL_ATTR(METER, 1) +/** return peak signal level */ +#define HPI_METER_PEAK HPI_CTL_ATTR(METER, 2) +/** ballistics for ALL rms meters on adapter */ +#define HPI_METER_RMS_BALLISTICS HPI_CTL_ATTR(METER, 3) +/** ballistics for ALL peak meters on adapter */ +#define HPI_METER_PEAK_BALLISTICS HPI_CTL_ATTR(METER, 4) + +/** For HPI_ControlQuery() to get the number of channels of a meter control*/ +#define HPI_METER_NUM_CHANNELS HPI_CTL_ATTR(METER, 5) + +/* Multiplexer control attributes */ +#define HPI_MULTIPLEXER_SOURCE HPI_CTL_ATTR(MULTIPLEXER, 1) +#define HPI_MULTIPLEXER_QUERYSOURCE HPI_CTL_ATTR(MULTIPLEXER, 2) + +/** AES/EBU transmitter control attributes */ +/** AESEBU or SPDIF */ +#define HPI_AESEBUTX_FORMAT HPI_CTL_ATTR(AESEBUTX, 1) +#define HPI_AESEBUTX_SAMPLERATE HPI_CTL_ATTR(AESEBUTX, 3) +#define HPI_AESEBUTX_CHANNELSTATUS HPI_CTL_ATTR(AESEBUTX, 4) +#define HPI_AESEBUTX_USERDATA HPI_CTL_ATTR(AESEBUTX, 5) + +/** AES/EBU receiver control attributes */ +#define HPI_AESEBURX_FORMAT HPI_CTL_ATTR(AESEBURX, 1) +#define HPI_AESEBURX_ERRORSTATUS HPI_CTL_ATTR(AESEBURX, 2) +#define HPI_AESEBURX_SAMPLERATE HPI_CTL_ATTR(AESEBURX, 3) +#define HPI_AESEBURX_CHANNELSTATUS HPI_CTL_ATTR(AESEBURX, 4) +#define HPI_AESEBURX_USERDATA HPI_CTL_ATTR(AESEBURX, 5) + +/** \defgroup tuner_defs Tuners +\{ +*/ +/** \defgroup tuner_attrs Tuner control attributes +\{ +*/ +#define HPI_TUNER_BAND HPI_CTL_ATTR(TUNER, 1) +#define HPI_TUNER_FREQ HPI_CTL_ATTR(TUNER, 2) +#define HPI_TUNER_LEVEL HPI_CTL_ATTR(TUNER, 3) +#define HPI_TUNER_AUDIOMUTE HPI_CTL_ATTR(TUNER, 4) +/* use TUNER_STATUS instead */ +#define HPI_TUNER_VIDEO_STATUS HPI_CTL_ATTR(TUNER, 5) +#define HPI_TUNER_GAIN HPI_CTL_ATTR(TUNER, 6) +#define HPI_TUNER_STATUS HPI_CTL_ATTR(TUNER, 7) +#define HPI_TUNER_MODE HPI_CTL_ATTR(TUNER, 8) +/** RDS data. */ +#define HPI_TUNER_RDS HPI_CTL_ATTR(TUNER, 9) +/** Audio pre-emphasis. */ +#define HPI_TUNER_DEEMPHASIS HPI_CTL_ATTR(TUNER, 10) +/** HD Radio tuner program control. */ +#define HPI_TUNER_PROGRAM HPI_CTL_ATTR(TUNER, 11) +/** HD Radio tuner digital signal quality. */ +#define HPI_TUNER_HDRADIO_SIGNAL_QUALITY HPI_CTL_ATTR(TUNER, 12) +/** HD Radio SDK firmware version. */ +#define HPI_TUNER_HDRADIO_SDK_VERSION HPI_CTL_ATTR(TUNER, 13) +/** HD Radio DSP firmware version. */ +#define HPI_TUNER_HDRADIO_DSP_VERSION HPI_CTL_ATTR(TUNER, 14) + +/** \} */ + +/** \defgroup pads_attrs Tuner PADs control attributes +\{ +*/ +/** The text string containing the station/channel combination. */ +#define HPI_PAD_CHANNEL_NAME HPI_CTL_ATTR(PAD, 1) +/** The text string containing the artist. */ +#define HPI_PAD_ARTIST HPI_CTL_ATTR(PAD, 2) +/** The text string containing the title. */ +#define HPI_PAD_TITLE HPI_CTL_ATTR(PAD, 3) +/** The text string containing the comment. */ +#define HPI_PAD_COMMENT HPI_CTL_ATTR(PAD, 4) +/** The integer containing the PTY code. */ +#define HPI_PAD_PROGRAM_TYPE HPI_CTL_ATTR(PAD, 5) +/** The integer containing the program identification. */ +#define HPI_PAD_PROGRAM_ID HPI_CTL_ATTR(PAD, 6) +/** The integer containing whether traffic information is supported. +Contains either 1 or 0. */ +#define HPI_PAD_TA_SUPPORT HPI_CTL_ATTR(PAD, 7) +/** The integer containing whether traffic announcement is in progress. +Contains either 1 or 0. */ +#define HPI_PAD_TA_ACTIVE HPI_CTL_ATTR(PAD, 8) +/** \} */ +/** \} */ + +/* VOX control attributes */ +#define HPI_VOX_THRESHOLD HPI_CTL_ATTR(VOX, 1) + +/*?? channel mode used hpi_multiplexer_source attribute == 1 */ +#define HPI_CHANNEL_MODE_MODE HPI_CTL_ATTR(CHANNEL_MODE, 1) + +/** \defgroup channel_modes Channel Modes +Used for HPI_ChannelModeSet/Get() +\{ +*/ +/** Left channel out = left channel in, Right channel out = right channel in. */ +#define HPI_CHANNEL_MODE_NORMAL 1 +/** Left channel out = right channel in, Right channel out = left channel in. */ +#define HPI_CHANNEL_MODE_SWAP 2 +/** Left channel out = left channel in, Right channel out = left channel in. */ +#define HPI_CHANNEL_MODE_LEFT_TO_STEREO 3 +/** Left channel out = right channel in, Right channel out = right channel in.*/ +#define HPI_CHANNEL_MODE_RIGHT_TO_STEREO 4 +/** Left channel out = (left channel in + right channel in)/2, + Right channel out = mute. */ +#define HPI_CHANNEL_MODE_STEREO_TO_LEFT 5 +/** Left channel out = mute, + Right channel out = (right channel in + left channel in)/2. */ +#define HPI_CHANNEL_MODE_STEREO_TO_RIGHT 6 +#define HPI_CHANNEL_MODE_LAST 6 +/** \} */ + +/* Bitstream control set attributes */ +#define HPI_BITSTREAM_DATA_POLARITY HPI_CTL_ATTR(BITSTREAM, 1) +#define HPI_BITSTREAM_CLOCK_EDGE HPI_CTL_ATTR(BITSTREAM, 2) +#define HPI_BITSTREAM_CLOCK_SOURCE HPI_CTL_ATTR(BITSTREAM, 3) + +#define HPI_POLARITY_POSITIVE 0 +#define HPI_POLARITY_NEGATIVE 1 + +/* Bitstream control get attributes */ +#define HPI_BITSTREAM_ACTIVITY 1 + +/* SampleClock control attributes */ +#define HPI_SAMPLECLOCK_SOURCE HPI_CTL_ATTR(SAMPLECLOCK, 1) +#define HPI_SAMPLECLOCK_SAMPLERATE HPI_CTL_ATTR(SAMPLECLOCK, 2) +#define HPI_SAMPLECLOCK_SOURCE_INDEX HPI_CTL_ATTR(SAMPLECLOCK, 3) +#define HPI_SAMPLECLOCK_LOCAL_SAMPLERATE\ + HPI_CTL_ATTR(SAMPLECLOCK, 4) +#define HPI_SAMPLECLOCK_AUTO HPI_CTL_ATTR(SAMPLECLOCK, 5) +#define HPI_SAMPLECLOCK_LOCAL_LOCK HPI_CTL_ATTR(SAMPLECLOCK, 6) + +/* Microphone control attributes */ +#define HPI_MICROPHONE_PHANTOM_POWER HPI_CTL_ATTR(MICROPHONE, 1) + +/** Equalizer control attributes +*/ +/** Used to get number of filters in an EQ. (Can't set) */ +#define HPI_EQUALIZER_NUM_FILTERS HPI_CTL_ATTR(EQUALIZER, 1) +/** Set/get the filter by type, freq, Q, gain */ +#define HPI_EQUALIZER_FILTER HPI_CTL_ATTR(EQUALIZER, 2) +/** Get the biquad coefficients */ +#define HPI_EQUALIZER_COEFFICIENTS HPI_CTL_ATTR(EQUALIZER, 3) + +#define HPI_COMPANDER_PARAMS HPI_CTL_ATTR(COMPANDER, 1) + +/* Cobranet control attributes. + MUST be distinct from all other control attributes. + This is so that host side processing can easily identify a Cobranet control + and apply additional host side operations (like copying data) as required. +*/ +#define HPI_COBRANET_SET HPI_CTL_ATTR(COBRANET, 1) +#define HPI_COBRANET_GET HPI_CTL_ATTR(COBRANET, 2) +#define HPI_COBRANET_SET_DATA HPI_CTL_ATTR(COBRANET, 3) +#define HPI_COBRANET_GET_DATA HPI_CTL_ATTR(COBRANET, 4) +#define HPI_COBRANET_GET_STATUS HPI_CTL_ATTR(COBRANET, 5) +#define HPI_COBRANET_SEND_PACKET HPI_CTL_ATTR(COBRANET, 6) +#define HPI_COBRANET_GET_PACKET HPI_CTL_ATTR(COBRANET, 7) + +/*------------------------------------------------------------ + Cobranet Chip Bridge - copied from HMI.H +------------------------------------------------------------*/ +#define HPI_COBRANET_HMI_cobra_bridge 0x20000 +#define HPI_COBRANET_HMI_cobra_bridge_tx_pkt_buf \ + (HPI_COBRANET_HMI_cobra_bridge + 0x1000) +#define HPI_COBRANET_HMI_cobra_bridge_rx_pkt_buf \ + (HPI_COBRANET_HMI_cobra_bridge + 0x2000) +#define HPI_COBRANET_HMI_cobra_if_table1 0x110000 +#define HPI_COBRANET_HMI_cobra_if_phy_address \ + (HPI_COBRANET_HMI_cobra_if_table1 + 0xd) +#define HPI_COBRANET_HMI_cobra_protocolIP 0x72000 +#define HPI_COBRANET_HMI_cobra_ip_mon_currentIP \ + (HPI_COBRANET_HMI_cobra_protocolIP + 0x0) +#define HPI_COBRANET_HMI_cobra_ip_mon_staticIP \ + (HPI_COBRANET_HMI_cobra_protocolIP + 0x2) +#define HPI_COBRANET_HMI_cobra_sys 0x100000 +#define HPI_COBRANET_HMI_cobra_sys_desc \ + (HPI_COBRANET_HMI_cobra_sys + 0x0) +#define HPI_COBRANET_HMI_cobra_sys_objectID \ + (HPI_COBRANET_HMI_cobra_sys + 0x100) +#define HPI_COBRANET_HMI_cobra_sys_contact \ + (HPI_COBRANET_HMI_cobra_sys + 0x200) +#define HPI_COBRANET_HMI_cobra_sys_name \ + (HPI_COBRANET_HMI_cobra_sys + 0x300) +#define HPI_COBRANET_HMI_cobra_sys_location \ + (HPI_COBRANET_HMI_cobra_sys + 0x400) + +/*------------------------------------------------------------ + Cobranet Chip Status bits +------------------------------------------------------------*/ +#define HPI_COBRANET_HMI_STATUS_RXPACKET 2 +#define HPI_COBRANET_HMI_STATUS_TXPACKET 3 + +/*------------------------------------------------------------ + Ethernet header size +------------------------------------------------------------*/ +#define HPI_ETHERNET_HEADER_SIZE (16) + +/* These defines are used to fill in protocol information for an Ethernet packet + sent using HMI on CS18102 */ +/** ID supplied by Cirrius for ASI packets. */ +#define HPI_ETHERNET_PACKET_ID 0x85 +/** Simple packet - no special routing required */ +#define HPI_ETHERNET_PACKET_V1 0x01 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI 0x20 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI_V1 0x21 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI 0x40 +/** This packet must make its way to the host across the HPI interface */ +#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI_V1 0x41 + +#define HPI_ETHERNET_UDP_PORT (44600) /*!< UDP messaging port */ + +/** Base network time out is set to 100 milli-seconds. */ +#define HPI_ETHERNET_TIMEOUT_MS (100) + +/** \defgroup tonedet_attr Tonedetector attributes +\{ +Used by HPI_ToneDetector_Set() and HPI_ToneDetector_Get() +*/ + +/** Set the threshold level of a tonedetector, +Threshold is a -ve number in units of dB/100, +*/ +#define HPI_TONEDETECTOR_THRESHOLD HPI_CTL_ATTR(TONEDETECTOR, 1) + +/** Get the current state of tonedetection +The result is a bitmap of detected tones. pairs of bits represent the left +and right channels, with left channel in LSB. +The lowest frequency detector state is in the LSB +*/ +#define HPI_TONEDETECTOR_STATE HPI_CTL_ATTR(TONEDETECTOR, 2) + +/** Get the frequency of a tonedetector band. +*/ +#define HPI_TONEDETECTOR_FREQUENCY HPI_CTL_ATTR(TONEDETECTOR, 3) + +/**\}*/ + +/** \defgroup silencedet_attr SilenceDetector attributes +\{ +*/ + +/** Get the current state of tonedetection +The result is a bitmap with 1s for silent channels. Left channel is in LSB +*/ +#define HPI_SILENCEDETECTOR_STATE \ + HPI_CTL_ATTR(SILENCEDETECTOR, 2) + +/** Set the threshold level of a SilenceDetector, +Threshold is a -ve number in units of dB/100, +*/ +#define HPI_SILENCEDETECTOR_THRESHOLD \ + HPI_CTL_ATTR(SILENCEDETECTOR, 1) + +/** get/set the silence time before the detector triggers +*/ +#define HPI_SILENCEDETECTOR_DELAY \ + HPI_CTL_ATTR(SILENCEDETECTOR, 3) + +/**\}*/ + +/* Locked memory buffer alloc/free phases */ +/** use one message to allocate or free physical memory */ +#define HPI_BUFFER_CMD_EXTERNAL 0 +/** alloc physical memory */ +#define HPI_BUFFER_CMD_INTERNAL_ALLOC 1 +/** send physical memory address to adapter */ +#define HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER 2 +/** notify adapter to stop using physical buffer */ +#define HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER 3 +/** free physical buffer */ +#define HPI_BUFFER_CMD_INTERNAL_FREE 4 + +/******************************************* CONTROLX ATTRIBUTES ****/ +/* NOTE: All controlx attributes must be unique, unlike control attributes */ + +/*****************************************************************************/ +/*****************************************************************************/ +/******** HPI LOW LEVEL MESSAGES *******/ +/*****************************************************************************/ +/*****************************************************************************/ +/** Pnp ids */ +/** "ASI" - actual is "ASX" - need to change */ +#define HPI_ID_ISAPNP_AUDIOSCIENCE 0x0669 +/** PCI vendor ID that AudioScience uses */ +#define HPI_PCI_VENDOR_ID_AUDIOSCIENCE 0x175C +/** PCI vendor ID that the DSP56301 has */ +#define HPI_PCI_VENDOR_ID_MOTOROLA 0x1057 +/** PCI vendor ID that TI uses */ +#define HPI_PCI_VENDOR_ID_TI 0x104C + +#define HPI_PCI_DEV_ID_PCI2040 0xAC60 +/** TI's C6205 PCI interface has this ID */ +#define HPI_PCI_DEV_ID_DSP6205 0xA106 + +#define HPI_USB_VENDOR_ID_AUDIOSCIENCE 0x1257 +#define HPI_USB_W2K_TAG 0x57495341 /* "ASIW" */ +#define HPI_USB_LINUX_TAG 0x4C495341 /* "ASIL" */ + +/** First 2 hex digits define the adapter family */ +#define HPI_ADAPTER_FAMILY_MASK 0xff00 + +#define HPI_ADAPTER_FAMILY_ASI(f) (f & HPI_ADAPTER_FAMILY_MASK) +#define HPI_ADAPTER_ASI(f) (f) + +/******************************************* message types */ +#define HPI_TYPE_MESSAGE 1 +#define HPI_TYPE_RESPONSE 2 +#define HPI_TYPE_DATA 3 +#define HPI_TYPE_SSX2BYPASS_MESSAGE 4 + +/******************************************* object types */ +#define HPI_OBJ_SUBSYSTEM 1 +#define HPI_OBJ_ADAPTER 2 +#define HPI_OBJ_OSTREAM 3 +#define HPI_OBJ_ISTREAM 4 +#define HPI_OBJ_MIXER 5 +#define HPI_OBJ_NODE 6 +#define HPI_OBJ_CONTROL 7 +#define HPI_OBJ_NVMEMORY 8 +#define HPI_OBJ_GPIO 9 +#define HPI_OBJ_WATCHDOG 10 +#define HPI_OBJ_CLOCK 11 +#define HPI_OBJ_PROFILE 12 +#define HPI_OBJ_CONTROLEX 13 +#define HPI_OBJ_ASYNCEVENT 14 + +#define HPI_OBJ_MAXINDEX 14 + +/******************************************* methods/functions */ + +#define HPI_OBJ_FUNCTION_SPACING 0x100 +#define HPI_MAKE_INDEX(obj, index) (obj * HPI_OBJ_FUNCTION_SPACING + index) +#define HPI_EXTRACT_INDEX(fn) (fn & 0xff) + +/* SUB-SYSTEM */ +#define HPI_SUBSYS_OPEN HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 1) +#define HPI_SUBSYS_GET_VERSION HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 2) +#define HPI_SUBSYS_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 3) +#define HPI_SUBSYS_FIND_ADAPTERS HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 4) +#define HPI_SUBSYS_CREATE_ADAPTER HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 5) +#define HPI_SUBSYS_CLOSE HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 6) +#define HPI_SUBSYS_DELETE_ADAPTER HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 7) +#define HPI_SUBSYS_DRIVER_LOAD HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 8) +#define HPI_SUBSYS_DRIVER_UNLOAD HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 9) +#define HPI_SUBSYS_READ_PORT_8 HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 10) +#define HPI_SUBSYS_WRITE_PORT_8 HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 11) +#define HPI_SUBSYS_GET_NUM_ADAPTERS HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 12) +#define HPI_SUBSYS_GET_ADAPTER HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 13) +#define HPI_SUBSYS_SET_NETWORK_INTERFACE HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 14) +#define HPI_SUBSYS_FUNCTION_COUNT 14 +/* ADAPTER */ +#define HPI_ADAPTER_OPEN HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 1) +#define HPI_ADAPTER_CLOSE HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 2) +#define HPI_ADAPTER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 3) +#define HPI_ADAPTER_GET_ASSERT HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 4) +#define HPI_ADAPTER_TEST_ASSERT HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 5) +#define HPI_ADAPTER_SET_MODE HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 6) +#define HPI_ADAPTER_GET_MODE HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 7) +#define HPI_ADAPTER_ENABLE_CAPABILITY HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 8) +#define HPI_ADAPTER_SELFTEST HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 9) +#define HPI_ADAPTER_FIND_OBJECT HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 10) +#define HPI_ADAPTER_QUERY_FLASH HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 11) +#define HPI_ADAPTER_START_FLASH HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 12) +#define HPI_ADAPTER_PROGRAM_FLASH HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 13) +#define HPI_ADAPTER_SET_PROPERTY HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 14) +#define HPI_ADAPTER_GET_PROPERTY HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 15) +#define HPI_ADAPTER_ENUM_PROPERTY HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 16) +#define HPI_ADAPTER_MODULE_INFO HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 17) +#define HPI_ADAPTER_DEBUG_READ HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 18) +#define HPI_ADAPTER_FUNCTION_COUNT 18 +/* OUTPUT STREAM */ +#define HPI_OSTREAM_OPEN HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 1) +#define HPI_OSTREAM_CLOSE HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 2) +#define HPI_OSTREAM_WRITE HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 3) +#define HPI_OSTREAM_START HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 4) +#define HPI_OSTREAM_STOP HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 5) +#define HPI_OSTREAM_RESET HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 6) +#define HPI_OSTREAM_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 7) +#define HPI_OSTREAM_QUERY_FORMAT HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 8) +#define HPI_OSTREAM_DATA HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 9) +#define HPI_OSTREAM_SET_VELOCITY HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 10) +#define HPI_OSTREAM_SET_PUNCHINOUT HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 11) +#define HPI_OSTREAM_SINEGEN HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 12) +#define HPI_OSTREAM_ANC_RESET HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 13) +#define HPI_OSTREAM_ANC_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 14) +#define HPI_OSTREAM_ANC_READ HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 15) +#define HPI_OSTREAM_SET_TIMESCALE HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 16) +#define HPI_OSTREAM_SET_FORMAT HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 17) +#define HPI_OSTREAM_HOSTBUFFER_ALLOC HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 18) +#define HPI_OSTREAM_HOSTBUFFER_FREE HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 19) +#define HPI_OSTREAM_GROUP_ADD HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 20) +#define HPI_OSTREAM_GROUP_GETMAP HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 21) +#define HPI_OSTREAM_GROUP_RESET HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 22) +#define HPI_OSTREAM_HOSTBUFFER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 23) +#define HPI_OSTREAM_WAIT_START HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 24) +#define HPI_OSTREAM_FUNCTION_COUNT 24 +/* INPUT STREAM */ +#define HPI_ISTREAM_OPEN HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 1) +#define HPI_ISTREAM_CLOSE HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 2) +#define HPI_ISTREAM_SET_FORMAT HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 3) +#define HPI_ISTREAM_READ HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 4) +#define HPI_ISTREAM_START HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 5) +#define HPI_ISTREAM_STOP HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 6) +#define HPI_ISTREAM_RESET HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 7) +#define HPI_ISTREAM_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 8) +#define HPI_ISTREAM_QUERY_FORMAT HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 9) +#define HPI_ISTREAM_ANC_RESET HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 10) +#define HPI_ISTREAM_ANC_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 11) +#define HPI_ISTREAM_ANC_WRITE HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 12) +#define HPI_ISTREAM_HOSTBUFFER_ALLOC HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 13) +#define HPI_ISTREAM_HOSTBUFFER_FREE HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 14) +#define HPI_ISTREAM_GROUP_ADD HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 15) +#define HPI_ISTREAM_GROUP_GETMAP HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 16) +#define HPI_ISTREAM_GROUP_RESET HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 17) +#define HPI_ISTREAM_HOSTBUFFER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 18) +#define HPI_ISTREAM_WAIT_START HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 19) +#define HPI_ISTREAM_FUNCTION_COUNT 19 +/* MIXER */ +/* NOTE: + GET_NODE_INFO, SET_CONNECTION, GET_CONNECTIONS are not currently used */ +#define HPI_MIXER_OPEN HPI_MAKE_INDEX(HPI_OBJ_MIXER, 1) +#define HPI_MIXER_CLOSE HPI_MAKE_INDEX(HPI_OBJ_MIXER, 2) +#define HPI_MIXER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_MIXER, 3) +#define HPI_MIXER_GET_NODE_INFO HPI_MAKE_INDEX(HPI_OBJ_MIXER, 4) +#define HPI_MIXER_GET_CONTROL HPI_MAKE_INDEX(HPI_OBJ_MIXER, 5) +#define HPI_MIXER_SET_CONNECTION HPI_MAKE_INDEX(HPI_OBJ_MIXER, 6) +#define HPI_MIXER_GET_CONNECTIONS HPI_MAKE_INDEX(HPI_OBJ_MIXER, 7) +#define HPI_MIXER_GET_CONTROL_BY_INDEX HPI_MAKE_INDEX(HPI_OBJ_MIXER, 8) +#define HPI_MIXER_GET_CONTROL_ARRAY_BY_INDEX HPI_MAKE_INDEX(HPI_OBJ_MIXER, 9) +#define HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES HPI_MAKE_INDEX(HPI_OBJ_MIXER, 10) +#define HPI_MIXER_STORE HPI_MAKE_INDEX(HPI_OBJ_MIXER, 11) +#define HPI_MIXER_FUNCTION_COUNT 11 +/* MIXER CONTROLS */ +#define HPI_CONTROL_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 1) +#define HPI_CONTROL_GET_STATE HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 2) +#define HPI_CONTROL_SET_STATE HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 3) +#define HPI_CONTROL_FUNCTION_COUNT 3 +/* NONVOL MEMORY */ +#define HPI_NVMEMORY_OPEN HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 1) +#define HPI_NVMEMORY_READ_BYTE HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 2) +#define HPI_NVMEMORY_WRITE_BYTE HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 3) +#define HPI_NVMEMORY_FUNCTION_COUNT 3 +/* GPIO */ +#define HPI_GPIO_OPEN HPI_MAKE_INDEX(HPI_OBJ_GPIO, 1) +#define HPI_GPIO_READ_BIT HPI_MAKE_INDEX(HPI_OBJ_GPIO, 2) +#define HPI_GPIO_WRITE_BIT HPI_MAKE_INDEX(HPI_OBJ_GPIO, 3) +#define HPI_GPIO_READ_ALL HPI_MAKE_INDEX(HPI_OBJ_GPIO, 4) +#define HPI_GPIO_WRITE_STATUS HPI_MAKE_INDEX(HPI_OBJ_GPIO, 5) +#define HPI_GPIO_FUNCTION_COUNT 5 +/* ASYNC EVENT */ +#define HPI_ASYNCEVENT_OPEN HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 1) +#define HPI_ASYNCEVENT_CLOSE HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 2) +#define HPI_ASYNCEVENT_WAIT HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 3) +#define HPI_ASYNCEVENT_GETCOUNT HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 4) +#define HPI_ASYNCEVENT_GET HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 5) +#define HPI_ASYNCEVENT_SENDEVENTS HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 6) +#define HPI_ASYNCEVENT_FUNCTION_COUNT 6 +/* WATCH-DOG */ +#define HPI_WATCHDOG_OPEN HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 1) +#define HPI_WATCHDOG_SET_TIME HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 2) +#define HPI_WATCHDOG_PING HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 3) +/* CLOCK */ +#define HPI_CLOCK_OPEN HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 1) +#define HPI_CLOCK_SET_TIME HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 2) +#define HPI_CLOCK_GET_TIME HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 3) +/* PROFILE */ +#define HPI_PROFILE_OPEN_ALL HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 1) +#define HPI_PROFILE_START_ALL HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 2) +#define HPI_PROFILE_STOP_ALL HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 3) +#define HPI_PROFILE_GET HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 4) +#define HPI_PROFILE_GET_IDLECOUNT HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 5) +#define HPI_PROFILE_GET_NAME HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 6) +#define HPI_PROFILE_GET_UTILIZATION HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 7) +#define HPI_PROFILE_FUNCTION_COUNT 7 +/* ////////////////////////////////////////////////////////////////////// */ +/* PRIVATE ATTRIBUTES */ + +/* ////////////////////////////////////////////////////////////////////// */ +/* STRUCTURES */ +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** PCI bus resource */ +struct hpi_pci { + u32 __iomem *ap_mem_base[HPI_MAX_ADAPTER_MEM_SPACES]; + struct pci_dev *p_os_data; + +#ifndef HPI64BIT /* keep structure size constant */ + u32 padding[HPI_MAX_ADAPTER_MEM_SPACES + 1]; +#endif + u16 vendor_id; + u16 device_id; + u16 subsys_vendor_id; + u16 subsys_device_id; + u16 bus_number; + u16 device_number; + u32 interrupt; +}; + +struct hpi_resource { + union { + const struct hpi_pci *pci; + const char *net_if; + } r; +#ifndef HPI64BIT /* keep structure size constant */ + u32 pad_to64; +#endif + u16 bus_type; /* HPI_BUS_PNPISA, _PCI, _USB etc */ + u16 padding; + +}; + +/** Format info used inside struct hpi_message + Not the same as public API struct hpi_format */ +struct hpi_msg_format { + u32 sample_rate; + /**< 11025, 32000, 44100 ... */ + u32 bit_rate; /**< for MPEG */ + u32 attributes; + /**< Stereo/JointStereo/Mono */ + u16 channels; /**< 1,2..., (or ancillary mode or idle bit */ + u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see \ref HPI_FORMATS. */ +}; + +/** Buffer+format structure. + Must be kept 7 * 32 bits to match public struct hpi_datastruct */ +struct hpi_msg_data { + struct hpi_msg_format format; + u8 *pb_data; +#ifndef HPI64BIT + u32 padding; +#endif + u32 data_size; +}; + +/** struct hpi_datastructure used up to 3.04 driver */ +struct hpi_data_legacy32 { + struct hpi_format format; + u32 pb_data; + u32 data_size; +}; + +#ifdef HPI64BIT +/* Compatibility version of struct hpi_data*/ +struct hpi_data_compat32 { + struct hpi_msg_format format; + u32 pb_data; + u32 padding; + u32 data_size; +}; +#endif + +struct hpi_buffer { + /** placehoder for backward compatability (see dwBufferSize) */ + struct hpi_msg_format reserved; + u32 command; /**< HPI_BUFFER_CMD_xxx*/ + u32 pci_address; /**< PCI physical address of buffer for DSP DMA */ + u32 buffer_size; /**< must line up with data_size of HPI_DATA*/ +}; + +/*/////////////////////////////////////////////////////////////////////////// */ +/* This is used for background buffer bus mastering stream buffers. */ +struct hpi_hostbuffer_status { + u32 samples_processed; + u32 auxiliary_data_available; + u32 stream_state; + /* DSP index in to the host bus master buffer. */ + u32 dSP_index; + /* Host index in to the host bus master buffer. */ + u32 host_index; + u32 size_in_bytes; +}; + +struct hpi_streamid { + u16 object_type; + /**< Type of object, HPI_OBJ_OSTREAM or HPI_OBJ_ISTREAM. */ + u16 stream_index; /**< outstream or instream index. */ +}; + +struct hpi_punchinout { + u32 punch_in_sample; + u32 punch_out_sample; +}; + +struct hpi_subsys_msg { + struct hpi_resource resource; +}; + +struct hpi_subsys_res { + u32 version; + u32 data; /* used to return extended version */ + u16 num_adapters; /* number of adapters */ + u16 adapter_index; + u16 aw_adapter_list[HPI_MAX_ADAPTERS]; +}; + +struct hpi_adapter_msg { + u32 adapter_mode; /* adapter mode */ + u16 assert_id; /* assert number for "test assert" call + object_index for find object call + query_or_set for hpi_adapter_set_mode_ex() */ + u16 object_type; /* for adapter find object call */ +}; + +union hpi_adapterx_msg { + struct hpi_adapter_msg adapter; + struct { + u32 offset; + } query_flash; + struct { + u32 offset; + u32 length; + u32 key; + } start_flash; + struct { + u32 checksum; + u16 sequence; + u16 length; + u16 offset; /**< offset from start of msg to data */ + u16 unused; + } program_flash; + struct { + u16 property; + u16 parameter1; + u16 parameter2; + } property_set; + struct { + u16 index; + u16 what; + u16 property_index; + } property_enum; + struct { + u16 index; + } module_info; + struct { + u32 dsp_address; + u32 count_bytes; + } debug_read; +}; + +struct hpi_adapter_res { + u32 serial_number; + u16 adapter_type; + u16 adapter_index; /* is this needed? also used for dsp_index */ + u16 num_instreams; + u16 num_outstreams; + u16 num_mixers; + u16 version; + u8 sz_adapter_assert[HPI_STRING_LEN]; +}; + +union hpi_adapterx_res { + struct hpi_adapter_res adapter; + struct { + u32 checksum; + u32 length; + u32 version; + } query_flash; + struct { + u16 sequence; + } program_flash; + struct { + u16 parameter1; + u16 parameter2; + } property_get; +}; + +struct hpi_stream_msg { + union { + struct hpi_msg_data data; + struct hpi_data_legacy32 data32; + u16 velocity; + struct hpi_punchinout pio; + u32 time_scale; + struct hpi_buffer buffer; + struct hpi_streamid stream; + } u; +}; + +struct hpi_stream_res { + union { + struct { + /* size of hardware buffer */ + u32 buffer_size; + /* OutStream - data to play, + InStream - data recorded */ + u32 data_available; + /* OutStream - samples played, + InStream - samples recorded */ + u32 samples_transferred; + /* Adapter - OutStream - data to play, + InStream - data recorded */ + u32 auxiliary_data_available; + u16 state; /* HPI_STATE_PLAYING, _STATE_STOPPED */ + u16 padding; + } stream_info; + struct { + u32 buffer_size; + u32 data_available; + u32 samples_transfered; + u16 state; + u16 outstream_index; + u16 instream_index; + u16 padding; + u32 auxiliary_data_available; + } legacy_stream_info; + struct { + /* bitmap of grouped OutStreams */ + u32 outstream_group_map; + /* bitmap of grouped InStreams */ + u32 instream_group_map; + } group_info; + struct { + /* pointer to the buffer */ + u8 *p_buffer; + /* pointer to the hostbuffer status */ + struct hpi_hostbuffer_status *p_status; + } hostbuffer_info; + } u; +}; + +struct hpi_mixer_msg { + u16 control_index; + u16 control_type; /* = HPI_CONTROL_METER _VOLUME etc */ + u16 padding1; /* maintain alignment of subsequent fields */ + u16 node_type1; /* = HPI_SOURCENODE_LINEIN etc */ + u16 node_index1; /* = 0..N */ + u16 node_type2; + u16 node_index2; + u16 padding2; /* round to 4 bytes */ +}; + +struct hpi_mixer_res { + u16 src_node_type; /* = HPI_SOURCENODE_LINEIN etc */ + u16 src_node_index; /* = 0..N */ + u16 dst_node_type; + u16 dst_node_index; + /* Also controlType for MixerGetControlByIndex */ + u16 control_index; + /* may indicate which DSP the control is located on */ + u16 dsp_index; +}; + +union hpi_mixerx_msg { + struct { + u16 starting_index; + u16 flags; + u32 length_in_bytes; /* length in bytes of p_data */ + u32 p_data; /* pointer to a data array */ + } gcabi; + struct { + u16 command; + u16 index; + } store; /* for HPI_MIXER_STORE message */ +}; + +union hpi_mixerx_res { + struct { + u32 bytes_returned; /* size of items returned */ + u32 p_data; /* pointer to data array */ + u16 more_to_do; /* indicates if there is more to do */ + } gcabi; +}; + +struct hpi_control_msg { + u16 attribute; /* control attribute or property */ + u16 saved_index; + u32 param1; /* generic parameter 1 */ + u32 param2; /* generic parameter 2 */ + short an_log_value[HPI_MAX_CHANNELS]; +}; + +struct hpi_control_union_msg { + u16 attribute; /* control attribute or property */ + u16 saved_index; /* only used in ctrl save/restore */ + union { + struct { + u32 param1; /* generic parameter 1 */ + u32 param2; /* generic parameter 2 */ + short an_log_value[HPI_MAX_CHANNELS]; + } old; + union { + u32 frequency; + u32 gain; + u32 band; + u32 deemphasis; + u32 program; + struct { + u32 mode; + u32 value; + } mode; + } tuner; + } u; +}; + +struct hpi_control_res { + /* Could make union. dwParam, anLogValue never used in same response */ + u32 param1; + u32 param2; + short an_log_value[HPI_MAX_CHANNELS]; +}; + +union hpi_control_union_res { + struct { + u32 param1; + u32 param2; + short an_log_value[HPI_MAX_CHANNELS]; + } old; + union { + u32 band; + u32 frequency; + u32 gain; + u32 level; + u32 deemphasis; + struct { + u32 data[2]; + u32 bLER; + } rds; + } tuner; + struct { + char sz_data[8]; + u32 remaining_chars; + } chars8; + char c_data12[12]; +}; + +/* HPI_CONTROLX_STRUCTURES */ + +/* Message */ + +/** Used for all HMI variables where max length <= 8 bytes +*/ +struct hpi_controlx_msg_cobranet_data { + u32 hmi_address; + u32 byte_count; + u32 data[2]; +}; + +/** Used for string data, and for packet bridge +*/ +struct hpi_controlx_msg_cobranet_bigdata { + u32 hmi_address; + u32 byte_count; + u8 *pb_data; +#ifndef HPI64BIT + u32 padding; +#endif +}; + +/** Used for PADS control reading of string fields. +*/ +struct hpi_controlx_msg_pad_data { + u32 field; + u32 byte_count; + u8 *pb_data; +#ifndef HPI64BIT + u32 padding; +#endif +}; + +/** Used for generic data +*/ + +struct hpi_controlx_msg_generic { + u32 param1; + u32 param2; +}; + +struct hpi_controlx_msg { + u16 attribute; /* control attribute or property */ + u16 saved_index; + union { + struct hpi_controlx_msg_cobranet_data cobranet_data; + struct hpi_controlx_msg_cobranet_bigdata cobranet_bigdata; + struct hpi_controlx_msg_generic generic; + struct hpi_controlx_msg_pad_data pad_data; + /*struct param_value universal_value; */ + /* nothing extra to send for status read */ + } u; +}; + +/* Response */ +/** +*/ +struct hpi_controlx_res_cobranet_data { + u32 byte_count; + u32 data[2]; +}; + +struct hpi_controlx_res_cobranet_bigdata { + u32 byte_count; +}; + +struct hpi_controlx_res_cobranet_status { + u32 status; + u32 readable_size; + u32 writeable_size; +}; + +struct hpi_controlx_res_generic { + u32 param1; + u32 param2; +}; + +struct hpi_controlx_res { + union { + struct hpi_controlx_res_cobranet_bigdata cobranet_bigdata; + struct hpi_controlx_res_cobranet_data cobranet_data; + struct hpi_controlx_res_cobranet_status cobranet_status; + struct hpi_controlx_res_generic generic; + /*struct param_info universal_info; */ + /*struct param_value universal_value; */ + } u; +}; + +struct hpi_nvmemory_msg { + u16 address; + u16 data; +}; + +struct hpi_nvmemory_res { + u16 size_in_bytes; + u16 data; +}; + +struct hpi_gpio_msg { + u16 bit_index; + u16 bit_data; +}; + +struct hpi_gpio_res { + u16 number_input_bits; + u16 number_output_bits; + u16 bit_data[4]; +}; + +struct hpi_async_msg { + u32 events; + u16 maximum_events; + u16 padding; +}; + +struct hpi_async_res { + union { + struct { + u16 count; + } count; + struct { + u32 events; + u16 number_returned; + u16 padding; + } get; + struct hpi_async_event event; + } u; +}; + +struct hpi_watchdog_msg { + u32 time_ms; +}; + +struct hpi_watchdog_res { + u32 time_ms; +}; + +struct hpi_clock_msg { + u16 hours; + u16 minutes; + u16 seconds; + u16 milli_seconds; +}; + +struct hpi_clock_res { + u16 size_in_bytes; + u16 hours; + u16 minutes; + u16 seconds; + u16 milli_seconds; + u16 padding; +}; + +struct hpi_profile_msg { + u16 bin_index; + u16 padding; +}; + +struct hpi_profile_res_open { + u16 max_profiles; +}; + +struct hpi_profile_res_time { + u32 micro_seconds; + u32 call_count; + u32 max_micro_seconds; + u32 min_micro_seconds; + u16 seconds; +}; + +struct hpi_profile_res_name { + u8 sz_name[32]; +}; + +struct hpi_profile_res { + union { + struct hpi_profile_res_open o; + struct hpi_profile_res_time t; + struct hpi_profile_res_name n; + } u; +}; + +struct hpi_message_header { + u16 size; /* total size in bytes */ + u8 type; /* HPI_TYPE_MESSAGE */ + u8 version; /* message version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* */ +}; + +struct hpi_message { + /* following fields must match HPI_MESSAGE_HEADER */ + u16 size; /* total size in bytes */ + u8 type; /* HPI_TYPE_MESSAGE */ + u8 version; /* message version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* */ + union { + struct hpi_subsys_msg s; + struct hpi_adapter_msg a; + union hpi_adapterx_msg ax; + struct hpi_stream_msg d; + struct hpi_mixer_msg m; + union hpi_mixerx_msg mx; /* extended mixer; */ + struct hpi_control_msg c; /* mixer control; */ + /* identical to struct hpi_control_msg, + but field naming is improved */ + struct hpi_control_union_msg cu; + struct hpi_controlx_msg cx; /* extended mixer control; */ + struct hpi_nvmemory_msg n; + struct hpi_gpio_msg l; /* digital i/o */ + struct hpi_watchdog_msg w; + struct hpi_clock_msg t; /* dsp time */ + struct hpi_profile_msg p; + struct hpi_async_msg as; + char fixed_size[32]; + } u; +}; + +#define HPI_MESSAGE_SIZE_BY_OBJECT { \ + sizeof(struct hpi_message_header) , /* default, no object type 0 */ \ + sizeof(struct hpi_message_header) + sizeof(struct hpi_subsys_msg),\ + sizeof(struct hpi_message_header) + sizeof(union hpi_adapterx_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_mixer_msg),\ + sizeof(struct hpi_message_header) , /* no node message */ \ + sizeof(struct hpi_message_header) + sizeof(struct hpi_control_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_nvmemory_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_gpio_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_watchdog_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_clock_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_profile_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_controlx_msg),\ + sizeof(struct hpi_message_header) + sizeof(struct hpi_async_msg) \ +} + +struct hpi_response_header { + u16 size; + u8 type; /* HPI_TYPE_RESPONSE */ + u8 version; /* response version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 error; /* HPI_ERROR_xxx */ + u16 specific_error; /* adapter specific error */ +}; + +struct hpi_response { +/* following fields must match HPI_RESPONSE_HEADER */ + u16 size; + u8 type; /* HPI_TYPE_RESPONSE */ + u8 version; /* response version */ + u16 object; /* HPI_OBJ_* */ + u16 function; /* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */ + u16 error; /* HPI_ERROR_xxx */ + u16 specific_error; /* adapter specific error */ + union { + struct hpi_subsys_res s; + struct hpi_adapter_res a; + union hpi_adapterx_res ax; + struct hpi_stream_res d; + struct hpi_mixer_res m; + union hpi_mixerx_res mx; /* extended mixer; */ + struct hpi_control_res c; /* mixer control; */ + /* identical to hpi_control_res, but field naming is improved */ + union hpi_control_union_res cu; + struct hpi_controlx_res cx; /* extended mixer control; */ + struct hpi_nvmemory_res n; + struct hpi_gpio_res l; /* digital i/o */ + struct hpi_watchdog_res w; + struct hpi_clock_res t; /* dsp time */ + struct hpi_profile_res p; + struct hpi_async_res as; + u8 bytes[52]; + } u; +}; + +#define HPI_RESPONSE_SIZE_BY_OBJECT { \ + sizeof(struct hpi_response_header) ,/* default, no object type 0 */ \ + sizeof(struct hpi_response_header) + sizeof(struct hpi_subsys_res),\ + sizeof(struct hpi_response_header) + sizeof(union hpi_adapterx_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_mixer_res),\ + sizeof(struct hpi_response_header) , /* no node response */ \ + sizeof(struct hpi_response_header) + sizeof(struct hpi_control_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_nvmemory_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_gpio_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_watchdog_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_clock_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_profile_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_controlx_res),\ + sizeof(struct hpi_response_header) + sizeof(struct hpi_async_res) \ +} + +/*********************** version 1 message/response *****************************/ +#define HPINET_ETHERNET_DATA_SIZE (1500) +#define HPINET_IP_HDR_SIZE (20) +#define HPINET_IP_DATA_SIZE (HPINET_ETHERNET_DATA_SIZE - HPINET_IP_HDR_SIZE) +#define HPINET_UDP_HDR_SIZE (8) +#define HPINET_UDP_DATA_SIZE (HPINET_IP_DATA_SIZE - HPINET_UDP_HDR_SIZE) +#define HPINET_ASI_HDR_SIZE (2) +#define HPINET_ASI_DATA_SIZE (HPINET_UDP_DATA_SIZE - HPINET_ASI_HDR_SIZE) + +#define HPI_MAX_PAYLOAD_SIZE (HPINET_ASI_DATA_SIZE - 2) + +/* New style message/response, but still V0 compatible */ +struct hpi_msg_adapter_get_info { + struct hpi_message_header h; +}; + +struct hpi_res_adapter_get_info { + struct hpi_response_header h; /*v0 */ + struct hpi_adapter_res p; +}; + +/* padding is so these are same size as v0 hpi_message */ +struct hpi_msg_adapter_query_flash { + struct hpi_message_header h; + u32 offset; + u8 pad_to_version0_size[sizeof(struct hpi_message) - /* V0 res */ + sizeof(struct hpi_message_header) - 1 * sizeof(u32)]; +}; + +/* padding is so these are same size as v0 hpi_response */ +struct hpi_res_adapter_query_flash { + struct hpi_response_header h; + u32 checksum; + u32 length; + u32 version; + u8 pad_to_version0_size[sizeof(struct hpi_response) - /* V0 res */ + sizeof(struct hpi_response_header) - 3 * sizeof(u32)]; +}; + +struct hpi_msg_adapter_start_flash { + struct hpi_message_header h; + u32 offset; + u32 length; + u32 key; + u8 pad_to_version0_size[sizeof(struct hpi_message) - /* V0 res */ + sizeof(struct hpi_message_header) - 3 * sizeof(u32)]; +}; + +struct hpi_res_adapter_start_flash { + struct hpi_response_header h; + u8 pad_to_version0_size[sizeof(struct hpi_response) - /* V0 res */ + sizeof(struct hpi_response_header)]; +}; + +struct hpi_msg_adapter_program_flash_payload { + u32 checksum; + u16 sequence; + u16 length; + u16 offset; /**< offset from start of msg to data */ + u16 unused; + /* ensure sizeof(header + payload) == sizeof(hpi_message_V0) + because old firmware expects data after message of this size */ + u8 pad_to_version0_size[sizeof(struct hpi_message) - /* V0 message */ + sizeof(struct hpi_message_header) - sizeof(u32) - + 4 * sizeof(u16)]; +}; + +struct hpi_msg_adapter_program_flash { + struct hpi_message_header h; + struct hpi_msg_adapter_program_flash_payload p; + u32 data[256]; +}; + +struct hpi_res_adapter_program_flash { + struct hpi_response_header h; + u16 sequence; + u8 pad_to_version0_size[sizeof(struct hpi_response) - /* V0 res */ + sizeof(struct hpi_response_header) - sizeof(u16)]; +}; + +#if 1 +#define hpi_message_header_v1 hpi_message_header +#define hpi_response_header_v1 hpi_response_header +#else +/* V1 headers in Addition to v0 headers */ +struct hpi_message_header_v1 { + struct hpi_message_header h0; +/* struct { +} h1; */ +}; + +struct hpi_response_header_v1 { + struct hpi_response_header h0; + struct { + u16 adapter_index; /* the adapter index */ + u16 obj_index; /* object index */ + } h1; +}; +#endif + +/* STRV HPI Packet */ +struct hpi_msg_strv { + struct hpi_message_header h; + struct hpi_entity strv; +}; + +struct hpi_res_strv { + struct hpi_response_header h; + struct hpi_entity strv; +}; +#define MIN_STRV_PACKET_SIZE sizeof(struct hpi_res_strv) + +struct hpi_msg_payload_v0 { + struct hpi_message_header h; + union { + struct hpi_subsys_msg s; + struct hpi_adapter_msg a; + union hpi_adapterx_msg ax; + struct hpi_stream_msg d; + struct hpi_mixer_msg m; + union hpi_mixerx_msg mx; + struct hpi_control_msg c; + struct hpi_control_union_msg cu; + struct hpi_controlx_msg cx; + struct hpi_nvmemory_msg n; + struct hpi_gpio_msg l; + struct hpi_watchdog_msg w; + struct hpi_clock_msg t; + struct hpi_profile_msg p; + struct hpi_async_msg as; + } u; +}; + +struct hpi_res_payload_v0 { + struct hpi_response_header h; + union { + struct hpi_subsys_res s; + struct hpi_adapter_res a; + union hpi_adapterx_res ax; + struct hpi_stream_res d; + struct hpi_mixer_res m; + union hpi_mixerx_res mx; + struct hpi_control_res c; + union hpi_control_union_res cu; + struct hpi_controlx_res cx; + struct hpi_nvmemory_res n; + struct hpi_gpio_res l; + struct hpi_watchdog_res w; + struct hpi_clock_res t; + struct hpi_profile_res p; + struct hpi_async_res as; + } u; +}; + +union hpi_message_buffer_v1 { + struct hpi_message m0; /* version 0 */ + struct hpi_message_header_v1 h; + unsigned char buf[HPI_MAX_PAYLOAD_SIZE]; +}; + +union hpi_response_buffer_v1 { + struct hpi_response r0; /* version 0 */ + struct hpi_response_header_v1 h; + unsigned char buf[HPI_MAX_PAYLOAD_SIZE]; +}; + +compile_time_assert((sizeof(union hpi_message_buffer_v1) <= + HPI_MAX_PAYLOAD_SIZE), message_buffer_ok); +compile_time_assert((sizeof(union hpi_response_buffer_v1) <= + HPI_MAX_PAYLOAD_SIZE), response_buffer_ok); + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for compact control calls */ +struct hpi_control_defn { + u8 type; + u8 channels; + u8 src_node_type; + u8 src_node_index; + u8 dest_node_type; + u8 dest_node_index; +}; + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for control caching (internal to HPI<->DSP interaction) */ + +/** A compact representation of (part of) a controls state. +Used for efficient transfer of the control state +between DSP and host or across a network +*/ +struct hpi_control_cache_info { + /** one of HPI_CONTROL_* */ + u8 control_type; + /** The total size of cached information in 32-bit words. */ + u8 size_in32bit_words; + /** The original index of the control on the DSP */ + u16 control_index; +}; + +struct hpi_control_cache_single { + struct hpi_control_cache_info i; + union { + struct { /* volume */ + u16 an_log[2]; + } v; + struct { /* peak meter */ + u16 an_log_peak[2]; + u16 an_logRMS[2]; + } p; + struct { /* channel mode */ + u16 mode; + } m; + struct { /* multiplexer */ + u16 source_node_type; + u16 source_node_index; + } x; + struct { /* level/trim */ + u16 an_log[2]; + } l; + struct { /* tuner - partial caching. + some attributes go to the DSP. */ + u32 freq_ink_hz; + u16 band; + u16 level; + } t; + struct { /* AESEBU rx status */ + u32 error_status; + u32 source; + } aes3rx; + struct { /* AESEBU tx */ + u32 format; + } aes3tx; + struct { /* tone detector */ + u16 state; + } tone; + struct { /* silence detector */ + u32 state; + u32 count; + } silence; + struct { /* sample clock */ + u16 source; + u16 source_index; + u32 sample_rate; + } clk; + struct { /* microphone control */ + u16 state; + } phantom_power; + struct { /* generic control */ + u32 dw1; + u32 dw2; + } g; + } u; +}; + +struct hpi_control_cache_pad { + struct hpi_control_cache_info i; + u32 field_valid_flags; + u8 c_channel[8]; + u8 c_artist[40]; + u8 c_title[40]; + u8 c_comment[200]; + u32 pTY; + u32 pI; + u32 traffic_supported; + u32 traffic_anouncement; +}; + +/*/////////////////////////////////////////////////////////////////////////// */ +/* declarations for 2^N sized FIFO buffer (internal to HPI<->DSP interaction) */ +struct hpi_fifo_buffer { + u32 size; + u32 dSP_index; + u32 host_index; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/* skip host side function declarations for DSP + compile and documentation extraction */ + +char hpi_handle_object(const u32 handle); + +void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index, + u16 *pw_object_index); + +u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index, + const u16 object_index); + +/*////////////////////////////////////////////////////////////////////////// */ + +/* main HPI entry point */ +hpi_handler_func hpi_send_recv; + +/* UDP message */ +void hpi_send_recvUDP(struct hpi_message *phm, struct hpi_response *phr, + const unsigned int timeout); + +/* used in PnP OS/driver */ +u16 hpi_subsys_create_adapter(const struct hpi_hsubsys *ph_subsys, + const struct hpi_resource *p_resource, u16 *pw_adapter_index); + +u16 hpi_subsys_delete_adapter(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index); + +u16 hpi_outstream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status); + +u16 hpi_instream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status); + +u16 hpi_adapter_restart(u16 adapter_index); + +/* +The following 3 functions were last declared in header files for +driver 3.10. HPI_ControlQuery() used to be the recommended way +of getting a volume range. Declared here for binary asihpi32.dll +compatibility. +*/ + +void hpi_format_to_msg(struct hpi_msg_format *pMF, + const struct hpi_format *pF); +void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR); + +/*////////////////////////////////////////////////////////////////////////// */ +/* declarations for individual HPI entry points */ +hpi_handler_func HPI_1000; +hpi_handler_func HPI_6000; +hpi_handler_func HPI_6205; +hpi_handler_func HPI_COMMON; + +#endif /* _HPI_INTERNAL_H_ */ diff --git a/sound/pci/asihpi/hpicmn.c b/sound/pci/asihpi/hpicmn.c new file mode 100644 index 00000000000..565102cae4f --- /dev/null +++ b/sound/pci/asihpi/hpicmn.c @@ -0,0 +1,643 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +\file hpicmn.c + + Common functions used by hpixxxx.c modules + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ +#define SOURCEFILE_NAME "hpicmn.c" + +#include "hpi_internal.h" +#include "hpidebug.h" +#include "hpicmn.h" + +struct hpi_adapters_list { + struct hpios_spinlock list_lock; + struct hpi_adapter_obj adapter[HPI_MAX_ADAPTERS]; + u16 gw_num_adapters; +}; + +static struct hpi_adapters_list adapters; + +/** +* Given an HPI Message that was sent out and a response that was received, +* validate that the response has the correct fields filled in, +* i.e ObjectType, Function etc +**/ +u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr) +{ + u16 error = 0; + + if ((phr->type != HPI_TYPE_RESPONSE) + || (phr->object != phm->object) + || (phr->function != phm->function)) + error = HPI_ERROR_INVALID_RESPONSE; + + return error; +} + +u16 hpi_add_adapter(struct hpi_adapter_obj *pao) +{ + u16 retval = 0; + /*HPI_ASSERT(pao->wAdapterType); */ + + hpios_alistlock_lock(&adapters); + + if (pao->index >= HPI_MAX_ADAPTERS) { + retval = HPI_ERROR_BAD_ADAPTER_NUMBER; + goto unlock; + } + + if (adapters.adapter[pao->index].adapter_type) { + { + retval = HPI_DUPLICATE_ADAPTER_NUMBER; + goto unlock; + } + } + adapters.adapter[pao->index] = *pao; + hpios_dsplock_init(&adapters.adapter[pao->index]); + adapters.gw_num_adapters++; + +unlock: + hpios_alistlock_un_lock(&adapters); + return retval; +} + +void hpi_delete_adapter(struct hpi_adapter_obj *pao) +{ + memset(pao, 0, sizeof(struct hpi_adapter_obj)); + + hpios_alistlock_lock(&adapters); + adapters.gw_num_adapters--; /* dec the number of adapters */ + hpios_alistlock_un_lock(&adapters); +} + +/** +* FindAdapter returns a pointer to the struct hpi_adapter_obj with +* index wAdapterIndex in an HPI_ADAPTERS_LIST structure. +* +*/ +struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index) +{ + struct hpi_adapter_obj *pao = NULL; + + if (adapter_index >= HPI_MAX_ADAPTERS) { + HPI_DEBUG_LOG(VERBOSE, "find_adapter invalid index %d ", + adapter_index); + return NULL; + } + + pao = &adapters.adapter[adapter_index]; + if (pao->adapter_type != 0) { + /* + HPI_DEBUG_LOG(VERBOSE, "Found adapter index %d\n", + wAdapterIndex); + */ + return pao; + } else { + /* + HPI_DEBUG_LOG(VERBOSE, "No adapter index %d\n", + wAdapterIndex); + */ + return NULL; + } +} + +/** +* +* wipe an HPI_ADAPTERS_LIST structure. +* +**/ +static void wipe_adapter_list(void + ) +{ + memset(&adapters, 0, sizeof(adapters)); +} + +/** +* SubSysGetAdapters fills awAdapterList in an struct hpi_response structure +* with all adapters in the given HPI_ADAPTERS_LIST. +* +*/ +static void subsys_get_adapters(struct hpi_response *phr) +{ + /* fill in the response adapter array with the position */ + /* identified by the adapter number/index of the adapters in */ + /* this HPI */ + /* i.e. if we have an A120 with it's jumper set to */ + /* Adapter Number 2 then put an Adapter type A120 in the */ + /* array in position 1 */ + /* NOTE: AdapterNumber is 1..N, Index is 0..N-1 */ + + /* input: NONE */ + /* output: wNumAdapters */ + /* awAdapter[] */ + /* */ + + short i; + struct hpi_adapter_obj *pao = NULL; + + HPI_DEBUG_LOG(VERBOSE, "subsys_get_adapters\n"); + + /* for each adapter, place it's type in the position of the array */ + /* corresponding to it's adapter number */ + for (i = 0; i < adapters.gw_num_adapters; i++) { + pao = &adapters.adapter[i]; + if (phr->u.s.aw_adapter_list[pao->index] != 0) { + phr->error = HPI_DUPLICATE_ADAPTER_NUMBER; + phr->specific_error = pao->index; + return; + } + phr->u.s.aw_adapter_list[pao->index] = pao->adapter_type; + } + + phr->u.s.num_adapters = adapters.gw_num_adapters; + phr->error = 0; /* the function completed OK; */ +} + +static unsigned int control_cache_alloc_check(struct hpi_control_cache *pC) +{ + unsigned int i; + int cached = 0; + if (!pC) + return 0; + if ((!pC->init) && (pC->p_cache != NULL) && (pC->control_count) + && (pC->cache_size_in_bytes) + ) { + u32 *p_master_cache; + pC->init = 1; + + p_master_cache = (u32 *)pC->p_cache; + HPI_DEBUG_LOG(VERBOSE, "check %d controls\n", + pC->control_count); + for (i = 0; i < pC->control_count; i++) { + struct hpi_control_cache_info *info = + (struct hpi_control_cache_info *) + p_master_cache; + + if (info->control_type) { + pC->p_info[i] = info; + cached++; + } else + pC->p_info[i] = NULL; + + if (info->size_in32bit_words) + p_master_cache += info->size_in32bit_words; + else + p_master_cache += + sizeof(struct + hpi_control_cache_single) / + sizeof(u32); + + HPI_DEBUG_LOG(VERBOSE, + "cached %d, pinfo %p index %d type %d\n", + cached, pC->p_info[i], info->control_index, + info->control_type); + } + /* + We didn't find anything to cache, so try again later ! + */ + if (!cached) + pC->init = 0; + } + return pC->init; +} + +/** Find a control. +*/ +static short find_control(struct hpi_message *phm, + struct hpi_control_cache *p_cache, struct hpi_control_cache_info **pI, + u16 *pw_control_index) +{ + *pw_control_index = phm->obj_index; + + if (!control_cache_alloc_check(p_cache)) { + HPI_DEBUG_LOG(VERBOSE, + "control_cache_alloc_check() failed. adap%d ci%d\n", + phm->adapter_index, *pw_control_index); + return 0; + } + + *pI = p_cache->p_info[*pw_control_index]; + if (!*pI) { + HPI_DEBUG_LOG(VERBOSE, "uncached adap %d, control %d\n", + phm->adapter_index, *pw_control_index); + return 0; + } else { + HPI_DEBUG_LOG(VERBOSE, "find_control() type %d\n", + (*pI)->control_type); + } + return 1; +} + +/** Used by the kernel driver to figure out if a buffer needs mapping. + */ +short hpi_check_buffer_mapping(struct hpi_control_cache *p_cache, + struct hpi_message *phm, void **p, unsigned int *pN) +{ + *pN = 0; + *p = NULL; + if ((phm->function == HPI_CONTROL_GET_STATE) + && (phm->object == HPI_OBJ_CONTROLEX) + ) { + u16 control_index; + struct hpi_control_cache_info *pI; + + if (!find_control(phm, p_cache, &pI, &control_index)) + return 0; + } + return 0; +} + +/* allow unified treatment of several string fields within struct */ +#define HPICMN_PAD_OFS_AND_SIZE(m) {\ + offsetof(struct hpi_control_cache_pad, m), \ + sizeof(((struct hpi_control_cache_pad *)(NULL))->m) } + +struct pad_ofs_size { + unsigned int offset; + unsigned int field_size; +}; + +static struct pad_ofs_size pad_desc[] = { + HPICMN_PAD_OFS_AND_SIZE(c_channel), /* HPI_PAD_CHANNEL_NAME */ + HPICMN_PAD_OFS_AND_SIZE(c_artist), /* HPI_PAD_ARTIST */ + HPICMN_PAD_OFS_AND_SIZE(c_title), /* HPI_PAD_TITLE */ + HPICMN_PAD_OFS_AND_SIZE(c_comment), /* HPI_PAD_COMMENT */ +}; + +/** CheckControlCache checks the cache and fills the struct hpi_response + * accordingly. It returns one if a cache hit occurred, zero otherwise. + */ +short hpi_check_control_cache(struct hpi_control_cache *p_cache, + struct hpi_message *phm, struct hpi_response *phr) +{ + short found = 1; + u16 control_index; + struct hpi_control_cache_info *pI; + struct hpi_control_cache_single *pC; + struct hpi_control_cache_pad *p_pad; + + if (!find_control(phm, p_cache, &pI, &control_index)) + return 0; + + phr->error = 0; + + /* pC is the default cached control strucure. May be cast to + something else in the following switch statement. + */ + pC = (struct hpi_control_cache_single *)pI; + p_pad = (struct hpi_control_cache_pad *)pI; + + switch (pI->control_type) { + + case HPI_CONTROL_METER: + if (phm->u.c.attribute == HPI_METER_PEAK) { + phr->u.c.an_log_value[0] = pC->u.p.an_log_peak[0]; + phr->u.c.an_log_value[1] = pC->u.p.an_log_peak[1]; + } else if (phm->u.c.attribute == HPI_METER_RMS) { + phr->u.c.an_log_value[0] = pC->u.p.an_logRMS[0]; + phr->u.c.an_log_value[1] = pC->u.p.an_logRMS[1]; + } else + found = 0; + break; + case HPI_CONTROL_VOLUME: + if (phm->u.c.attribute == HPI_VOLUME_GAIN) { + phr->u.c.an_log_value[0] = pC->u.v.an_log[0]; + phr->u.c.an_log_value[1] = pC->u.v.an_log[1]; + } else + found = 0; + break; + case HPI_CONTROL_MULTIPLEXER: + if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) { + phr->u.c.param1 = pC->u.x.source_node_type; + phr->u.c.param2 = pC->u.x.source_node_index; + } else { + found = 0; + } + break; + case HPI_CONTROL_CHANNEL_MODE: + if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE) + phr->u.c.param1 = pC->u.m.mode; + else + found = 0; + break; + case HPI_CONTROL_LEVEL: + if (phm->u.c.attribute == HPI_LEVEL_GAIN) { + phr->u.c.an_log_value[0] = pC->u.l.an_log[0]; + phr->u.c.an_log_value[1] = pC->u.l.an_log[1]; + } else + found = 0; + break; + case HPI_CONTROL_TUNER: + { + struct hpi_control_cache_single *pCT = + (struct hpi_control_cache_single *)pI; + if (phm->u.c.attribute == HPI_TUNER_FREQ) + phr->u.c.param1 = pCT->u.t.freq_ink_hz; + else if (phm->u.c.attribute == HPI_TUNER_BAND) + phr->u.c.param1 = pCT->u.t.band; + else if ((phm->u.c.attribute == HPI_TUNER_LEVEL) + && (phm->u.c.param1 == + HPI_TUNER_LEVEL_AVERAGE)) + phr->u.c.param1 = pCT->u.t.level; + else + found = 0; + } + break; + case HPI_CONTROL_AESEBU_RECEIVER: + if (phm->u.c.attribute == HPI_AESEBURX_ERRORSTATUS) + phr->u.c.param1 = pC->u.aes3rx.error_status; + else if (phm->u.c.attribute == HPI_AESEBURX_FORMAT) + phr->u.c.param1 = pC->u.aes3rx.source; + else + found = 0; + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT) + phr->u.c.param1 = pC->u.aes3tx.format; + else + found = 0; + break; + case HPI_CONTROL_TONEDETECTOR: + if (phm->u.c.attribute == HPI_TONEDETECTOR_STATE) + phr->u.c.param1 = pC->u.tone.state; + else + found = 0; + break; + case HPI_CONTROL_SILENCEDETECTOR: + if (phm->u.c.attribute == HPI_SILENCEDETECTOR_STATE) { + phr->u.c.param1 = pC->u.silence.state; + phr->u.c.param2 = pC->u.silence.count; + } else + found = 0; + break; + case HPI_CONTROL_MICROPHONE: + if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER) + phr->u.c.param1 = pC->u.phantom_power.state; + else + found = 0; + break; + case HPI_CONTROL_SAMPLECLOCK: + if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE) + phr->u.c.param1 = pC->u.clk.source; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) { + if (pC->u.clk.source_index == + HPI_ERROR_ILLEGAL_CACHE_VALUE) { + phr->u.c.param1 = 0; + phr->error = HPI_ERROR_INVALID_OPERATION; + } else + phr->u.c.param1 = pC->u.clk.source_index; + } else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE) + phr->u.c.param1 = pC->u.clk.sample_rate; + else + found = 0; + break; + case HPI_CONTROL_PAD: + + if (!(p_pad->field_valid_flags & (1 << + HPI_CTL_ATTR_INDEX(phm->u.c. + attribute)))) { + phr->error = HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + break; + } + + if (phm->u.c.attribute == HPI_PAD_PROGRAM_ID) + phr->u.c.param1 = p_pad->pI; + else if (phm->u.c.attribute == HPI_PAD_PROGRAM_TYPE) + phr->u.c.param1 = p_pad->pTY; + else { + unsigned int index = + HPI_CTL_ATTR_INDEX(phm->u.c.attribute) - 1; + unsigned int offset = phm->u.c.param1; + unsigned int pad_string_len, field_size; + char *pad_string; + unsigned int tocopy; + + HPI_DEBUG_LOG(VERBOSE, "PADS HPI_PADS_ %d\n", + phm->u.c.attribute); + + if (index > ARRAY_SIZE(pad_desc) - 1) { + phr->error = + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE; + break; + } + + pad_string = ((char *)p_pad) + pad_desc[index].offset; + field_size = pad_desc[index].field_size; + /* Ensure null terminator */ + pad_string[field_size - 1] = 0; + + pad_string_len = strlen(pad_string) + 1; + + if (offset > pad_string_len) { + phr->error = HPI_ERROR_INVALID_CONTROL_VALUE; + break; + } + + tocopy = pad_string_len - offset; + if (tocopy > sizeof(phr->u.cu.chars8.sz_data)) + tocopy = sizeof(phr->u.cu.chars8.sz_data); + + HPI_DEBUG_LOG(VERBOSE, + "PADS memcpy(%d), offset %d \n", tocopy, + offset); + memcpy(phr->u.cu.chars8.sz_data, &pad_string[offset], + tocopy); + + phr->u.cu.chars8.remaining_chars = + pad_string_len - offset - tocopy; + } + break; + default: + found = 0; + break; + } + + if (found) + HPI_DEBUG_LOG(VERBOSE, + "cached adap %d, ctl %d, type %d, attr %d\n", + phm->adapter_index, pI->control_index, + pI->control_type, phm->u.c.attribute); + else + HPI_DEBUG_LOG(VERBOSE, + "uncached adap %d, ctl %d, ctl type %d\n", + phm->adapter_index, pI->control_index, + pI->control_type); + + if (found) + phr->size = + sizeof(struct hpi_response_header) + + sizeof(struct hpi_control_res); + + return found; +} + +/** Updates the cache with Set values. + +Only update if no error. +Volume and Level return the limited values in the response, so use these +Multiplexer does so use sent values +*/ +void hpi_sync_control_cache(struct hpi_control_cache *p_cache, + struct hpi_message *phm, struct hpi_response *phr) +{ + u16 control_index; + struct hpi_control_cache_single *pC; + struct hpi_control_cache_info *pI; + + if (!find_control(phm, p_cache, &pI, &control_index)) + return; + + /* pC is the default cached control strucure. + May be cast to something else in the following switch statement. + */ + pC = (struct hpi_control_cache_single *)pI; + + switch (pI->control_type) { + case HPI_CONTROL_VOLUME: + if (phm->u.c.attribute == HPI_VOLUME_GAIN) { + pC->u.v.an_log[0] = phr->u.c.an_log_value[0]; + pC->u.v.an_log[1] = phr->u.c.an_log_value[1]; + } + break; + case HPI_CONTROL_MULTIPLEXER: + /* mux does not return its setting on Set command. */ + if (phr->error) + return; + if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) { + pC->u.x.source_node_type = (u16)phm->u.c.param1; + pC->u.x.source_node_index = (u16)phm->u.c.param2; + } + break; + case HPI_CONTROL_CHANNEL_MODE: + /* mode does not return its setting on Set command. */ + if (phr->error) + return; + if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE) + pC->u.m.mode = (u16)phm->u.c.param1; + break; + case HPI_CONTROL_LEVEL: + if (phm->u.c.attribute == HPI_LEVEL_GAIN) { + pC->u.v.an_log[0] = phr->u.c.an_log_value[0]; + pC->u.v.an_log[1] = phr->u.c.an_log_value[1]; + } + break; + case HPI_CONTROL_MICROPHONE: + if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER) + pC->u.phantom_power.state = (u16)phm->u.c.param1; + break; + case HPI_CONTROL_AESEBU_TRANSMITTER: + if (phr->error) + return; + if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT) + pC->u.aes3tx.format = phm->u.c.param1; + break; + case HPI_CONTROL_AESEBU_RECEIVER: + if (phr->error) + return; + if (phm->u.c.attribute == HPI_AESEBURX_FORMAT) + pC->u.aes3rx.source = phm->u.c.param1; + break; + case HPI_CONTROL_SAMPLECLOCK: + if (phr->error) + return; + if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE) + pC->u.clk.source = (u16)phm->u.c.param1; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) + pC->u.clk.source_index = (u16)phm->u.c.param1; + else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE) + pC->u.clk.sample_rate = phm->u.c.param1; + break; + default: + break; + } +} + +struct hpi_control_cache *hpi_alloc_control_cache(const u32 + number_of_controls, const u32 size_in_bytes, + struct hpi_control_cache_info *pDSP_control_buffer) +{ + struct hpi_control_cache *p_cache = + kmalloc(sizeof(*p_cache), GFP_KERNEL); + p_cache->cache_size_in_bytes = size_in_bytes; + p_cache->control_count = number_of_controls; + p_cache->p_cache = + (struct hpi_control_cache_single *)pDSP_control_buffer; + p_cache->init = 0; + p_cache->p_info = + kmalloc(sizeof(*p_cache->p_info) * p_cache->control_count, + GFP_KERNEL); + return p_cache; +} + +void hpi_free_control_cache(struct hpi_control_cache *p_cache) +{ + if ((p_cache->init) && (p_cache->p_info)) { + kfree(p_cache->p_info); + p_cache->p_info = NULL; + p_cache->init = 0; + kfree(p_cache); + } +} + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr) +{ + + switch (phm->function) { + case HPI_SUBSYS_OPEN: + case HPI_SUBSYS_CLOSE: + case HPI_SUBSYS_DRIVER_UNLOAD: + phr->error = 0; + break; + case HPI_SUBSYS_DRIVER_LOAD: + wipe_adapter_list(); + hpios_alistlock_init(&adapters); + phr->error = 0; + break; + case HPI_SUBSYS_GET_INFO: + subsys_get_adapters(phr); + break; + case HPI_SUBSYS_CREATE_ADAPTER: + case HPI_SUBSYS_DELETE_ADAPTER: + phr->error = 0; + break; + default: + phr->error = HPI_ERROR_INVALID_FUNC; + break; + } +} + +void HPI_COMMON(struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->type) { + case HPI_TYPE_MESSAGE: + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr); + break; + } + break; + + default: + phr->error = HPI_ERROR_INVALID_TYPE; + break; + } +} diff --git a/sound/pci/asihpi/hpicmn.h b/sound/pci/asihpi/hpicmn.h new file mode 100644 index 00000000000..6229022f56c --- /dev/null +++ b/sound/pci/asihpi/hpicmn.h @@ -0,0 +1,64 @@ +/** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +*/ + +struct hpi_adapter_obj { + struct hpi_pci pci; /* PCI info - bus#,dev#,address etc */ + u16 adapter_type; /* ASI6701 etc */ + u16 index; /* */ + u16 open; /* =1 when adapter open */ + u16 mixer_open; + + struct hpios_spinlock dsp_lock; + + u16 dsp_crashed; + u16 has_control_cache; + void *priv; +}; + +struct hpi_control_cache { + u32 init; /**< indicates whether the + structures are initialized */ + u32 control_count; + u32 cache_size_in_bytes; + struct hpi_control_cache_info + **p_info; /**< pointer to allocated memory of + lookup pointers. */ + struct hpi_control_cache_single + *p_cache; /**< pointer to DSP's control cache. */ +}; + +struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index); +u16 hpi_add_adapter(struct hpi_adapter_obj *pao); + +void hpi_delete_adapter(struct hpi_adapter_obj *pao); + +short hpi_check_control_cache(struct hpi_control_cache *pC, + struct hpi_message *phm, struct hpi_response *phr); +struct hpi_control_cache *hpi_alloc_control_cache(const u32 + number_of_controls, const u32 size_in_bytes, + struct hpi_control_cache_info + *pDSP_control_buffer); +void hpi_free_control_cache(struct hpi_control_cache *p_cache); + +void hpi_sync_control_cache(struct hpi_control_cache *pC, + struct hpi_message *phm, struct hpi_response *phr); +u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr); +short hpi_check_buffer_mapping(struct hpi_control_cache *p_cache, + struct hpi_message *phm, void **p, unsigned int *pN); diff --git a/sound/pci/asihpi/hpidebug.c b/sound/pci/asihpi/hpidebug.c new file mode 100644 index 00000000000..4cd85a401b3 --- /dev/null +++ b/sound/pci/asihpi/hpidebug.c @@ -0,0 +1,225 @@ +/************************************************************************ + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Debug macro translation. + +************************************************************************/ + +#include "hpi_internal.h" +#include "hpidebug.h" + +/* Debug level; 0 quiet; 1 informative, 2 debug, 3 verbose debug. */ +int hpi_debug_level = HPI_DEBUG_LEVEL_DEFAULT; + +void hpi_debug_init(void) +{ + printk(KERN_INFO "debug start\n"); +} + +int hpi_debug_level_set(int level) +{ + int old_level; + + old_level = hpi_debug_level; + hpi_debug_level = level; + return old_level; +} + +int hpi_debug_level_get(void) +{ + return hpi_debug_level; +} + +#ifdef HPIOS_DEBUG_PRINT +/* implies OS has no printf-like function */ +#include + +void hpi_debug_printf(char *fmt, ...) +{ + va_list arglist; + char buffer[128]; + + va_start(arglist, fmt); + + if (buffer[0]) + HPIOS_DEBUG_PRINT(buffer); + va_end(arglist); +} +#endif + +struct treenode { + void *array; + unsigned int num_elements; +}; + +#define make_treenode_from_array(nodename, array) \ +static void *tmp_strarray_##nodename[] = array; \ +static struct treenode nodename = { \ + &tmp_strarray_##nodename, \ + ARRAY_SIZE(tmp_strarray_##nodename) \ +}; + +#define get_treenode_elem(node_ptr, idx, type) \ + (&(*((type *)(node_ptr)->array)[idx])) + +make_treenode_from_array(hpi_control_type_strings, HPI_CONTROL_TYPE_STRINGS) + + make_treenode_from_array(hpi_subsys_strings, HPI_SUBSYS_STRINGS) + make_treenode_from_array(hpi_adapter_strings, HPI_ADAPTER_STRINGS) + make_treenode_from_array(hpi_istream_strings, HPI_ISTREAM_STRINGS) + make_treenode_from_array(hpi_ostream_strings, HPI_OSTREAM_STRINGS) + make_treenode_from_array(hpi_mixer_strings, HPI_MIXER_STRINGS) + make_treenode_from_array(hpi_node_strings, + { + "NODE is invalid object"}) + + make_treenode_from_array(hpi_control_strings, HPI_CONTROL_STRINGS) + make_treenode_from_array(hpi_nvmemory_strings, HPI_OBJ_STRINGS) + make_treenode_from_array(hpi_digitalio_strings, HPI_DIGITALIO_STRINGS) + make_treenode_from_array(hpi_watchdog_strings, HPI_WATCHDOG_STRINGS) + make_treenode_from_array(hpi_clock_strings, HPI_CLOCK_STRINGS) + make_treenode_from_array(hpi_profile_strings, HPI_PROFILE_STRINGS) + make_treenode_from_array(hpi_asyncevent_strings, HPI_ASYNCEVENT_STRINGS) +#define HPI_FUNCTION_STRINGS \ +{ \ + &hpi_subsys_strings,\ + &hpi_adapter_strings,\ + &hpi_ostream_strings,\ + &hpi_istream_strings,\ + &hpi_mixer_strings,\ + &hpi_node_strings,\ + &hpi_control_strings,\ + &hpi_nvmemory_strings,\ + &hpi_digitalio_strings,\ + &hpi_watchdog_strings,\ + &hpi_clock_strings,\ + &hpi_profile_strings,\ + &hpi_control_strings, \ + &hpi_asyncevent_strings \ +}; + make_treenode_from_array(hpi_function_strings, HPI_FUNCTION_STRINGS) + + compile_time_assert(HPI_OBJ_MAXINDEX == 14, obj_list_doesnt_match); + +static char *hpi_function_string(unsigned int function) +{ + unsigned int object; + struct treenode *tmp; + + object = function / HPI_OBJ_FUNCTION_SPACING; + function = function - object * HPI_OBJ_FUNCTION_SPACING; + + if (object == 0 || object == HPI_OBJ_NODE + || object > hpi_function_strings.num_elements) + return "invalid object"; + + tmp = get_treenode_elem(&hpi_function_strings, object - 1, + struct treenode *); + + if (function == 0 || function > tmp->num_elements) + return "invalid function"; + + return get_treenode_elem(tmp, function - 1, char *); +} + +void hpi_debug_message(struct hpi_message *phm, char *sz_fileline) +{ + if (phm) { + if ((phm->object <= HPI_OBJ_MAXINDEX) && phm->object) { + u16 index = 0; + u16 attrib = 0; + int is_control = 0; + + index = phm->obj_index; + switch (phm->object) { + case HPI_OBJ_ADAPTER: + case HPI_OBJ_PROFILE: + break; + case HPI_OBJ_MIXER: + if (phm->function == + HPI_MIXER_GET_CONTROL_BY_INDEX) + index = phm->u.m.control_index; + break; + case HPI_OBJ_OSTREAM: + case HPI_OBJ_ISTREAM: + break; + + case HPI_OBJ_CONTROLEX: + case HPI_OBJ_CONTROL: + if (phm->version == 1) + attrib = HPI_CTL_ATTR(UNIVERSAL, 1); + else + attrib = phm->u.c.attribute; + is_control = 1; + break; + default: + break; + } + + if (is_control && (attrib & 0xFF00)) { + int control_type = (attrib & 0xFF00) >> 8; + int attr_index = HPI_CTL_ATTR_INDEX(attrib); + /* note the KERN facility level + is in szFileline already */ + printk("%s adapter %d %s " + "ctrl_index x%04x %s %d\n", + sz_fileline, phm->adapter_index, + hpi_function_string(phm->function), + index, + get_treenode_elem + (&hpi_control_type_strings, + control_type, char *), + attr_index); + + } else + printk("%s adapter %d %s " + "idx x%04x attr x%04x \n", + sz_fileline, phm->adapter_index, + hpi_function_string(phm->function), + index, attrib); + } else { + printk("adap=%d, invalid obj=%d, func=0x%x\n", + phm->adapter_index, phm->object, + phm->function); + } + } else + printk(KERN_ERR + "NULL message pointer to hpi_debug_message!\n"); +} + +void hpi_debug_data(u16 *pdata, u32 len) +{ + u32 i; + int j; + int k; + int lines; + int cols = 8; + + lines = (len + cols - 1) / cols; + if (lines > 8) + lines = 8; + + for (i = 0, j = 0; j < lines; j++) { + printk(KERN_DEBUG "%p:", (pdata + i)); + + for (k = 0; k < cols && i < len; i++, k++) + printk("%s%04x", k == 0 ? "" : " ", pdata[i]); + + printk("\n"); + } +} diff --git a/sound/pci/asihpi/hpidebug.h b/sound/pci/asihpi/hpidebug.h new file mode 100644 index 00000000000..44dccadcc25 --- /dev/null +++ b/sound/pci/asihpi/hpidebug.h @@ -0,0 +1,385 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Debug macros. + +*****************************************************************************/ + +#ifndef _HPIDEBUG_H +#define _HPIDEBUG_H + +#include "hpi_internal.h" + +/* Define debugging levels. */ +enum { HPI_DEBUG_LEVEL_ERROR = 0, /* always log errors */ + HPI_DEBUG_LEVEL_WARNING = 1, + HPI_DEBUG_LEVEL_NOTICE = 2, + HPI_DEBUG_LEVEL_INFO = 3, + HPI_DEBUG_LEVEL_DEBUG = 4, + HPI_DEBUG_LEVEL_VERBOSE = 5 /* same printk level as DEBUG */ +}; + +#define HPI_DEBUG_LEVEL_DEFAULT HPI_DEBUG_LEVEL_NOTICE + +/* an OS can define an extra flag string that is appended to + the start of each message, eg see hpios_linux.h */ + +#ifdef SOURCEFILE_NAME +#define FILE_LINE SOURCEFILE_NAME ":" __stringify(__LINE__) " " +#else +#define FILE_LINE __FILE__ ":" __stringify(__LINE__) " " +#endif + +#if defined(HPI_DEBUG) && defined(_WINDOWS) +#define HPI_DEBUGBREAK() debug_break() +#else +#define HPI_DEBUGBREAK() +#endif + +#define HPI_DEBUG_ASSERT(expression) \ + do { \ + if (!(expression)) {\ + printk(KERN_ERR FILE_LINE\ + "ASSERT " __stringify(expression));\ + HPI_DEBUGBREAK();\ + } \ + } while (0) + +#define HPI_DEBUG_LOG(level, ...) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \ + printk(HPI_DEBUG_FLAG_##level \ + FILE_LINE __VA_ARGS__); \ + } \ + } while (0) + +void hpi_debug_init(void); +int hpi_debug_level_set(int level); +int hpi_debug_level_get(void); +/* needed by Linux driver for dynamic debug level changes */ +extern int hpi_debug_level; + +void hpi_debug_message(struct hpi_message *phm, char *sz_fileline); + +void hpi_debug_data(u16 *pdata, u32 len); + +#define HPI_DEBUG_DATA(pdata, len) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \ + hpi_debug_data(pdata, len); \ + } while (0) + +#define HPI_DEBUG_MESSAGE(level, phm) \ + do { \ + if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \ + hpi_debug_message(phm,HPI_DEBUG_FLAG_##level \ + FILE_LINE __stringify(level));\ + } \ + } while (0) + +#define HPI_DEBUG_RESPONSE(phr) \ + do { \ + if ((hpi_debug_level >= HPI_DEBUG_LEVEL_DEBUG) && (phr->error))\ + HPI_DEBUG_LOG(ERROR, \ + "HPI response - error# %d\n", \ + phr->error); \ + else if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \ + HPI_DEBUG_LOG(VERBOSE, "HPI response OK\n");\ + } while (0) + +#ifndef compile_time_assert +#define compile_time_assert(cond, msg) \ + typedef char msg[(cond) ? 1 : -1] +#endif + + /* check that size is exactly some number */ +#define function_count_check(sym, size) \ + compile_time_assert((sym##_FUNCTION_COUNT) == (size),\ + strings_match_defs_##sym) + +/* These strings should be generated using a macro which defines + the corresponding symbol values. */ +#define HPI_OBJ_STRINGS \ +{ \ + "HPI_OBJ_SUBSYSTEM", \ + "HPI_OBJ_ADAPTER", \ + "HPI_OBJ_OSTREAM", \ + "HPI_OBJ_ISTREAM", \ + "HPI_OBJ_MIXER", \ + "HPI_OBJ_NODE", \ + "HPI_OBJ_CONTROL", \ + "HPI_OBJ_NVMEMORY", \ + "HPI_OBJ_DIGITALIO", \ + "HPI_OBJ_WATCHDOG", \ + "HPI_OBJ_CLOCK", \ + "HPI_OBJ_PROFILE", \ + "HPI_OBJ_CONTROLEX" \ +} + +#define HPI_SUBSYS_STRINGS \ +{ \ + "HPI_SUBSYS_OPEN", \ + "HPI_SUBSYS_GET_VERSION", \ + "HPI_SUBSYS_GET_INFO", \ + "HPI_SUBSYS_FIND_ADAPTERS", \ + "HPI_SUBSYS_CREATE_ADAPTER",\ + "HPI_SUBSYS_CLOSE", \ + "HPI_SUBSYS_DELETE_ADAPTER", \ + "HPI_SUBSYS_DRIVER_LOAD", \ + "HPI_SUBSYS_DRIVER_UNLOAD", \ + "HPI_SUBSYS_READ_PORT_8", \ + "HPI_SUBSYS_WRITE_PORT_8", \ + "HPI_SUBSYS_GET_NUM_ADAPTERS",\ + "HPI_SUBSYS_GET_ADAPTER", \ + "HPI_SUBSYS_SET_NETWORK_INTERFACE"\ +} +function_count_check(HPI_SUBSYS, 14); + +#define HPI_ADAPTER_STRINGS \ +{ \ + "HPI_ADAPTER_OPEN", \ + "HPI_ADAPTER_CLOSE", \ + "HPI_ADAPTER_GET_INFO", \ + "HPI_ADAPTER_GET_ASSERT", \ + "HPI_ADAPTER_TEST_ASSERT", \ + "HPI_ADAPTER_SET_MODE", \ + "HPI_ADAPTER_GET_MODE", \ + "HPI_ADAPTER_ENABLE_CAPABILITY",\ + "HPI_ADAPTER_SELFTEST", \ + "HPI_ADAPTER_FIND_OBJECT", \ + "HPI_ADAPTER_QUERY_FLASH", \ + "HPI_ADAPTER_START_FLASH", \ + "HPI_ADAPTER_PROGRAM_FLASH", \ + "HPI_ADAPTER_SET_PROPERTY", \ + "HPI_ADAPTER_GET_PROPERTY", \ + "HPI_ADAPTER_ENUM_PROPERTY", \ + "HPI_ADAPTER_MODULE_INFO", \ + "HPI_ADAPTER_DEBUG_READ" \ +} + +function_count_check(HPI_ADAPTER, 18); + +#define HPI_OSTREAM_STRINGS \ +{ \ + "HPI_OSTREAM_OPEN", \ + "HPI_OSTREAM_CLOSE", \ + "HPI_OSTREAM_WRITE", \ + "HPI_OSTREAM_START", \ + "HPI_OSTREAM_STOP", \ + "HPI_OSTREAM_RESET", \ + "HPI_OSTREAM_GET_INFO", \ + "HPI_OSTREAM_QUERY_FORMAT", \ + "HPI_OSTREAM_DATA", \ + "HPI_OSTREAM_SET_VELOCITY", \ + "HPI_OSTREAM_SET_PUNCHINOUT", \ + "HPI_OSTREAM_SINEGEN", \ + "HPI_OSTREAM_ANC_RESET", \ + "HPI_OSTREAM_ANC_GET_INFO", \ + "HPI_OSTREAM_ANC_READ", \ + "HPI_OSTREAM_SET_TIMESCALE",\ + "HPI_OSTREAM_SET_FORMAT", \ + "HPI_OSTREAM_HOSTBUFFER_ALLOC", \ + "HPI_OSTREAM_HOSTBUFFER_FREE", \ + "HPI_OSTREAM_GROUP_ADD",\ + "HPI_OSTREAM_GROUP_GETMAP", \ + "HPI_OSTREAM_GROUP_RESET", \ + "HPI_OSTREAM_HOSTBUFFER_GET_INFO", \ + "HPI_OSTREAM_WAIT_START", \ +} +function_count_check(HPI_OSTREAM, 24); + +#define HPI_ISTREAM_STRINGS \ +{ \ + "HPI_ISTREAM_OPEN", \ + "HPI_ISTREAM_CLOSE", \ + "HPI_ISTREAM_SET_FORMAT", \ + "HPI_ISTREAM_READ", \ + "HPI_ISTREAM_START", \ + "HPI_ISTREAM_STOP", \ + "HPI_ISTREAM_RESET", \ + "HPI_ISTREAM_GET_INFO", \ + "HPI_ISTREAM_QUERY_FORMAT", \ + "HPI_ISTREAM_ANC_RESET", \ + "HPI_ISTREAM_ANC_GET_INFO", \ + "HPI_ISTREAM_ANC_WRITE", \ + "HPI_ISTREAM_HOSTBUFFER_ALLOC",\ + "HPI_ISTREAM_HOSTBUFFER_FREE", \ + "HPI_ISTREAM_GROUP_ADD", \ + "HPI_ISTREAM_GROUP_GETMAP", \ + "HPI_ISTREAM_GROUP_RESET", \ + "HPI_ISTREAM_HOSTBUFFER_GET_INFO", \ + "HPI_ISTREAM_WAIT_START", \ +} +function_count_check(HPI_ISTREAM, 19); + +#define HPI_MIXER_STRINGS \ +{ \ + "HPI_MIXER_OPEN", \ + "HPI_MIXER_CLOSE", \ + "HPI_MIXER_GET_INFO", \ + "HPI_MIXER_GET_NODE_INFO", \ + "HPI_MIXER_GET_CONTROL", \ + "HPI_MIXER_SET_CONNECTION", \ + "HPI_MIXER_GET_CONNECTIONS", \ + "HPI_MIXER_GET_CONTROL_BY_INDEX", \ + "HPI_MIXER_GET_CONTROL_ARRAY_BY_INDEX", \ + "HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES", \ + "HPI_MIXER_STORE", \ +} +function_count_check(HPI_MIXER, 11); + +#define HPI_CONTROL_STRINGS \ +{ \ + "HPI_CONTROL_GET_INFO", \ + "HPI_CONTROL_GET_STATE", \ + "HPI_CONTROL_SET_STATE" \ +} +function_count_check(HPI_CONTROL, 3); + +#define HPI_NVMEMORY_STRINGS \ +{ \ + "HPI_NVMEMORY_OPEN", \ + "HPI_NVMEMORY_READ_BYTE", \ + "HPI_NVMEMORY_WRITE_BYTE" \ +} +function_count_check(HPI_NVMEMORY, 3); + +#define HPI_DIGITALIO_STRINGS \ +{ \ + "HPI_GPIO_OPEN", \ + "HPI_GPIO_READ_BIT", \ + "HPI_GPIO_WRITE_BIT", \ + "HPI_GPIO_READ_ALL", \ + "HPI_GPIO_WRITE_STATUS"\ +} +function_count_check(HPI_GPIO, 5); + +#define HPI_WATCHDOG_STRINGS \ +{ \ + "HPI_WATCHDOG_OPEN", \ + "HPI_WATCHDOG_SET_TIME", \ + "HPI_WATCHDOG_PING" \ +} + +#define HPI_CLOCK_STRINGS \ +{ \ + "HPI_CLOCK_OPEN", \ + "HPI_CLOCK_SET_TIME", \ + "HPI_CLOCK_GET_TIME" \ +} + +#define HPI_PROFILE_STRINGS \ +{ \ + "HPI_PROFILE_OPEN_ALL", \ + "HPI_PROFILE_START_ALL", \ + "HPI_PROFILE_STOP_ALL", \ + "HPI_PROFILE_GET", \ + "HPI_PROFILE_GET_IDLECOUNT", \ + "HPI_PROFILE_GET_NAME", \ + "HPI_PROFILE_GET_UTILIZATION" \ +} +function_count_check(HPI_PROFILE, 7); + +#define HPI_ASYNCEVENT_STRINGS \ +{ \ + "HPI_ASYNCEVENT_OPEN",\ + "HPI_ASYNCEVENT_CLOSE ",\ + "HPI_ASYNCEVENT_WAIT",\ + "HPI_ASYNCEVENT_GETCOUNT",\ + "HPI_ASYNCEVENT_GET",\ + "HPI_ASYNCEVENT_SENDEVENTS"\ +} +function_count_check(HPI_ASYNCEVENT, 6); + +#define HPI_CONTROL_TYPE_STRINGS \ +{ \ + "null control", \ + "HPI_CONTROL_CONNECTION", \ + "HPI_CONTROL_VOLUME", \ + "HPI_CONTROL_METER", \ + "HPI_CONTROL_MUTE", \ + "HPI_CONTROL_MULTIPLEXER", \ + "HPI_CONTROL_AESEBU_TRANSMITTER", \ + "HPI_CONTROL_AESEBU_RECEIVER", \ + "HPI_CONTROL_LEVEL", \ + "HPI_CONTROL_TUNER", \ + "HPI_CONTROL_ONOFFSWITCH", \ + "HPI_CONTROL_VOX", \ + "HPI_CONTROL_AES18_TRANSMITTER", \ + "HPI_CONTROL_AES18_RECEIVER", \ + "HPI_CONTROL_AES18_BLOCKGENERATOR", \ + "HPI_CONTROL_CHANNEL_MODE", \ + "HPI_CONTROL_BITSTREAM", \ + "HPI_CONTROL_SAMPLECLOCK", \ + "HPI_CONTROL_MICROPHONE", \ + "HPI_CONTROL_PARAMETRIC_EQ", \ + "HPI_CONTROL_COMPANDER", \ + "HPI_CONTROL_COBRANET", \ + "HPI_CONTROL_TONE_DETECT", \ + "HPI_CONTROL_SILENCE_DETECT", \ + "HPI_CONTROL_PAD", \ + "HPI_CONTROL_SRC" ,\ + "HPI_CONTROL_UNIVERSAL" \ +} + +compile_time_assert((HPI_CONTROL_LAST_INDEX + 1 == 27), + controltype_strings_match_defs); + +#define HPI_SOURCENODE_STRINGS \ +{ \ + "no source", \ + "HPI_SOURCENODE_OSTREAM", \ + "HPI_SOURCENODE_LINEIN", \ + "HPI_SOURCENODE_AESEBU_IN", \ + "HPI_SOURCENODE_TUNER", \ + "HPI_SOURCENODE_RF", \ + "HPI_SOURCENODE_CLOCK_SOURCE", \ + "HPI_SOURCENODE_RAW_BITSTREAM", \ + "HPI_SOURCENODE_MICROPHONE", \ + "HPI_SOURCENODE_COBRANET", \ + "HPI_SOURCENODE_ANALOG", \ + "HPI_SOURCENODE_ADAPTER" \ +} + +compile_time_assert((HPI_SOURCENODE_LAST_INDEX - HPI_SOURCENODE_BASE + 1) == + (12), sourcenode_strings_match_defs); + +#define HPI_DESTNODE_STRINGS \ +{ \ + "no destination", \ + "HPI_DESTNODE_ISTREAM", \ + "HPI_DESTNODE_LINEOUT", \ + "HPI_DESTNODE_AESEBU_OUT", \ + "HPI_DESTNODE_RF", \ + "HPI_DESTNODE_SPEAKER", \ + "HPI_DESTNODE_COBRANET", \ + "HPI_DESTNODE_ANALOG" \ +} +compile_time_assert((HPI_DESTNODE_LAST_INDEX - HPI_DESTNODE_BASE + 1) == (8), + destnode_strings_match_defs); + +#define HPI_CONTROL_CHANNEL_MODE_STRINGS \ +{ \ + "XXX HPI_CHANNEL_MODE_ERROR XXX", \ + "HPI_CHANNEL_MODE_NORMAL", \ + "HPI_CHANNEL_MODE_SWAP", \ + "HPI_CHANNEL_MODE_LEFT_ONLY", \ + "HPI_CHANNEL_MODE_RIGHT_ONLY" \ +} + +#endif /* _HPIDEBUG_H */ diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c new file mode 100644 index 00000000000..9b10d9a5c25 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.c @@ -0,0 +1,172 @@ +/***********************************************************************/ +/*! + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +\file +Functions for reading DSP code to load into DSP + +(Linux only:) If DSPCODE_FIRMWARE_LOADER is defined, code is read using +hotplug firmware loader from individual dsp code files + +If neither of the above is defined, code is read from linked arrays. +DSPCODE_ARRAY is defined. + +HPI_INCLUDE_**** must be defined +and the appropriate hzz?????.c or hex?????.c linked in + + */ +/***********************************************************************/ +#define SOURCEFILE_NAME "hpidspcd.c" +#include "hpidspcd.h" +#include "hpidebug.h" + +/** + Header structure for binary dsp code file (see asidsp.doc) + This structure must match that used in s2bin.c for generation of asidsp.bin + */ + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +struct code_header { + u32 size; + char type[4]; + u32 adapter; + u32 version; + u32 crc; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +#define HPI_VER_DECIMAL ((int)(HPI_VER_MAJOR(HPI_VER) * 10000 + \ + HPI_VER_MINOR(HPI_VER) * 100 + HPI_VER_RELEASE(HPI_VER))) + +/***********************************************************************/ +#include "linux/pci.h" +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_open(u32 adapter, struct dsp_code *ps_dsp_code, + u32 *pos_error_code) +{ + const struct firmware *ps_firmware = ps_dsp_code->ps_firmware; + struct code_header header; + char fw_name[20]; + int err; + + sprintf(fw_name, "asihpi/dsp%04x.bin", adapter); + HPI_DEBUG_LOG(INFO, "requesting firmware for %s\n", fw_name); + + err = request_firmware(&ps_firmware, fw_name, + &ps_dsp_code->ps_dev->dev); + if (err != 0) { + HPI_DEBUG_LOG(ERROR, "%d, request_firmware failed for %s\n", + err, fw_name); + goto error1; + } + if (ps_firmware->size < sizeof(header)) { + HPI_DEBUG_LOG(ERROR, "header size too small %s\n", fw_name); + goto error2; + } + memcpy(&header, ps_firmware->data, sizeof(header)); + if (header.adapter != adapter) { + HPI_DEBUG_LOG(ERROR, "adapter type incorrect %4x != %4x\n", + header.adapter, adapter); + goto error2; + } + if (header.size != ps_firmware->size) { + HPI_DEBUG_LOG(ERROR, "code size wrong %d != %ld\n", + header.size, (unsigned long)ps_firmware->size); + goto error2; + } + + if (header.version / 10000 != HPI_VER_DECIMAL / 10000) { + HPI_DEBUG_LOG(ERROR, + "firmware major version mismatch " + "DSP image %d != driver %d\n", header.version, + HPI_VER_DECIMAL); + goto error2; + } + + if (header.version != HPI_VER_DECIMAL) { + HPI_DEBUG_LOG(WARNING, + "version mismatch DSP image %d != driver %d\n", + header.version, HPI_VER_DECIMAL); + /* goto error2; still allow driver to load */ + } + + HPI_DEBUG_LOG(INFO, "dsp code %s opened\n", fw_name); + ps_dsp_code->ps_firmware = ps_firmware; + ps_dsp_code->block_length = header.size / sizeof(u32); + ps_dsp_code->word_count = sizeof(header) / sizeof(u32); + ps_dsp_code->version = header.version; + ps_dsp_code->crc = header.crc; + return 0; + +error2: + release_firmware(ps_firmware); +error1: + ps_dsp_code->ps_firmware = NULL; + ps_dsp_code->block_length = 0; + return HPI_ERROR_DSP_FILE_NOT_FOUND; +} + +/*-------------------------------------------------------------------*/ +void hpi_dsp_code_close(struct dsp_code *ps_dsp_code) +{ + if (ps_dsp_code->ps_firmware != NULL) { + HPI_DEBUG_LOG(DEBUG, "dsp code closed\n"); + release_firmware(ps_dsp_code->ps_firmware); + ps_dsp_code->ps_firmware = NULL; + } +} + +/*-------------------------------------------------------------------*/ +void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code) +{ + /* Go back to start of data, after header */ + ps_dsp_code->word_count = sizeof(struct code_header) / sizeof(u32); +} + +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code, u32 *pword) +{ + if (ps_dsp_code->word_count + 1 > ps_dsp_code->block_length) + return (HPI_ERROR_DSP_FILE_FORMAT); + + *pword = ((u32 *)(ps_dsp_code->ps_firmware->data))[ps_dsp_code-> + word_count]; + ps_dsp_code->word_count++; + return 0; +} + +/*-------------------------------------------------------------------*/ +short hpi_dsp_code_read_block(size_t words_requested, + struct dsp_code *ps_dsp_code, u32 **ppblock) +{ + if (ps_dsp_code->word_count + words_requested > + ps_dsp_code->block_length) + return HPI_ERROR_DSP_FILE_FORMAT; + + *ppblock = + ((u32 *)(ps_dsp_code->ps_firmware->data)) + + ps_dsp_code->word_count; + ps_dsp_code->word_count += words_requested; + return 0; +} diff --git a/sound/pci/asihpi/hpidspcd.h b/sound/pci/asihpi/hpidspcd.h new file mode 100644 index 00000000000..d7c24039822 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.h @@ -0,0 +1,104 @@ +/***********************************************************************/ +/** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +\file +Functions for reading DSP code to load into DSP + + hpi_dspcode_defines HPI DSP code loading method +Define exactly one of these to select how the DSP code is supplied to +the adapter. + +End users writing applications that use the HPI interface do not have to +use any of the below defines; they are only necessary for building drivers + +HPI_DSPCODE_FILE: +DSP code is supplied as a file that is opened and read from by the driver. + +HPI_DSPCODE_FIRMWARE: +DSP code is read using the hotplug firmware loader module. + Only valid when compiling the HPI kernel driver under Linux. +*/ +/***********************************************************************/ +#ifndef _HPIDSPCD_H_ +#define _HPIDSPCD_H_ + +#include "hpi_internal.h" + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** Descriptor for dspcode from firmware loader */ +struct dsp_code { + /** Firmware descriptor */ + const struct firmware *ps_firmware; + struct pci_dev *ps_dev; + /** Expected number of words in the whole dsp code,INCL header */ + long int block_length; + /** Number of words read so far */ + long int word_count; + /** Version read from dsp code file */ + u32 version; + /** CRC read from dsp code file */ + u32 crc; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/** Prepare *psDspCode to refer to the requuested adapter. + Searches the file, or selects the appropriate linked array + +\return 0 for success, or error code if requested code is not available +*/ +short hpi_dsp_code_open( + /** Code identifier, usually adapter family */ + u32 adapter, + /** Pointer to DSP code control structure */ + struct dsp_code *ps_dsp_code, + /** Pointer to dword to receive OS specific error code */ + u32 *pos_error_code); + +/** Close the DSP code file */ +void hpi_dsp_code_close(struct dsp_code *ps_dsp_code); + +/** Rewind to the beginning of the DSP code file (for verify) */ +void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code); + +/** Read one word from the dsp code file + \return 0 for success, or error code if eof, or block length exceeded +*/ +short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code, + /**< DSP code descriptor */ + u32 *pword /**< where to store the read word */ + ); + +/** Get a block of dsp code into an internal buffer, and provide a pointer to +that buffer. (If dsp code is already an array in memory, it is referenced, +not copied.) + +\return Error if requested number of words are not available +*/ +short hpi_dsp_code_read_block(size_t words_requested, + struct dsp_code *ps_dsp_code, + /* Pointer to store (Pointer to code buffer) */ + u32 **ppblock); + +#endif diff --git a/sound/pci/asihpi/hpifunc.c b/sound/pci/asihpi/hpifunc.c new file mode 100644 index 00000000000..254c580db63 --- /dev/null +++ b/sound/pci/asihpi/hpifunc.c @@ -0,0 +1,3864 @@ + +#include "hpi_internal.h" +#include "hpimsginit.h" + +#include "hpidebug.h" + +struct hpi_handle { + unsigned int obj_index:12; + unsigned int obj_type:4; + unsigned int adapter_index:14; + unsigned int spare:1; + unsigned int read_only:1; +}; + +union handle_word { + struct hpi_handle h; + u32 w; +}; + +u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index, + const u16 object_index) +{ + union handle_word handle; + + handle.h.adapter_index = adapter_index; + handle.h.spare = 0; + handle.h.read_only = 0; + handle.h.obj_type = c_object; + handle.h.obj_index = object_index; + return handle.w; +} + +void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index, + u16 *pw_object_index) +{ + union handle_word uhandle; + uhandle.w = handle; + + if (pw_adapter_index) + *pw_adapter_index = (u16)uhandle.h.adapter_index; + if (pw_object_index) + *pw_object_index = (u16)uhandle.h.obj_index; +} + +char hpi_handle_object(const u32 handle) +{ + union handle_word uhandle; + uhandle.w = handle; + return (char)uhandle.h.obj_type; +} + +#define u32TOINDEX(h, i1) \ +do {\ + if (h == 0) \ + return HPI_ERROR_INVALID_OBJ; \ + else \ + hpi_handle_to_indexes(h, i1, NULL); \ +} while (0) + +#define u32TOINDEXES(h, i1, i2) \ +do {\ + if (h == 0) \ + return HPI_ERROR_INVALID_OBJ; \ + else \ + hpi_handle_to_indexes(h, i1, i2);\ +} while (0) + +void hpi_format_to_msg(struct hpi_msg_format *pMF, + const struct hpi_format *pF) +{ + pMF->sample_rate = pF->sample_rate; + pMF->bit_rate = pF->bit_rate; + pMF->attributes = pF->attributes; + pMF->channels = pF->channels; + pMF->format = pF->format; +} + +static void hpi_msg_to_format(struct hpi_format *pF, + struct hpi_msg_format *pMF) +{ + pF->sample_rate = pMF->sample_rate; + pF->bit_rate = pMF->bit_rate; + pF->attributes = pMF->attributes; + pF->channels = pMF->channels; + pF->format = pMF->format; + pF->mode_legacy = 0; + pF->unused = 0; +} + +void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR) +{ + pSR->u.legacy_stream_info.auxiliary_data_available = + pSR->u.stream_info.auxiliary_data_available; + pSR->u.legacy_stream_info.state = pSR->u.stream_info.state; +} + +static struct hpi_hsubsys gh_subsys; + +struct hpi_hsubsys *hpi_subsys_create(void + ) +{ + struct hpi_message hm; + struct hpi_response hr; + + memset(&gh_subsys, 0, sizeof(struct hpi_hsubsys)); + + { + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_OPEN); + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + return &gh_subsys; + + } + return NULL; +} + +void hpi_subsys_free(const struct hpi_hsubsys *ph_subsys) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CLOSE); + hpi_send_recv(&hm, &hr); + +} + +u16 hpi_subsys_get_version(const struct hpi_hsubsys *ph_subsys, u32 *pversion) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_VERSION); + hpi_send_recv(&hm, &hr); + *pversion = hr.u.s.version; + return hr.error; +} + +u16 hpi_subsys_get_version_ex(const struct hpi_hsubsys *ph_subsys, + u32 *pversion_ex) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_VERSION); + hpi_send_recv(&hm, &hr); + *pversion_ex = hr.u.s.data; + return hr.error; +} + +u16 hpi_subsys_get_info(const struct hpi_hsubsys *ph_subsys, u32 *pversion, + u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_INFO); + + hpi_send_recv(&hm, &hr); + + *pversion = hr.u.s.version; + if (list_length > HPI_MAX_ADAPTERS) + memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list, + HPI_MAX_ADAPTERS); + else + memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list, list_length); + *pw_num_adapters = hr.u.s.num_adapters; + return hr.error; +} + +u16 hpi_subsys_find_adapters(const struct hpi_hsubsys *ph_subsys, + u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_FIND_ADAPTERS); + + hpi_send_recv(&hm, &hr); + + if (list_length > HPI_MAX_ADAPTERS) { + memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list, + HPI_MAX_ADAPTERS * sizeof(u16)); + memset(&aw_adapter_list[HPI_MAX_ADAPTERS], 0, + (list_length - HPI_MAX_ADAPTERS) * sizeof(u16)); + } else + memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list, + list_length * sizeof(u16)); + *pw_num_adapters = hr.u.s.num_adapters; + + return hr.error; +} + +u16 hpi_subsys_create_adapter(const struct hpi_hsubsys *ph_subsys, + const struct hpi_resource *p_resource, u16 *pw_adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CREATE_ADAPTER); + hm.u.s.resource = *p_resource; + + hpi_send_recv(&hm, &hr); + + *pw_adapter_index = hr.u.s.adapter_index; + return hr.error; +} + +u16 hpi_subsys_delete_adapter(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DELETE_ADAPTER); + hm.adapter_index = adapter_index; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_subsys_get_num_adapters(const struct hpi_hsubsys *ph_subsys, + int *pn_num_adapters) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_NUM_ADAPTERS); + hpi_send_recv(&hm, &hr); + *pn_num_adapters = (int)hr.u.s.num_adapters; + return hr.error; +} + +u16 hpi_subsys_get_adapter(const struct hpi_hsubsys *ph_subsys, int iterator, + u32 *padapter_index, u16 *pw_adapter_type) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_ADAPTER); + hm.adapter_index = (u16)iterator; + hpi_send_recv(&hm, &hr); + *padapter_index = (int)hr.u.s.adapter_index; + *pw_adapter_type = hr.u.s.aw_adapter_list[0]; + return hr.error; +} + +u16 hpi_subsys_set_host_network_interface(const struct hpi_hsubsys *ph_subsys, + const char *sz_interface) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_SET_NETWORK_INTERFACE); + if (sz_interface == NULL) + return HPI_ERROR_INVALID_RESOURCE; + hm.u.s.resource.r.net_if = sz_interface; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_adapter_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + return hr.error; + +} + +u16 hpi_adapter_close(const struct hpi_hsubsys *ph_subsys, u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_CLOSE); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_set_mode(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 adapter_mode) +{ + return hpi_adapter_set_mode_ex(ph_subsys, adapter_index, adapter_mode, + HPI_ADAPTER_MODE_SET); +} + +u16 hpi_adapter_set_mode_ex(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 adapter_mode, u16 query_or_set) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_MODE); + hm.adapter_index = adapter_index; + hm.u.a.adapter_mode = adapter_mode; + hm.u.a.assert_id = query_or_set; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_adapter_get_mode(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 *padapter_mode) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_MODE); + hm.adapter_index = adapter_index; + hpi_send_recv(&hm, &hr); + if (padapter_mode) + *padapter_mode = hr.u.a.serial_number; + return hr.error; +} + +u16 hpi_adapter_get_info(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *pw_num_outstreams, u16 *pw_num_instreams, + u16 *pw_version, u32 *pserial_number, u16 *pw_adapter_type) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_INFO); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + *pw_adapter_type = hr.u.a.adapter_type; + *pw_num_outstreams = hr.u.a.num_outstreams; + *pw_num_instreams = hr.u.a.num_instreams; + *pw_version = hr.u.a.version; + *pserial_number = hr.u.a.serial_number; + return hr.error; +} + +u16 hpi_adapter_get_module_by_index(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 module_index, u16 *pw_num_outputs, + u16 *pw_num_inputs, u16 *pw_version, u32 *pserial_number, + u16 *pw_module_type, u32 *ph_module) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_MODULE_INFO); + hm.adapter_index = adapter_index; + hm.u.ax.module_info.index = module_index; + + hpi_send_recv(&hm, &hr); + + *pw_module_type = hr.u.a.adapter_type; + *pw_num_outputs = hr.u.a.num_outstreams; + *pw_num_inputs = hr.u.a.num_instreams; + *pw_version = hr.u.a.version; + *pserial_number = hr.u.a.serial_number; + *ph_module = 0; + + return hr.error; +} + +u16 hpi_adapter_get_assert(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *assert_present, char *psz_assert, + u16 *pw_line_number) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_ASSERT); + hm.adapter_index = adapter_index; + hpi_send_recv(&hm, &hr); + + *assert_present = 0; + + if (!hr.error) { + + *pw_line_number = (u16)hr.u.a.serial_number; + if (*pw_line_number) { + + int i; + char *src = (char *)hr.u.a.sz_adapter_assert; + char *dst = psz_assert; + + *assert_present = 1; + + for (i = 0; i < HPI_STRING_LEN; i++) { + char c; + c = *src++; + *dst++ = c; + if (c == 0) + break; + } + + } + } + return hr.error; +} + +u16 hpi_adapter_get_assert_ex(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 *assert_present, char *psz_assert, + u32 *pline_number, u16 *pw_assert_on_dsp) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_ASSERT); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + *assert_present = 0; + + if (!hr.error) { + + *pline_number = hr.u.a.serial_number; + + *assert_present = hr.u.a.adapter_type; + + *pw_assert_on_dsp = hr.u.a.adapter_index; + + if (!*assert_present && *pline_number) + + *assert_present = 1; + + if (*assert_present) { + + int i; + char *src = (char *)hr.u.a.sz_adapter_assert; + char *dst = psz_assert; + + for (i = 0; i < HPI_STRING_LEN; i++) { + char c; + c = *src++; + *dst++ = c; + if (c == 0) + break; + } + + } else { + *psz_assert = 0; + } + } + return hr.error; +} + +u16 hpi_adapter_test_assert(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 assert_id) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_TEST_ASSERT); + hm.adapter_index = adapter_index; + hm.u.a.assert_id = assert_id; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_enable_capability(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 capability, u32 key) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_ENABLE_CAPABILITY); + hm.adapter_index = adapter_index; + hm.u.a.assert_id = capability; + hm.u.a.adapter_mode = key; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_self_test(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SELFTEST); + hm.adapter_index = adapter_index; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_adapter_debug_read(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 dsp_address, char *p_buffer, int *count_bytes) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_DEBUG_READ); + + hr.size = sizeof(hr); + + hm.adapter_index = adapter_index; + hm.u.ax.debug_read.dsp_address = dsp_address; + + if (*count_bytes > sizeof(hr.u.bytes)) + *count_bytes = sizeof(hr.u.bytes); + + hm.u.ax.debug_read.count_bytes = *count_bytes; + + hpi_send_recv(&hm, &hr); + + if (!hr.error) { + *count_bytes = hr.size - 12; + memcpy(p_buffer, &hr.u.bytes, *count_bytes); + } else + *count_bytes = 0; + return hr.error; +} + +u16 hpi_adapter_set_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 property, u16 parameter1, u16 parameter2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_SET_PROPERTY); + hm.adapter_index = adapter_index; + hm.u.ax.property_set.property = property; + hm.u.ax.property_set.parameter1 = parameter1; + hm.u.ax.property_set.parameter2 = parameter2; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_adapter_get_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 property, u16 *pw_parameter1, + u16 *pw_parameter2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_PROPERTY); + hm.adapter_index = adapter_index; + hm.u.ax.property_set.property = property; + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + if (pw_parameter1) + *pw_parameter1 = hr.u.ax.property_get.parameter1; + if (pw_parameter2) + *pw_parameter2 = hr.u.ax.property_get.parameter2; + } + + return hr.error; +} + +u16 hpi_adapter_enumerate_property(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 index, u16 what_to_enumerate, + u16 property_index, u32 *psetting) +{ + return 0; +} + +u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format, + u32 sample_rate, u32 bit_rate, u32 attributes) +{ + u16 error = 0; + struct hpi_msg_format fmt; + + switch (channels) { + case 1: + case 2: + case 4: + case 6: + case 8: + case 16: + break; + default: + error = HPI_ERROR_INVALID_CHANNELS; + return error; + } + fmt.channels = channels; + + switch (format) { + case HPI_FORMAT_PCM16_SIGNED: + case HPI_FORMAT_PCM24_SIGNED: + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + case HPI_FORMAT_PCM16_BIGENDIAN: + case HPI_FORMAT_PCM8_UNSIGNED: + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + case HPI_FORMAT_DOLBY_AC2: + case HPI_FORMAT_AA_TAGIT1_HITS: + case HPI_FORMAT_AA_TAGIT1_INSERTS: + case HPI_FORMAT_RAW_BITSTREAM: + case HPI_FORMAT_AA_TAGIT1_HITS_EX1: + case HPI_FORMAT_OEM1: + case HPI_FORMAT_OEM2: + break; + default: + error = HPI_ERROR_INVALID_FORMAT; + return error; + } + fmt.format = format; + + if (sample_rate < 8000L) { + error = HPI_ERROR_INCOMPATIBLE_SAMPLERATE; + sample_rate = 8000L; + } + if (sample_rate > 200000L) { + error = HPI_ERROR_INCOMPATIBLE_SAMPLERATE; + sample_rate = 200000L; + } + fmt.sample_rate = sample_rate; + + switch (format) { + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + fmt.bit_rate = bit_rate; + break; + case HPI_FORMAT_PCM16_SIGNED: + case HPI_FORMAT_PCM16_BIGENDIAN: + fmt.bit_rate = channels * sample_rate * 2; + break; + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + fmt.bit_rate = channels * sample_rate * 4; + break; + case HPI_FORMAT_PCM8_UNSIGNED: + fmt.bit_rate = channels * sample_rate; + break; + default: + fmt.bit_rate = 0; + } + + switch (format) { + case HPI_FORMAT_MPEG_L2: + if ((channels == 1) + && (attributes != HPI_MPEG_MODE_DEFAULT)) { + attributes = HPI_MPEG_MODE_DEFAULT; + error = HPI_ERROR_INVALID_FORMAT; + } else if (attributes > HPI_MPEG_MODE_DUALCHANNEL) { + attributes = HPI_MPEG_MODE_DEFAULT; + error = HPI_ERROR_INVALID_FORMAT; + } + fmt.attributes = attributes; + break; + default: + fmt.attributes = attributes; + } + + hpi_msg_to_format(p_format, &fmt); + return error; +} + +u16 hpi_stream_estimate_buffer_size(struct hpi_format *p_format, + u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size) +{ + + u32 bytes_per_second; + u32 size; + u16 channels; + struct hpi_format *pF = p_format; + + channels = pF->channels; + + switch (pF->format) { + case HPI_FORMAT_PCM16_BIGENDIAN: + case HPI_FORMAT_PCM16_SIGNED: + bytes_per_second = pF->sample_rate * 2L * channels; + break; + case HPI_FORMAT_PCM24_SIGNED: + bytes_per_second = pF->sample_rate * 3L * channels; + break; + case HPI_FORMAT_PCM32_SIGNED: + case HPI_FORMAT_PCM32_FLOAT: + bytes_per_second = pF->sample_rate * 4L * channels; + break; + case HPI_FORMAT_PCM8_UNSIGNED: + bytes_per_second = pF->sample_rate * 1L * channels; + break; + case HPI_FORMAT_MPEG_L1: + case HPI_FORMAT_MPEG_L2: + case HPI_FORMAT_MPEG_L3: + bytes_per_second = pF->bit_rate / 8L; + break; + case HPI_FORMAT_DOLBY_AC2: + + bytes_per_second = 256000L / 8L; + break; + default: + return HPI_ERROR_INVALID_FORMAT; + } + size = (bytes_per_second * host_polling_rate_in_milli_seconds * 2) / + 1000L; + + *recommended_buffer_size = + roundup_pow_of_two(((size + 4095L) & ~4095L)); + return 0; +} + +u16 hpi_outstream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u16 outstream_index, u32 *ph_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN); + hm.adapter_index = adapter_index; + hm.obj_index = outstream_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_outstream = + hpi_indexes_to_handle(HPI_OBJ_OSTREAM, adapter_index, + outstream_index); + else + *ph_outstream = 0; + return hr.error; +} + +u16 hpi_outstream_close(const struct hpi_hsubsys *ph_subsys, u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_RESET); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_CLOSE); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_get_info_ex(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_to_play, + u32 *psamples_played, u32 *pauxiliary_data_to_play) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GET_INFO); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + if (pw_state) + *pw_state = hr.u.d.u.stream_info.state; + if (pbuffer_size) + *pbuffer_size = hr.u.d.u.stream_info.buffer_size; + if (pdata_to_play) + *pdata_to_play = hr.u.d.u.stream_info.data_available; + if (psamples_played) + *psamples_played = hr.u.d.u.stream_info.samples_transferred; + if (pauxiliary_data_to_play) + *pauxiliary_data_to_play = + hr.u.d.u.stream_info.auxiliary_data_available; + return hr.error; +} + +u16 hpi_outstream_write_buf(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, const u8 *pb_data, u32 bytes_to_write, + const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_WRITE); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.pb_data = (u8 *)pb_data; + hm.u.d.u.data.data_size = bytes_to_write; + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_start(const struct hpi_hsubsys *ph_subsys, u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_START); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_wait_start(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_WAIT_START); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_STOP); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_sinegen(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SINEGEN); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_query_format(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_QUERY_FORMAT); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_FORMAT); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_velocity(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, short velocity) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_VELOCITY); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.velocity = velocity; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_set_punch_in_out(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 punch_in_sample, u32 punch_out_sample) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_PUNCHINOUT); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hm.u.d.u.pio.punch_in_sample = punch_in_sample; + hm.u.d.u.pio.punch_out_sample = punch_out_sample; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_ancillary_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u16 mode) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_RESET); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.format.channels = mode; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 *pframes_available) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_GET_INFO); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + if (hr.error == 0) { + if (pframes_available) + *pframes_available = + hr.u.d.u.stream_info.data_available / + sizeof(struct hpi_anc_frame); + } + return hr.error; +} + +u16 hpi_outstream_ancillary_read(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_read) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_ANC_READ); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer; + hm.u.d.u.data.data_size = + number_of_ancillary_frames_to_read * + sizeof(struct hpi_anc_frame); + if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes) + hpi_send_recv(&hm, &hr); + else + hr.error = HPI_ERROR_INVALID_DATA_TRANSFER; + return hr.error; +} + +u16 hpi_outstream_set_time_scale(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 time_scale) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_SET_TIMESCALE); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + + hm.u.d.u.time_scale = time_scale; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_outstream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 size_in_bytes) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_ALLOC); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.data_size = size_in_bytes; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_GET_INFO); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + if (pp_buffer) + *pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer; + if (pp_status) + *pp_status = hr.u.d.u.hostbuffer_info.p_status; + } + return hr.error; +} + +u16 hpi_outstream_host_buffer_free(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_HOSTBUFFER_FREE); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_group_add(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 h_stream) +{ + struct hpi_message hm; + struct hpi_response hr; + u16 adapter; + char c_obj_type; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_ADD); + hr.error = 0; + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + c_obj_type = hpi_handle_object(h_stream); + switch (c_obj_type) { + case HPI_OBJ_OSTREAM: + hm.u.d.u.stream.object_type = HPI_OBJ_OSTREAM; + u32TOINDEXES(h_stream, &adapter, + &hm.u.d.u.stream.stream_index); + break; + case HPI_OBJ_ISTREAM: + hm.u.d.u.stream.object_type = HPI_OBJ_ISTREAM; + u32TOINDEXES(h_stream, &adapter, + &hm.u.d.u.stream.stream_index); + break; + default: + return HPI_ERROR_INVALID_STREAM; + } + if (adapter != hm.adapter_index) + return HPI_ERROR_NO_INTERADAPTER_GROUPS; + + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_outstream_group_get_map(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream, u32 *poutstream_map, u32 *pinstream_map) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_GETMAP); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + if (poutstream_map) + *poutstream_map = hr.u.d.u.group_info.outstream_group_map; + if (pinstream_map) + *pinstream_map = hr.u.d.u.group_info.instream_group_map; + + return hr.error; +} + +u16 hpi_outstream_group_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_outstream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_GROUP_RESET); + u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u16 instream_index, u32 *ph_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN); + hm.adapter_index = adapter_index; + hm.obj_index = instream_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_instream = + hpi_indexes_to_handle(HPI_OBJ_ISTREAM, adapter_index, + instream_index); + else + *ph_instream = 0; + + return hr.error; +} + +u16 hpi_instream_close(const struct hpi_hsubsys *ph_subsys, u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_RESET); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_CLOSE); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_query_format(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_QUERY_FORMAT); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_format *p_format) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_SET_FORMAT); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_format_to_msg(&hm.u.d.u.data.format, p_format); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_read_buf(const struct hpi_hsubsys *ph_subsys, u32 h_instream, + u8 *pb_data, u32 bytes_to_read) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_READ); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.data_size = bytes_to_read; + hm.u.d.u.data.pb_data = pb_data; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_start(const struct hpi_hsubsys *ph_subsys, u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_START); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_wait_start(const struct hpi_hsubsys *ph_subsys, + u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_WAIT_START); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_STOP); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_instream_get_info_ex(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_recorded, + u32 *psamples_recorded, u32 *pauxiliary_data_recorded) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GET_INFO); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + + hpi_send_recv(&hm, &hr); + + if (pw_state) + *pw_state = hr.u.d.u.stream_info.state; + if (pbuffer_size) + *pbuffer_size = hr.u.d.u.stream_info.buffer_size; + if (pdata_recorded) + *pdata_recorded = hr.u.d.u.stream_info.data_available; + if (psamples_recorded) + *psamples_recorded = hr.u.d.u.stream_info.samples_transferred; + if (pauxiliary_data_recorded) + *pauxiliary_data_recorded = + hr.u.d.u.stream_info.auxiliary_data_available; + return hr.error; +} + +u16 hpi_instream_ancillary_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u16 bytes_per_frame, u16 mode, u16 alignment, + u16 idle_bit) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_RESET); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.format.attributes = bytes_per_frame; + hm.u.d.u.data.format.format = (mode << 8) | (alignment & 0xff); + hm.u.d.u.data.format.channels = idle_bit; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 *pframe_space) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_GET_INFO); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + if (pframe_space) + *pframe_space = + (hr.u.d.u.stream_info.buffer_size - + hr.u.d.u.stream_info.data_available) / + sizeof(struct hpi_anc_frame); + return hr.error; +} + +u16 hpi_instream_ancillary_write(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, const struct hpi_anc_frame *p_anc_frame_buffer, + u32 anc_frame_buffer_size_in_bytes, + u32 number_of_ancillary_frames_to_write) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_ANC_WRITE); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer; + hm.u.d.u.data.data_size = + number_of_ancillary_frames_to_write * + sizeof(struct hpi_anc_frame); + if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes) + hpi_send_recv(&hm, &hr); + else + hr.error = HPI_ERROR_INVALID_DATA_TRANSFER; + return hr.error; +} + +u16 hpi_instream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 size_in_bytes) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_ALLOC); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hm.u.d.u.data.data_size = size_in_bytes; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u8 **pp_buffer, + struct hpi_hostbuffer_status **pp_status) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_GET_INFO); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + if (pp_buffer) + *pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer; + if (pp_status) + *pp_status = hr.u.d.u.hostbuffer_info.p_status; + } + return hr.error; +} + +u16 hpi_instream_host_buffer_free(const struct hpi_hsubsys *ph_subsys, + u32 h_instream) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_group_add(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 h_stream) +{ + struct hpi_message hm; + struct hpi_response hr; + u16 adapter; + char c_obj_type; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_ADD); + hr.error = 0; + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + c_obj_type = hpi_handle_object(h_stream); + + switch (c_obj_type) { + case HPI_OBJ_OSTREAM: + hm.u.d.u.stream.object_type = HPI_OBJ_OSTREAM; + u32TOINDEXES(h_stream, &adapter, + &hm.u.d.u.stream.stream_index); + break; + case HPI_OBJ_ISTREAM: + hm.u.d.u.stream.object_type = HPI_OBJ_ISTREAM; + u32TOINDEXES(h_stream, &adapter, + &hm.u.d.u.stream.stream_index); + break; + default: + return HPI_ERROR_INVALID_STREAM; + } + + if (adapter != hm.adapter_index) + return HPI_ERROR_NO_INTERADAPTER_GROUPS; + + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_instream_group_get_map(const struct hpi_hsubsys *ph_subsys, + u32 h_instream, u32 *poutstream_map, u32 *pinstream_map) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_HOSTBUFFER_FREE); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + if (poutstream_map) + *poutstream_map = hr.u.d.u.group_info.outstream_group_map; + if (pinstream_map) + *pinstream_map = hr.u.d.u.group_info.instream_group_map; + + return hr.error; +} + +u16 hpi_instream_group_reset(const struct hpi_hsubsys *ph_subsys, + u32 h_instream) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_GROUP_RESET); + u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_mixer_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_mixer) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_mixer = + hpi_indexes_to_handle(HPI_OBJ_MIXER, adapter_index, + 0); + else + *ph_mixer = 0; + return hr.error; +} + +u16 hpi_mixer_close(const struct hpi_hsubsys *ph_subsys, u32 h_mixer) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE); + u32TOINDEX(h_mixer, &hm.adapter_index); + hpi_send_recv(&hm, &hr); + return hr.error; +} + +u16 hpi_mixer_get_control(const struct hpi_hsubsys *ph_subsys, u32 h_mixer, + u16 src_node_type, u16 src_node_type_index, u16 dst_node_type, + u16 dst_node_type_index, u16 control_type, u32 *ph_control) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, + HPI_MIXER_GET_CONTROL); + u32TOINDEX(h_mixer, &hm.adapter_index); + hm.u.m.node_type1 = src_node_type; + hm.u.m.node_index1 = src_node_type_index; + hm.u.m.node_type2 = dst_node_type; + hm.u.m.node_index2 = dst_node_type_index; + hm.u.m.control_type = control_type; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_control = + hpi_indexes_to_handle(HPI_OBJ_CONTROL, + hm.adapter_index, hr.u.m.control_index); + else + *ph_control = 0; + return hr.error; +} + +u16 hpi_mixer_get_control_by_index(const struct hpi_hsubsys *ph_subsys, + u32 h_mixer, u16 control_index, u16 *pw_src_node_type, + u16 *pw_src_node_index, u16 *pw_dst_node_type, u16 *pw_dst_node_index, + u16 *pw_control_type, u32 *ph_control) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, + HPI_MIXER_GET_CONTROL_BY_INDEX); + u32TOINDEX(h_mixer, &hm.adapter_index); + hm.u.m.control_index = control_index; + hpi_send_recv(&hm, &hr); + + if (pw_src_node_type) { + *pw_src_node_type = + hr.u.m.src_node_type + HPI_SOURCENODE_NONE; + *pw_src_node_index = hr.u.m.src_node_index; + *pw_dst_node_type = hr.u.m.dst_node_type + HPI_DESTNODE_NONE; + *pw_dst_node_index = hr.u.m.dst_node_index; + } + if (pw_control_type) + *pw_control_type = hr.u.m.control_index; + + if (ph_control) { + if (hr.error == 0) + *ph_control = + hpi_indexes_to_handle(HPI_OBJ_CONTROL, + hm.adapter_index, control_index); + else + *ph_control = 0; + } + return hr.error; +} + +u16 hpi_mixer_store(const struct hpi_hsubsys *ph_subsys, u32 h_mixer, + enum HPI_MIXER_STORE_COMMAND command, u16 index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_STORE); + u32TOINDEX(h_mixer, &hm.adapter_index); + hm.u.mx.store.command = command; + hm.u.mx.store.index = index; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static +u16 hpi_control_param_set(const struct hpi_hsubsys *ph_subsys, + const u32 h_control, const u16 attrib, const u32 param1, + const u32 param2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = attrib; + hm.u.c.param1 = param1; + hm.u.c.param2 = param2; + hpi_send_recv(&hm, &hr); + return hr.error; +} + +static +u16 hpi_control_param_get(const struct hpi_hsubsys *ph_subsys, + const u32 h_control, const u16 attrib, u32 param1, u32 param2, + u32 *pparam1, u32 *pparam2) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = attrib; + hm.u.c.param1 = param1; + hm.u.c.param2 = param2; + hpi_send_recv(&hm, &hr); + if (pparam1) + *pparam1 = hr.u.c.param1; + if (pparam2) + *pparam2 = hr.u.c.param2; + + return hr.error; +} + +#define hpi_control_param1_get(s, h, a, p1) \ + hpi_control_param_get(s, h, a, 0, 0, p1, NULL) +#define hpi_control_param2_get(s, h, a, p1, p2) \ + hpi_control_param_get(s, h, a, 0, 0, p1, p2) +#define hpi_control_ex_param1_get(s, h, a, p1) \ + hpi_control_ex_param_get(s, h, a, 0, 0, p1, NULL) +#define hpi_control_ex_param2_get(s, h, a, p1, p2) \ + hpi_control_ex_param_get(s, h, a, 0, 0, p1, p2) + +static +u16 hpi_control_query(const struct hpi_hsubsys *ph_subsys, + const u32 h_control, const u16 attrib, const u32 index, + const u32 param, u32 *psetting) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_INFO); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.c.attribute = attrib; + hm.u.c.param1 = index; + hm.u.c.param2 = param; + + hpi_send_recv(&hm, &hr); + *psetting = hr.u.c.param1; + + return hr.error; +} + +static u16 hpi_control_get_string(const struct hpi_hsubsys *ph_subsys, + const u32 h_control, const u16 attribute, char *psz_string, + const u32 string_length) +{ + unsigned int sub_string_index = 0, j = 0; + char c = 0; + unsigned int n = 0; + u16 hE = 0; + + if ((string_length < 1) || (string_length > 256)) + return HPI_ERROR_INVALID_CONTROL_VALUE; + for (sub_string_index = 0; sub_string_index < string_length; + sub_string_index += 8) { + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = attribute; + hm.u.c.param1 = sub_string_index; + hm.u.c.param2 = 0; + hpi_send_recv(&hm, &hr); + + if (sub_string_index == 0 + && (hr.u.cu.chars8.remaining_chars + 8) > + string_length) + return HPI_ERROR_INVALID_CONTROL_VALUE; + + if (hr.error) { + hE = hr.error; + break; + } + for (j = 0; j < 8; j++) { + c = hr.u.cu.chars8.sz_data[j]; + psz_string[sub_string_index + j] = c; + n++; + if (n >= string_length) { + psz_string[string_length - 1] = 0; + hE = HPI_ERROR_INVALID_CONTROL_VALUE; + break; + } + if (c == 0) + break; + } + + if ((hr.u.cu.chars8.remaining_chars == 0) + && ((sub_string_index + j) < string_length) + && (c != 0)) { + c = 0; + psz_string[sub_string_index + j] = c; + } + if (c == 0) + break; + } + return hE; +} + +u16 HPI_AESEBU__receiver_query_format(const struct hpi_hsubsys *ph_subsys, + const u32 h_aes_rx, const u32 index, u16 *pw_format) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_aes_rx, HPI_AESEBURX_FORMAT, + index, 0, &qr); + *pw_format = (u16)qr; + return err; +} + +u16 HPI_AESEBU__receiver_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 format) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_AESEBURX_FORMAT, format, 0); +} + +u16 HPI_AESEBU__receiver_get_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_format) +{ + u16 err; + u32 param; + + err = hpi_control_param1_get(ph_subsys, h_control, + HPI_AESEBURX_FORMAT, ¶m); + if (!err && pw_format) + *pw_format = (u16)param; + + return err; +} + +u16 HPI_AESEBU__receiver_get_sample_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate) +{ + return hpi_control_param1_get(ph_subsys, h_control, + HPI_AESEBURX_SAMPLERATE, psample_rate); +} + +u16 HPI_AESEBU__receiver_get_user_data(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_AESEBURX_USERDATA; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 HPI_AESEBU__receiver_get_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_AESEBURX_CHANNELSTATUS; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 HPI_AESEBU__receiver_get_error_status(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_error_data) +{ + u32 error_data = 0; + u16 error = 0; + + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_AESEBURX_ERRORSTATUS, &error_data); + if (pw_error_data) + *pw_error_data = (u16)error_data; + return error; +} + +u16 HPI_AESEBU__transmitter_set_sample_rate(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u32 sample_rate) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_AESEBUTX_SAMPLERATE, sample_rate, 0); +} + +u16 HPI_AESEBU__transmitter_set_user_data(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 data) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_AESEBUTX_USERDATA, index, data); +} + +u16 HPI_AESEBU__transmitter_set_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 data) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_AESEBUTX_CHANNELSTATUS, index, data); +} + +u16 HPI_AESEBU__transmitter_get_channel_status(const struct hpi_hsubsys + *ph_subsys, u32 h_control, u16 index, u16 *pw_data) +{ + return HPI_ERROR_INVALID_OPERATION; +} + +u16 HPI_AESEBU__transmitter_query_format(const struct hpi_hsubsys *ph_subsys, + const u32 h_aes_tx, const u32 index, u16 *pw_format) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_aes_tx, HPI_AESEBUTX_FORMAT, + index, 0, &qr); + *pw_format = (u16)qr; + return err; +} + +u16 HPI_AESEBU__transmitter_set_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 output_format) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_AESEBUTX_FORMAT, output_format, 0); +} + +u16 HPI_AESEBU__transmitter_get_format(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_output_format) +{ + u16 err; + u32 param; + + err = hpi_control_param1_get(ph_subsys, h_control, + HPI_AESEBUTX_FORMAT, ¶m); + if (!err && pw_output_format) + *pw_output_format = (u16)param; + + return err; +} + +u16 hpi_bitstream_set_clock_edge(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 edge_type) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_BITSTREAM_CLOCK_EDGE, edge_type, 0); +} + +u16 hpi_bitstream_set_data_polarity(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 polarity) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_BITSTREAM_DATA_POLARITY, polarity, 0); +} + +u16 hpi_bitstream_get_activity(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_clk_activity, u16 *pw_data_activity) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_BITSTREAM_ACTIVITY; + hpi_send_recv(&hm, &hr); + if (pw_clk_activity) + *pw_clk_activity = (u16)hr.u.c.param1; + if (pw_data_activity) + *pw_data_activity = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_channel_mode_query_mode(const struct hpi_hsubsys *ph_subsys, + const u32 h_mode, const u32 index, u16 *pw_mode) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_mode, HPI_CHANNEL_MODE_MODE, + index, 0, &qr); + *pw_mode = (u16)qr; + return err; +} + +u16 hpi_channel_mode_set(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 mode) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_CHANNEL_MODE_MODE, mode, 0); +} + +u16 hpi_channel_mode_get(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *mode) +{ + u32 mode32 = 0; + u16 error = hpi_control_param1_get(ph_subsys, h_control, + HPI_CHANNEL_MODE_MODE, &mode32); + if (mode) + *mode = (u16)mode32; + return error; +} + +u16 hpi_cobranet_hmi_write(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 hmi_address, u32 byte_count, u8 *pb_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.cx.u.cobranet_data.byte_count = byte_count; + hm.u.cx.u.cobranet_data.hmi_address = hmi_address; + + if (byte_count <= 8) { + memcpy(hm.u.cx.u.cobranet_data.data, pb_data, byte_count); + hm.u.cx.attribute = HPI_COBRANET_SET; + } else { + hm.u.cx.u.cobranet_bigdata.pb_data = pb_data; + hm.u.cx.attribute = HPI_COBRANET_SET_DATA; + } + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_cobranet_hmi_read(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 hmi_address, u32 max_byte_count, u32 *pbyte_count, u8 *pb_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.cx.u.cobranet_data.byte_count = max_byte_count; + hm.u.cx.u.cobranet_data.hmi_address = hmi_address; + + if (max_byte_count <= 8) { + hm.u.cx.attribute = HPI_COBRANET_GET; + } else { + hm.u.cx.u.cobranet_bigdata.pb_data = pb_data; + hm.u.cx.attribute = HPI_COBRANET_GET_DATA; + } + + hpi_send_recv(&hm, &hr); + if (!hr.error && pb_data) { + + *pbyte_count = hr.u.cx.u.cobranet_data.byte_count; + + if (*pbyte_count < max_byte_count) + max_byte_count = *pbyte_count; + + if (hm.u.cx.attribute == HPI_COBRANET_GET) { + memcpy(pb_data, hr.u.cx.u.cobranet_data.data, + max_byte_count); + } else { + + } + + } + return hr.error; +} + +u16 hpi_cobranet_hmi_get_status(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pstatus, u32 *preadable_size, + u32 *pwriteable_size) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.cx.attribute = HPI_COBRANET_GET_STATUS; + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + if (pstatus) + *pstatus = hr.u.cx.u.cobranet_status.status; + if (preadable_size) + *preadable_size = + hr.u.cx.u.cobranet_status.readable_size; + if (pwriteable_size) + *pwriteable_size = + hr.u.cx.u.cobranet_status.writeable_size; + } + return hr.error; +} + +u16 hpi_cobranet_getI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pi_paddress) +{ + u32 byte_count; + u32 iP; + u16 error; + error = hpi_cobranet_hmi_read(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, &byte_count, + (u8 *)&iP); + + *pi_paddress = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (error) + *pi_paddress = 0; + + return error; + +} + +u16 hpi_cobranet_setI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 i_paddress) +{ + u32 iP; + u16 error; + + iP = ((i_paddress & 0xff000000) >> 8) | ((i_paddress & 0x00ff0000) << + 8) | ((i_paddress & 0x0000ff00) >> 8) | ((i_paddress & + 0x000000ff) << 8); + + error = hpi_cobranet_hmi_write(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, (u8 *)&iP); + + return error; + +} + +u16 hpi_cobranet_get_staticI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pi_paddress) +{ + u32 byte_count; + u32 iP; + u16 error; + error = hpi_cobranet_hmi_read(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, &byte_count, + (u8 *)&iP); + + *pi_paddress = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (error) + *pi_paddress = 0; + + return error; + +} + +u16 hpi_cobranet_set_staticI_paddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 i_paddress) +{ + u32 iP; + u16 error; + + iP = ((i_paddress & 0xff000000) >> 8) | ((i_paddress & 0x00ff0000) << + 8) | ((i_paddress & 0x0000ff00) >> 8) | ((i_paddress & + 0x000000ff) << 8); + + error = hpi_cobranet_hmi_write(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, (u8 *)&iP); + + return error; + +} + +u16 hpi_cobranet_getMA_caddress(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pmAC_MS_bs, u32 *pmAC_LS_bs) +{ + u32 byte_count; + u16 error; + u32 mAC; + error = hpi_cobranet_hmi_read(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_if_phy_address, 4, &byte_count, + (u8 *)&mAC); + *pmAC_MS_bs = + ((mAC & 0xff000000) >> 8) | ((mAC & 0x00ff0000) << 8) | ((mAC + & 0x0000ff00) >> 8) | ((mAC & 0x000000ff) << 8); + error += hpi_cobranet_hmi_read(ph_subsys, h_control, + HPI_COBRANET_HMI_cobra_if_phy_address + 1, 4, &byte_count, + (u8 *)&mAC); + *pmAC_LS_bs = + ((mAC & 0xff000000) >> 8) | ((mAC & 0x00ff0000) << 8) | ((mAC + & 0x0000ff00) >> 8) | ((mAC & 0x000000ff) << 8); + + if (error) { + *pmAC_MS_bs = 0; + *pmAC_LS_bs = 0; + } + + return error; +} + +u16 hpi_compander_set(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 attack, u16 decay, short ratio100, short threshold0_01dB, + short makeup_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.c.param1 = attack + ((u32)ratio100 << 16); + hm.u.c.param2 = (decay & 0xFFFFL); + hm.u.c.an_log_value[0] = threshold0_01dB; + hm.u.c.an_log_value[1] = makeup_gain0_01dB; + hm.u.c.attribute = HPI_COMPANDER_PARAMS; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_compander_get(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_attack, u16 *pw_decay, short *pw_ratio100, + short *pn_threshold0_01dB, short *pn_makeup_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_COMPANDER_PARAMS; + + hpi_send_recv(&hm, &hr); + + if (pw_attack) + *pw_attack = (short)(hr.u.c.param1 & 0xFFFF); + if (pw_decay) + *pw_decay = (short)(hr.u.c.param2 & 0xFFFF); + if (pw_ratio100) + *pw_ratio100 = (short)(hr.u.c.param1 >> 16); + + if (pn_threshold0_01dB) + *pn_threshold0_01dB = hr.u.c.an_log_value[0]; + if (pn_makeup_gain0_01dB) + *pn_makeup_gain0_01dB = hr.u.c.an_log_value[1]; + + return hr.error; +} + +u16 hpi_level_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_LEVEL_RANGE; + + hpi_send_recv(&hm, &hr); + if (hr.error) { + hr.u.c.an_log_value[0] = 0; + hr.u.c.an_log_value[1] = 0; + hr.u.c.param1 = 0; + } + if (min_gain_01dB) + *min_gain_01dB = hr.u.c.an_log_value[0]; + if (max_gain_01dB) + *max_gain_01dB = hr.u.c.an_log_value[1]; + if (step_gain_01dB) + *step_gain_01dB = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_level_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + memcpy(hm.u.c.an_log_value, an_gain0_01dB, + sizeof(short) * HPI_MAX_CHANNELS); + hm.u.c.attribute = HPI_LEVEL_GAIN; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_level_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_LEVEL_GAIN; + + hpi_send_recv(&hm, &hr); + + memcpy(an_gain0_01dB, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + return hr.error; +} + +u16 hpi_meter_query_channels(const struct hpi_hsubsys *ph_subsys, + const u32 h_meter, u32 *p_channels) +{ + return hpi_control_query(ph_subsys, h_meter, HPI_METER_NUM_CHANNELS, + 0, 0, p_channels); +} + +u16 hpi_meter_get_peak(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_peakdB[HPI_MAX_CHANNELS] + ) +{ + short i = 0; + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.obj_index = hm.obj_index; + hm.u.c.attribute = HPI_METER_PEAK; + + hpi_send_recv(&hm, &hr); + + if (!hr.error) + memcpy(an_peakdB, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + else + for (i = 0; i < HPI_MAX_CHANNELS; i++) + an_peakdB[i] = HPI_METER_MINIMUM; + return hr.error; +} + +u16 hpi_meter_get_rms(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_rmsdB[HPI_MAX_CHANNELS] + ) +{ + short i = 0; + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_METER_RMS; + + hpi_send_recv(&hm, &hr); + + if (!hr.error) + memcpy(an_rmsdB, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + else + for (i = 0; i < HPI_MAX_CHANNELS; i++) + an_rmsdB[i] = HPI_METER_MINIMUM; + + return hr.error; +} + +u16 hpi_meter_set_rms_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 attack, u16 decay) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_METER_RMS_BALLISTICS, attack, decay); +} + +u16 hpi_meter_get_rms_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pn_attack, u16 *pn_decay) +{ + u32 attack; + u32 decay; + u16 error; + + error = hpi_control_param2_get(ph_subsys, h_control, + HPI_METER_RMS_BALLISTICS, &attack, &decay); + + if (pn_attack) + *pn_attack = (unsigned short)attack; + if (pn_decay) + *pn_decay = (unsigned short)decay; + + return error; +} + +u16 hpi_meter_set_peak_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 attack, u16 decay) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_METER_PEAK_BALLISTICS, attack, decay); +} + +u16 hpi_meter_get_peak_ballistics(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pn_attack, u16 *pn_decay) +{ + u32 attack; + u32 decay; + u16 error; + + error = hpi_control_param2_get(ph_subsys, h_control, + HPI_METER_PEAK_BALLISTICS, &attack, &decay); + + if (pn_attack) + *pn_attack = (short)attack; + if (pn_decay) + *pn_decay = (short)decay; + + return error; +} + +u16 hpi_microphone_set_phantom_power(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 on_off) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_MICROPHONE_PHANTOM_POWER, (u32)on_off, 0); +} + +u16 hpi_microphone_get_phantom_power(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_on_off) +{ + u16 error = 0; + u32 on_off = 0; + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_MICROPHONE_PHANTOM_POWER, &on_off); + if (pw_on_off) + *pw_on_off = (u16)on_off; + return error; +} + +u16 hpi_multiplexer_set_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source_node_type, u16 source_node_index) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_MULTIPLEXER_SOURCE, source_node_type, source_node_index); +} + +u16 hpi_multiplexer_get_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *source_node_type, u16 *source_node_index) +{ + u32 node, index; + u16 error = hpi_control_param2_get(ph_subsys, h_control, + HPI_MULTIPLEXER_SOURCE, &node, + &index); + if (source_node_type) + *source_node_type = (u16)node; + if (source_node_index) + *source_node_index = (u16)index; + return error; +} + +u16 hpi_multiplexer_query_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *source_node_type, + u16 *source_node_index) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_MULTIPLEXER_QUERYSOURCE; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (source_node_type) + *source_node_type = (u16)hr.u.c.param1; + if (source_node_index) + *source_node_index = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_parametricEQ__get_info(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_number_of_bands, u16 *pw_on_off) +{ + u32 oB = 0; + u32 oO = 0; + u16 error = 0; + + error = hpi_control_param2_get(ph_subsys, h_control, + HPI_EQUALIZER_NUM_FILTERS, &oO, &oB); + if (pw_number_of_bands) + *pw_number_of_bands = (u16)oB; + if (pw_on_off) + *pw_on_off = (u16)oO; + return error; +} + +u16 hpi_parametricEQ__set_state(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 on_off) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_EQUALIZER_NUM_FILTERS, on_off, 0); +} + +u16 hpi_parametricEQ__get_band(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 *pn_type, u32 *pfrequency_hz, + short *pnQ100, short *pn_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_EQUALIZER_FILTER; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + + if (pfrequency_hz) + *pfrequency_hz = hr.u.c.param1; + if (pn_type) + *pn_type = (u16)(hr.u.c.param2 >> 16); + if (pnQ100) + *pnQ100 = hr.u.c.an_log_value[1]; + if (pn_gain0_01dB) + *pn_gain0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} + +u16 hpi_parametricEQ__set_band(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, u16 type, u32 frequency_hz, short q100, + short gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + hm.u.c.param1 = frequency_hz; + hm.u.c.param2 = (index & 0xFFFFL) + ((u32)type << 16); + hm.u.c.an_log_value[0] = gain0_01dB; + hm.u.c.an_log_value[1] = q100; + hm.u.c.attribute = HPI_EQUALIZER_FILTER; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_parametricEQ__get_coeffs(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 index, short coeffs[5] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_EQUALIZER_COEFFICIENTS; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + + coeffs[0] = (short)hr.u.c.an_log_value[0]; + coeffs[1] = (short)hr.u.c.an_log_value[1]; + coeffs[2] = (short)hr.u.c.param1; + coeffs[3] = (short)(hr.u.c.param1 >> 16); + coeffs[4] = (short)hr.u.c.param2; + + return hr.error; +} + +u16 hpi_sample_clock_query_source(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, u16 *pw_source) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_clock, HPI_SAMPLECLOCK_SOURCE, + index, 0, &qr); + *pw_source = (u16)qr; + return err; +} + +u16 hpi_sample_clock_set_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SAMPLECLOCK_SOURCE, source, 0); +} + +u16 hpi_sample_clock_get_source(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_source) +{ + u16 error = 0; + u32 source = 0; + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_SOURCE, &source); + if (!error) + if (pw_source) + *pw_source = (u16)source; + return error; +} + +u16 hpi_sample_clock_query_source_index(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, const u32 source, + u16 *pw_source_index) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_clock, + HPI_SAMPLECLOCK_SOURCE_INDEX, index, source, &qr); + *pw_source_index = (u16)qr; + return err; +} + +u16 hpi_sample_clock_set_source_index(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 source_index) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SAMPLECLOCK_SOURCE_INDEX, source_index, 0); +} + +u16 hpi_sample_clock_get_source_index(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u16 *pw_source_index) +{ + u16 error = 0; + u32 source_index = 0; + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_SOURCE_INDEX, &source_index); + if (!error) + if (pw_source_index) + *pw_source_index = (u16)source_index; + return error; +} + +u16 hpi_sample_clock_query_local_rate(const struct hpi_hsubsys *ph_subsys, + const u32 h_clock, const u32 index, u32 *prate) +{ + u16 err; + err = hpi_control_query(ph_subsys, h_clock, + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, index, 0, prate); + + return err; +} + +u16 hpi_sample_clock_set_local_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 sample_rate) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, sample_rate, 0); +} + +u16 hpi_sample_clock_get_local_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate) +{ + u16 error = 0; + u32 sample_rate = 0; + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, &sample_rate); + if (!error) + if (psample_rate) + *psample_rate = sample_rate; + return error; +} + +u16 hpi_sample_clock_get_sample_rate(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *psample_rate) +{ + u16 error = 0; + u32 sample_rate = 0; + error = hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_SAMPLERATE, &sample_rate); + if (!error) + if (psample_rate) + *psample_rate = sample_rate; + return error; +} + +u16 hpi_sample_clock_set_auto(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 enable) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SAMPLECLOCK_AUTO, enable, 0); +} + +u16 hpi_sample_clock_get_auto(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *penable) +{ + return hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_AUTO, penable); +} + +u16 hpi_sample_clock_set_local_rate_lock(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 lock) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SAMPLECLOCK_LOCAL_LOCK, lock, 0); +} + +u16 hpi_sample_clock_get_local_rate_lock(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *plock) +{ + return hpi_control_param1_get(ph_subsys, h_control, + HPI_SAMPLECLOCK_LOCAL_LOCK, plock); +} + +u16 hpi_tone_detector_get_frequency(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 index, u32 *frequency) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_TONEDETECTOR_FREQUENCY, index, 0, frequency, NULL); +} + +u16 hpi_tone_detector_get_state(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *state) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_TONEDETECTOR_STATE, 0, 0, (u32 *)state, NULL); +} + +u16 hpi_tone_detector_set_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 enable) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_GENERIC_ENABLE, + (u32)enable, 0); +} + +u16 hpi_tone_detector_get_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *enable) +{ + return hpi_control_param_get(ph_subsys, h_control, HPI_GENERIC_ENABLE, + 0, 0, (u32 *)enable, NULL); +} + +u16 hpi_tone_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 event_enable) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_GENERIC_EVENT_ENABLE, (u32)event_enable, 0); +} + +u16 hpi_tone_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *event_enable) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_GENERIC_EVENT_ENABLE, 0, 0, (u32 *)event_enable, NULL); +} + +u16 hpi_tone_detector_set_threshold(const struct hpi_hsubsys *ph_subsys, + u32 h_control, int threshold) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_TONEDETECTOR_THRESHOLD, (u32)threshold, 0); +} + +u16 hpi_tone_detector_get_threshold(const struct hpi_hsubsys *ph_subsys, + u32 h_control, int *threshold) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_TONEDETECTOR_THRESHOLD, 0, 0, (u32 *)threshold, NULL); +} + +u16 hpi_silence_detector_get_state(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *state) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_SILENCEDETECTOR_STATE, 0, 0, (u32 *)state, NULL); +} + +u16 hpi_silence_detector_set_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 enable) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_GENERIC_ENABLE, + (u32)enable, 0); +} + +u16 hpi_silence_detector_get_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *enable) +{ + return hpi_control_param_get(ph_subsys, h_control, HPI_GENERIC_ENABLE, + 0, 0, (u32 *)enable, NULL); +} + +u16 hpi_silence_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 event_enable) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_GENERIC_EVENT_ENABLE, (u32)event_enable, 0); +} + +u16 hpi_silence_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *event_enable) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_GENERIC_EVENT_ENABLE, 0, 0, (u32 *)event_enable, NULL); +} + +u16 hpi_silence_detector_set_delay(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 delay) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SILENCEDETECTOR_DELAY, (u32)delay, 0); +} + +u16 hpi_silence_detector_get_delay(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *delay) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_SILENCEDETECTOR_DELAY, 0, 0, (u32 *)delay, NULL); +} + +u16 hpi_silence_detector_set_threshold(const struct hpi_hsubsys *ph_subsys, + u32 h_control, int threshold) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_SILENCEDETECTOR_THRESHOLD, (u32)threshold, 0); +} + +u16 hpi_silence_detector_get_threshold(const struct hpi_hsubsys *ph_subsys, + u32 h_control, int *threshold) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_SILENCEDETECTOR_THRESHOLD, 0, 0, (u32 *)threshold, NULL); +} + +u16 hpi_tuner_query_band(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, u16 *pw_band) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_BAND, index, 0, + &qr); + *pw_band = (u16)qr; + return err; +} + +u16 hpi_tuner_set_band(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 band) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_BAND, + band, 0); +} + +u16 hpi_tuner_get_band(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_band) +{ + u32 band = 0; + u16 error = 0; + + error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_BAND, + &band); + if (pw_band) + *pw_band = (u16)band; + return error; +} + +u16 hpi_tuner_query_frequency(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, const u16 band, u32 *pfreq) +{ + return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_FREQ, index, + band, pfreq); +} + +u16 hpi_tuner_set_frequency(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 freq_ink_hz) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_FREQ, + freq_ink_hz, 0); +} + +u16 hpi_tuner_get_frequency(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pw_freq_ink_hz) +{ + return hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_FREQ, + pw_freq_ink_hz); +} + +u16 hpi_tuner_query_gain(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, u16 *pw_gain) +{ + u32 qr; + u16 err; + + err = hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_BAND, index, 0, + &qr); + *pw_gain = (u16)qr; + return err; +} + +u16 hpi_tuner_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short gain) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_GAIN, + gain, 0); +} + +u16 hpi_tuner_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *pn_gain) +{ + u32 gain = 0; + u16 error = 0; + + error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_GAIN, + &gain); + if (pn_gain) + *pn_gain = (u16)gain; + return error; +} + +u16 hpi_tuner_getRF_level(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *pw_level) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_TUNER_LEVEL; + hm.u.c.param1 = HPI_TUNER_LEVEL_AVERAGE; + hpi_send_recv(&hm, &hr); + if (pw_level) + *pw_level = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_tuner_get_rawRF_level(const struct hpi_hsubsys *ph_subsys, + u32 h_control, short *pw_level) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_TUNER_LEVEL; + hm.u.c.param1 = HPI_TUNER_LEVEL_RAW; + hpi_send_recv(&hm, &hr); + if (pw_level) + *pw_level = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_tuner_query_deemphasis(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, const u32 index, const u16 band, u32 *pdeemphasis) +{ + return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_DEEMPHASIS, + index, band, pdeemphasis); +} + +u16 hpi_tuner_set_deemphasis(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 deemphasis) +{ + return hpi_control_param_set(ph_subsys, h_control, + HPI_TUNER_DEEMPHASIS, deemphasis, 0); +} + +u16 hpi_tuner_get_deemphasis(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pdeemphasis) +{ + return hpi_control_param1_get(ph_subsys, h_control, + HPI_TUNER_DEEMPHASIS, pdeemphasis); +} + +u16 hpi_tuner_query_program(const struct hpi_hsubsys *ph_subsys, + const u32 h_tuner, u32 *pbitmap_program) +{ + return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_PROGRAM, 0, 0, + pbitmap_program); +} + +u16 hpi_tuner_set_program(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 program) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_PROGRAM, + program, 0); +} + +u16 hpi_tuner_get_program(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 *pprogram) +{ + return hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_PROGRAM, + pprogram); +} + +u16 hpi_tuner_get_hd_radio_dsp_version(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_dsp_version, const u32 string_size) +{ + return hpi_control_get_string(ph_subsys, h_control, + HPI_TUNER_HDRADIO_DSP_VERSION, psz_dsp_version, string_size); +} + +u16 hpi_tuner_get_hd_radio_sdk_version(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_sdk_version, const u32 string_size) +{ + return hpi_control_get_string(ph_subsys, h_control, + HPI_TUNER_HDRADIO_SDK_VERSION, psz_sdk_version, string_size); +} + +u16 hpi_tuner_get_status(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u16 *pw_status_mask, u16 *pw_status) +{ + u32 status = 0; + u16 error = 0; + + error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_STATUS, + &status); + if (pw_status) { + if (!error) { + *pw_status_mask = (u16)(status >> 16); + *pw_status = (u16)(status & 0xFFFF); + } else { + *pw_status_mask = 0; + *pw_status = 0; + } + } + return error; +} + +u16 hpi_tuner_set_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 mode, u32 value) +{ + return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_MODE, + mode, value); +} + +u16 hpi_tuner_get_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 mode, u32 *pn_value) +{ + return hpi_control_param_get(ph_subsys, h_control, HPI_TUNER_MODE, + mode, 0, pn_value, NULL); +} + +u16 hpi_tuner_get_hd_radio_signal_quality(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *pquality) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_TUNER_HDRADIO_SIGNAL_QUALITY, 0, 0, pquality, NULL); +} + +u16 hpi_tuner_getRDS(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *p_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_TUNER_RDS; + hpi_send_recv(&hm, &hr); + if (p_data) { + *(u32 *)&p_data[0] = hr.u.cu.tuner.rds.data[0]; + *(u32 *)&p_data[4] = hr.u.cu.tuner.rds.data[1]; + *(u32 *)&p_data[8] = hr.u.cu.tuner.rds.bLER; + } + return hr.error; +} + +u16 HPI_PAD__get_channel_name(const struct hpi_hsubsys *ph_subsys, + u32 h_control, char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(ph_subsys, h_control, + HPI_PAD_CHANNEL_NAME, psz_string, data_length); +} + +u16 HPI_PAD__get_artist(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(ph_subsys, h_control, HPI_PAD_ARTIST, + psz_string, data_length); +} + +u16 HPI_PAD__get_title(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(ph_subsys, h_control, HPI_PAD_TITLE, + psz_string, data_length); +} + +u16 HPI_PAD__get_comment(const struct hpi_hsubsys *ph_subsys, u32 h_control, + char *psz_string, const u32 data_length) +{ + return hpi_control_get_string(ph_subsys, h_control, HPI_PAD_COMMENT, + psz_string, data_length); +} + +u16 HPI_PAD__get_program_type(const struct hpi_hsubsys *ph_subsys, + u32 h_control, u32 *ppTY) +{ + return hpi_control_param_get(ph_subsys, h_control, + HPI_PAD_PROGRAM_TYPE, 0, 0, ppTY, NULL); +} + +u16 HPI_PAD__get_rdsPI(const struct hpi_hsubsys *ph_subsys, u32 h_control, + u32 *ppI) +{ + return hpi_control_param_get(ph_subsys, h_control, HPI_PAD_PROGRAM_ID, + 0, 0, ppI, NULL); +} + +u16 hpi_volume_query_channels(const struct hpi_hsubsys *ph_subsys, + const u32 h_volume, u32 *p_channels) +{ + return hpi_control_query(ph_subsys, h_volume, HPI_VOLUME_NUM_CHANNELS, + 0, 0, p_channels); +} + +u16 hpi_volume_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_log_gain[HPI_MAX_CHANNELS] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + memcpy(hm.u.c.an_log_value, an_log_gain, + sizeof(short) * HPI_MAX_CHANNELS); + hm.u.c.attribute = HPI_VOLUME_GAIN; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_volume_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_log_gain[HPI_MAX_CHANNELS] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_VOLUME_GAIN; + + hpi_send_recv(&hm, &hr); + + memcpy(an_log_gain, hr.u.c.an_log_value, + sizeof(short) * HPI_MAX_CHANNELS); + return hr.error; +} + +u16 hpi_volume_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_VOLUME_RANGE; + + hpi_send_recv(&hm, &hr); + if (hr.error) { + hr.u.c.an_log_value[0] = 0; + hr.u.c.an_log_value[1] = 0; + hr.u.c.param1 = 0; + } + if (min_gain_01dB) + *min_gain_01dB = hr.u.c.an_log_value[0]; + if (max_gain_01dB) + *max_gain_01dB = hr.u.c.an_log_value[1]; + if (step_gain_01dB) + *step_gain_01dB = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_volume_auto_fade_profile(const struct hpi_hsubsys *ph_subsys, + u32 h_control, short an_stop_gain0_01dB[HPI_MAX_CHANNELS], + u32 duration_ms, u16 profile) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + + memcpy(hm.u.c.an_log_value, an_stop_gain0_01dB, + sizeof(short) * HPI_MAX_CHANNELS); + + hm.u.c.attribute = HPI_VOLUME_AUTOFADE; + hm.u.c.param1 = duration_ms; + hm.u.c.param2 = profile; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_volume_auto_fade(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms) +{ + return hpi_volume_auto_fade_profile(ph_subsys, h_control, + an_stop_gain0_01dB, duration_ms, HPI_VOLUME_AUTOFADE_LOG); +} + +u16 hpi_vox_set_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short an_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_VOX_THRESHOLD; + + hm.u.c.an_log_value[0] = an_gain0_01dB; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_vox_get_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control, + short *an_gain0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index); + hm.u.c.attribute = HPI_VOX_THRESHOLD; + + hpi_send_recv(&hm, &hr); + + *an_gain0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} + +static size_t strv_packet_size = MIN_STRV_PACKET_SIZE; + +static size_t entity_type_to_size[LAST_ENTITY_TYPE] = { + 0, + sizeof(struct hpi_entity), + sizeof(void *), + + sizeof(int), + sizeof(float), + sizeof(double), + + sizeof(char), + sizeof(char), + + 4 * sizeof(char), + 16 * sizeof(char), + 6 * sizeof(char), +}; + +inline size_t hpi_entity_size(struct hpi_entity *entity_ptr) +{ + return entity_ptr->header.size; +} + +inline size_t hpi_entity_header_size(struct hpi_entity *entity_ptr) +{ + return sizeof(entity_ptr->header); +} + +inline size_t hpi_entity_value_size(struct hpi_entity *entity_ptr) +{ + return hpi_entity_size(entity_ptr) - + hpi_entity_header_size(entity_ptr); +} + +inline size_t hpi_entity_item_count(struct hpi_entity *entity_ptr) +{ + return hpi_entity_value_size(entity_ptr) / + entity_type_to_size[entity_ptr->header.type]; +} + +inline struct hpi_entity *hpi_entity_ptr_to_next(struct hpi_entity + *entity_ptr) +{ + return (void *)(((uint8_t *) entity_ptr) + + hpi_entity_size(entity_ptr)); +} + +inline u16 hpi_entity_check_type(const enum e_entity_type t) +{ + if (t >= 0 && t < STR_TYPE_FIELD_MAX) + return 0; + return HPI_ERROR_ENTITY_TYPE_INVALID; +} + +inline u16 hpi_entity_check_role(const enum e_entity_role r) +{ + if (r >= 0 && r < STR_ROLE_FIELD_MAX) + return 0; + return HPI_ERROR_ENTITY_ROLE_INVALID; +} + +static u16 hpi_entity_get_next(struct hpi_entity *entity, int recursive_flag, + void *guard_p, struct hpi_entity **next) +{ + HPI_DEBUG_ASSERT(entity != NULL); + HPI_DEBUG_ASSERT(next != NULL); + HPI_DEBUG_ASSERT(hpi_entity_size(entity) != 0); + + if (guard_p <= (void *)entity) { + *next = NULL; + return 0; + } + + if (recursive_flag && entity->header.type == entity_type_sequence) + *next = (struct hpi_entity *)entity->value; + else + *next = (struct hpi_entity *)hpi_entity_ptr_to_next(entity); + + if (guard_p <= (void *)*next) { + *next = NULL; + return 0; + } + + HPI_DEBUG_ASSERT(guard_p >= (void *)hpi_entity_ptr_to_next(*next)); + return 0; +} + +u16 hpi_entity_find_next(struct hpi_entity *container_entity, + enum e_entity_type type, enum e_entity_role role, int recursive_flag, + struct hpi_entity **current_match) +{ + struct hpi_entity *tmp = NULL; + void *guard_p = NULL; + + HPI_DEBUG_ASSERT(container_entity != NULL); + guard_p = hpi_entity_ptr_to_next(container_entity); + + if (*current_match != NULL) + hpi_entity_get_next(*current_match, recursive_flag, guard_p, + &tmp); + else + hpi_entity_get_next(container_entity, 1, guard_p, &tmp); + + while (tmp) { + u16 err; + + HPI_DEBUG_ASSERT((void *)tmp >= (void *)container_entity); + + if ((!type || tmp->header.type == type) && (!role + || tmp->header.role == role)) { + *current_match = tmp; + return 0; + } + + err = hpi_entity_get_next(tmp, recursive_flag, guard_p, + current_match); + if (err) + return err; + + tmp = *current_match; + } + + *current_match = NULL; + return 0; +} + +void hpi_entity_free(struct hpi_entity *entity) +{ + if (entity != NULL) + kfree(entity); +} + +static u16 hpi_entity_alloc_and_copy(struct hpi_entity *src, + struct hpi_entity **dst) +{ + size_t buf_size; + HPI_DEBUG_ASSERT(dst != NULL); + HPI_DEBUG_ASSERT(src != NULL); + + buf_size = hpi_entity_size(src); + *dst = kmalloc(buf_size, GFP_KERNEL); + if (dst == NULL) + return HPI_ERROR_MEMORY_ALLOC; + memcpy(*dst, src, buf_size); + return 0; +} + +u16 hpi_universal_info(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity **info) +{ + struct hpi_msg_strv hm; + struct hpi_res_strv *phr; + u16 hpi_err; + int remaining_attempts = 2; + size_t resp_packet_size = 1024; + + *info = NULL; + + while (remaining_attempts--) { + phr = kmalloc(resp_packet_size, GFP_KERNEL); + HPI_DEBUG_ASSERT(phr != NULL); + + hpi_init_message_responseV1(&hm.h, (u16)sizeof(hm), &phr->h, + (u16)resp_packet_size, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_INFO); + u32TOINDEXES(hC, &hm.h.adapter_index, &hm.h.obj_index); + + hm.strv.header.size = sizeof(hm.strv); + phr->strv.header.size = resp_packet_size - sizeof(phr->h); + + hpi_send_recv((struct hpi_message *)&hm.h, + (struct hpi_response *)&phr->h); + if (phr->h.error == HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL) { + + HPI_DEBUG_ASSERT(phr->h.specific_error > + MIN_STRV_PACKET_SIZE + && phr->h.specific_error < 1500); + resp_packet_size = phr->h.specific_error; + } else { + remaining_attempts = 0; + if (!phr->h.error) + hpi_entity_alloc_and_copy(&phr->strv, info); + } + + hpi_err = phr->h.error; + kfree(phr); + } + + return hpi_err; +} + +u16 hpi_universal_get(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity **value) +{ + struct hpi_msg_strv hm; + struct hpi_res_strv *phr; + u16 hpi_err; + int remaining_attempts = 2; + + *value = NULL; + + while (remaining_attempts--) { + phr = kmalloc(strv_packet_size, GFP_KERNEL); + if (!phr) + return HPI_ERROR_MEMORY_ALLOC; + + hpi_init_message_responseV1(&hm.h, (u16)sizeof(hm), &phr->h, + (u16)strv_packet_size, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + u32TOINDEXES(hC, &hm.h.adapter_index, &hm.h.obj_index); + + hm.strv.header.size = sizeof(hm.strv); + phr->strv.header.size = strv_packet_size - sizeof(phr->h); + + hpi_send_recv((struct hpi_message *)&hm.h, + (struct hpi_response *)&phr->h); + if (phr->h.error == HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL) { + + HPI_DEBUG_ASSERT(phr->h.specific_error > + MIN_STRV_PACKET_SIZE + && phr->h.specific_error < 1000); + strv_packet_size = phr->h.specific_error; + } else { + remaining_attempts = 0; + if (!phr->h.error) + hpi_entity_alloc_and_copy(&phr->strv, value); + } + + hpi_err = phr->h.error; + kfree(phr); + } + + return hpi_err; +} + +u16 hpi_universal_set(const struct hpi_hsubsys *ph_subsys, u32 hC, + struct hpi_entity *value) +{ + struct hpi_msg_strv *phm; + struct hpi_res_strv hr; + + phm = kmalloc(sizeof(phm->h) + value->header.size, GFP_KERNEL); + HPI_DEBUG_ASSERT(phm != NULL); + + hpi_init_message_responseV1(&phm->h, + sizeof(phm->h) + value->header.size, &hr.h, sizeof(hr), + HPI_OBJ_CONTROL, HPI_CONTROL_SET_STATE); + u32TOINDEXES(hC, &phm->h.adapter_index, &phm->h.obj_index); + hr.strv.header.size = sizeof(hr.strv); + + memcpy(&phm->strv, value, value->header.size); + hpi_send_recv((struct hpi_message *)&phm->h, + (struct hpi_response *)&hr.h); + + return hr.h.error; +} + +u16 hpi_entity_alloc_and_pack(const enum e_entity_type type, + const size_t item_count, const enum e_entity_role role, void *value, + struct hpi_entity **entity) +{ + size_t bytes_to_copy, total_size; + u16 hE = 0; + *entity = NULL; + + hE = hpi_entity_check_type(type); + if (hE) + return hE; + + HPI_DEBUG_ASSERT(role > entity_role_null && type < LAST_ENTITY_ROLE); + + bytes_to_copy = entity_type_to_size[type] * item_count; + total_size = hpi_entity_header_size(*entity) + bytes_to_copy; + + HPI_DEBUG_ASSERT(total_size >= hpi_entity_header_size(*entity) + && total_size < STR_SIZE_FIELD_MAX); + + *entity = kmalloc(total_size, GFP_KERNEL); + if (*entity == NULL) + return HPI_ERROR_MEMORY_ALLOC; + memcpy((*entity)->value, value, bytes_to_copy); + (*entity)->header.size = + hpi_entity_header_size(*entity) + bytes_to_copy; + (*entity)->header.type = type; + (*entity)->header.role = role; + return 0; +} + +u16 hpi_entity_copy_value_from(struct hpi_entity *entity, + enum e_entity_type type, size_t item_count, void *value_dst_p) +{ + size_t bytes_to_copy; + + if (entity->header.type != type) + return HPI_ERROR_ENTITY_TYPE_MISMATCH; + + if (hpi_entity_item_count(entity) != item_count) + return HPI_ERROR_ENTITY_ITEM_COUNT; + + bytes_to_copy = entity_type_to_size[type] * item_count; + memcpy(value_dst_p, entity->value, bytes_to_copy); + return 0; +} + +u16 hpi_entity_unpack(struct hpi_entity *entity, enum e_entity_type *type, + size_t *item_count, enum e_entity_role *role, void **value) +{ + u16 err = 0; + HPI_DEBUG_ASSERT(entity != NULL); + + if (type) + *type = entity->header.type; + + if (role) + *role = entity->header.role; + + if (value) + *value = entity->value; + + if (item_count != NULL) { + if (entity->header.type == entity_type_sequence) { + void *guard_p = hpi_entity_ptr_to_next(entity); + struct hpi_entity *next = NULL; + void *contents = entity->value; + + *item_count = 0; + while (contents < guard_p) { + (*item_count)++; + err = hpi_entity_get_next(contents, 0, + guard_p, &next); + if (next == NULL || err) + break; + contents = next; + } + } else { + *item_count = hpi_entity_item_count(entity); + } + } + return err; +} + +u16 hpi_gpio_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_gpio, u16 *pw_number_input_bits, u16 *pw_number_output_bits) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + *ph_gpio = + hpi_indexes_to_handle(HPI_OBJ_GPIO, adapter_index, 0); + if (pw_number_input_bits) + *pw_number_input_bits = hr.u.l.number_input_bits; + if (pw_number_output_bits) + *pw_number_output_bits = hr.u.l.number_output_bits; + } else + *ph_gpio = 0; + return hr.error; +} + +u16 hpi_gpio_read_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 bit_index, u16 *pw_bit_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_READ_BIT); + u32TOINDEX(h_gpio, &hm.adapter_index); + hm.u.l.bit_index = bit_index; + + hpi_send_recv(&hm, &hr); + + *pw_bit_data = hr.u.l.bit_data[0]; + return hr.error; +} + +u16 hpi_gpio_read_all_bits(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 aw_all_bit_data[4] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_READ_ALL); + u32TOINDEX(h_gpio, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + + if (aw_all_bit_data) { + aw_all_bit_data[0] = hr.u.l.bit_data[0]; + aw_all_bit_data[1] = hr.u.l.bit_data[1]; + aw_all_bit_data[2] = hr.u.l.bit_data[2]; + aw_all_bit_data[3] = hr.u.l.bit_data[3]; + } + return hr.error; +} + +u16 hpi_gpio_write_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 bit_index, u16 bit_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_WRITE_BIT); + u32TOINDEX(h_gpio, &hm.adapter_index); + hm.u.l.bit_index = bit_index; + hm.u.l.bit_data = bit_data; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_gpio_write_status(const struct hpi_hsubsys *ph_subsys, u32 h_gpio, + u16 aw_all_bit_data[4] + ) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, + HPI_GPIO_WRITE_STATUS); + u32TOINDEX(h_gpio, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + + if (aw_all_bit_data) { + aw_all_bit_data[0] = hr.u.l.bit_data[0]; + aw_all_bit_data[1] = hr.u.l.bit_data[1]; + aw_all_bit_data[2] = hr.u.l.bit_data[2]; + aw_all_bit_data[3] = hr.u.l.bit_data[3]; + } + return hr.error; +} + +u16 hpi_async_event_open(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u32 *ph_async) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT, + HPI_ASYNCEVENT_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + + *ph_async = + hpi_indexes_to_handle(HPI_OBJ_ASYNCEVENT, + adapter_index, 0); + else + *ph_async = 0; + return hr.error; + +} + +u16 hpi_async_event_close(const struct hpi_hsubsys *ph_subsys, u32 h_async) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT, + HPI_ASYNCEVENT_OPEN); + u32TOINDEX(h_async, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_async_event_wait(const struct hpi_hsubsys *ph_subsys, u32 h_async, + u16 maximum_events, struct hpi_async_event *p_events, + u16 *pw_number_returned) +{ + return 0; +} + +u16 hpi_async_event_get_count(const struct hpi_hsubsys *ph_subsys, + u32 h_async, u16 *pw_count) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT, + HPI_ASYNCEVENT_GETCOUNT); + u32TOINDEX(h_async, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + if (pw_count) + *pw_count = hr.u.as.u.count.count; + + return hr.error; +} + +u16 hpi_async_event_get(const struct hpi_hsubsys *ph_subsys, u32 h_async, + u16 maximum_events, struct hpi_async_event *p_events, + u16 *pw_number_returned) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT, + HPI_ASYNCEVENT_GET); + u32TOINDEX(h_async, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + memcpy(p_events, &hr.u.as.u.event, + sizeof(struct hpi_async_event)); + *pw_number_returned = 1; + } + + return hr.error; +} + +u16 hpi_nv_memory_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_nv_memory, u16 *pw_size_in_bytes) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY, + HPI_NVMEMORY_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) { + *ph_nv_memory = + hpi_indexes_to_handle(HPI_OBJ_NVMEMORY, adapter_index, + 0); + if (pw_size_in_bytes) + *pw_size_in_bytes = hr.u.n.size_in_bytes; + } else + *ph_nv_memory = 0; + return hr.error; +} + +u16 hpi_nv_memory_read_byte(const struct hpi_hsubsys *ph_subsys, + u32 h_nv_memory, u16 index, u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY, + HPI_NVMEMORY_READ_BYTE); + u32TOINDEX(h_nv_memory, &hm.adapter_index); + hm.u.n.address = index; + + hpi_send_recv(&hm, &hr); + + *pw_data = hr.u.n.data; + return hr.error; +} + +u16 hpi_nv_memory_write_byte(const struct hpi_hsubsys *ph_subsys, + u32 h_nv_memory, u16 index, u16 data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY, + HPI_NVMEMORY_WRITE_BYTE); + u32TOINDEX(h_nv_memory, &hm.adapter_index); + hm.u.n.address = index; + hm.u.n.data = data; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_profile_open_all(const struct hpi_hsubsys *ph_subsys, + u16 adapter_index, u16 profile_index, u32 *ph_profile, + u16 *pw_max_profiles) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, + HPI_PROFILE_OPEN_ALL); + hm.adapter_index = adapter_index; + hm.obj_index = profile_index; + hpi_send_recv(&hm, &hr); + + *pw_max_profiles = hr.u.p.u.o.max_profiles; + if (hr.error == 0) + *ph_profile = + hpi_indexes_to_handle(HPI_OBJ_PROFILE, adapter_index, + profile_index); + else + *ph_profile = 0; + return hr.error; +} + +u16 hpi_profile_get(const struct hpi_hsubsys *ph_subsys, u32 h_profile, + u16 bin_index, u16 *pw_seconds, u32 *pmicro_seconds, u32 *pcall_count, + u32 *pmax_micro_seconds, u32 *pmin_micro_seconds) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, HPI_PROFILE_GET); + u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index); + hm.u.p.bin_index = bin_index; + hpi_send_recv(&hm, &hr); + if (pw_seconds) + *pw_seconds = hr.u.p.u.t.seconds; + if (pmicro_seconds) + *pmicro_seconds = hr.u.p.u.t.micro_seconds; + if (pcall_count) + *pcall_count = hr.u.p.u.t.call_count; + if (pmax_micro_seconds) + *pmax_micro_seconds = hr.u.p.u.t.max_micro_seconds; + if (pmin_micro_seconds) + *pmin_micro_seconds = hr.u.p.u.t.min_micro_seconds; + return hr.error; +} + +u16 hpi_profile_get_utilization(const struct hpi_hsubsys *ph_subsys, + u32 h_profile, u32 *putilization) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, + HPI_PROFILE_GET_UTILIZATION); + u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + if (hr.error) { + if (putilization) + *putilization = 0; + } else { + if (putilization) + *putilization = hr.u.p.u.t.call_count; + } + return hr.error; +} + +u16 hpi_profile_get_name(const struct hpi_hsubsys *ph_subsys, u32 h_profile, + u16 bin_index, char *sz_name, u16 name_length) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, + HPI_PROFILE_GET_NAME); + u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index); + hm.u.p.bin_index = bin_index; + hpi_send_recv(&hm, &hr); + if (hr.error) { + if (sz_name) + strcpy(sz_name, "??"); + } else { + if (sz_name) + memcpy(sz_name, (char *)hr.u.p.u.n.sz_name, + name_length); + } + return hr.error; +} + +u16 hpi_profile_start_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, + HPI_PROFILE_START_ALL); + u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_profile_stop_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, + HPI_PROFILE_STOP_ALL); + u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index); + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_watchdog_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index, + u32 *ph_watchdog) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG, + HPI_WATCHDOG_OPEN); + hm.adapter_index = adapter_index; + + hpi_send_recv(&hm, &hr); + + if (hr.error == 0) + *ph_watchdog = + hpi_indexes_to_handle(HPI_OBJ_WATCHDOG, adapter_index, + 0); + else + *ph_watchdog = 0; + return hr.error; +} + +u16 hpi_watchdog_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog, + u32 time_millisec) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG, + HPI_WATCHDOG_SET_TIME); + u32TOINDEX(h_watchdog, &hm.adapter_index); + hm.u.w.time_ms = time_millisec; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_watchdog_ping(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG, + HPI_WATCHDOG_PING); + u32TOINDEX(h_watchdog, &hm.adapter_index); + + hpi_send_recv(&hm, &hr); + + return hr.error; +} diff --git a/sound/pci/asihpi/hpimsginit.c b/sound/pci/asihpi/hpimsginit.c new file mode 100644 index 00000000000..8e1d099ed7e --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.c @@ -0,0 +1,130 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + Hardware Programming Interface (HPI) Utility functions. + + (C) Copyright AudioScience Inc. 2007 +*******************************************************************************/ + +#include "hpi_internal.h" +#include "hpimsginit.h" + +/* The actual message size for each object type */ +static u16 msg_size[HPI_OBJ_MAXINDEX + 1] = HPI_MESSAGE_SIZE_BY_OBJECT; +/* The actual response size for each object type */ +static u16 res_size[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT; +/* Flag to enable alternate message type for SSX2 bypass. */ +static u16 gwSSX2_bypass; + +/** \internal + * Used by ASIO driver to disable SSX2 for a single process + * \param phSubSys Pointer to HPI subsystem handle. + * \param wBypass New bypass setting 0 = off, nonzero = on + * \return Previous bypass setting. + */ +u16 hpi_subsys_ssx2_bypass(const struct hpi_hsubsys *ph_subsys, u16 bypass) +{ + u16 old_value = gwSSX2_bypass; + + gwSSX2_bypass = bypass; + + return old_value; +} + +/** \internal + * initialize the HPI message structure + */ +static void hpi_init_message(struct hpi_message *phm, u16 object, + u16 function) +{ + memset(phm, 0, sizeof(*phm)); + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) + phm->size = msg_size[object]; + else + phm->size = sizeof(*phm); + + if (gwSSX2_bypass) + phm->type = HPI_TYPE_SSX2BYPASS_MESSAGE; + else + phm->type = HPI_TYPE_MESSAGE; + phm->object = object; + phm->function = function; + phm->version = 0; + /* Expect adapter index to be set by caller */ +} + +/** \internal + * initialize the HPI response structure + */ +void hpi_init_response(struct hpi_response *phr, u16 object, u16 function, + u16 error) +{ + memset(phr, 0, sizeof(*phr)); + phr->type = HPI_TYPE_RESPONSE; + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) + phr->size = res_size[object]; + else + phr->size = sizeof(*phr); + phr->object = object; + phr->function = function; + phr->error = error; + phr->specific_error = 0; + phr->version = 0; +} + +void hpi_init_message_response(struct hpi_message *phm, + struct hpi_response *phr, u16 object, u16 function) +{ + hpi_init_message(phm, object, function); + /* default error return if the response is + not filled in by the callee */ + hpi_init_response(phr, object, function, + HPI_ERROR_PROCESSING_MESSAGE); +} + +static void hpi_init_messageV1(struct hpi_message_header *phm, u16 size, + u16 object, u16 function) +{ + memset(phm, 0, sizeof(*phm)); + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + phm->size = size; + phm->type = HPI_TYPE_MESSAGE; + phm->object = object; + phm->function = function; + phm->version = 1; + /* Expect adapter index to be set by caller */ + } +} + +void hpi_init_responseV1(struct hpi_response_header *phr, u16 size, + u16 object, u16 function) +{ + memset(phr, 0, sizeof(*phr)); + phr->size = size; + phr->version = 1; + phr->type = HPI_TYPE_RESPONSE; + phr->error = HPI_ERROR_PROCESSING_MESSAGE; +} + +void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size, + struct hpi_response_header *phr, u16 res_size, u16 object, + u16 function) +{ + hpi_init_messageV1(phm, msg_size, object, function); + hpi_init_responseV1(phr, res_size, object, function); +} diff --git a/sound/pci/asihpi/hpimsginit.h b/sound/pci/asihpi/hpimsginit.h new file mode 100644 index 00000000000..864ad020c9b --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.h @@ -0,0 +1,40 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + Hardware Programming Interface (HPI) Utility functions + + (C) Copyright AudioScience Inc. 2007 +*******************************************************************************/ +/* Initialise response headers, or msg/response pairs. +Note that it is valid to just init a response e.g. when a lower level is preparing +a response to a message. +However, when sending a message, a matching response buffer always must be prepared +*/ + +void hpi_init_response(struct hpi_response *phr, u16 object, u16 function, + u16 error); + +void hpi_init_message_response(struct hpi_message *phm, + struct hpi_response *phr, u16 object, u16 function); + +void hpi_init_responseV1(struct hpi_response_header *phr, u16 size, + u16 object, u16 function); + +void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size, + struct hpi_response_header *phr, u16 res_size, u16 object, + u16 function); diff --git a/sound/pci/asihpi/hpimsgx.c b/sound/pci/asihpi/hpimsgx.c new file mode 100644 index 00000000000..2ee90dc3d89 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.c @@ -0,0 +1,907 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Extended Message Function With Response Cacheing + +(C) Copyright AudioScience Inc. 2002 +*****************************************************************************/ +#define SOURCEFILE_NAME "hpimsgx.c" +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpimsgx.h" +#include "hpidebug.h" + +static struct pci_device_id asihpi_pci_tbl[] = { +#include "hpipcida.h" +}; + +static struct hpios_spinlock msgx_lock; + +static hpi_handler_func *hpi_entry_points[HPI_MAX_ADAPTERS]; + +static hpi_handler_func *hpi_lookup_entry_point_function(const struct hpi_pci + *pci_info) +{ + + int i; + + for (i = 0; asihpi_pci_tbl[i].vendor != 0; i++) { + if (asihpi_pci_tbl[i].vendor != PCI_ANY_ID + && asihpi_pci_tbl[i].vendor != pci_info->vendor_id) + continue; + if (asihpi_pci_tbl[i].device != PCI_ANY_ID + && asihpi_pci_tbl[i].device != pci_info->device_id) + continue; + if (asihpi_pci_tbl[i].subvendor != PCI_ANY_ID + && asihpi_pci_tbl[i].subvendor != + pci_info->subsys_vendor_id) + continue; + if (asihpi_pci_tbl[i].subdevice != PCI_ANY_ID + && asihpi_pci_tbl[i].subdevice != + pci_info->subsys_device_id) + continue; + + HPI_DEBUG_LOG(DEBUG, " %x,%lu\n", i, + asihpi_pci_tbl[i].driver_data); + return (hpi_handler_func *) asihpi_pci_tbl[i].driver_data; + } + + return NULL; +} + +static inline void hw_entry_point(struct hpi_message *phm, + struct hpi_response *phr) +{ + + hpi_handler_func *ep; + + if (phm->adapter_index < HPI_MAX_ADAPTERS) { + ep = (hpi_handler_func *) hpi_entry_points[phm-> + adapter_index]; + if (ep) { + HPI_DEBUG_MESSAGE(DEBUG, phm); + ep(phm, phr); + HPI_DEBUG_RESPONSE(phr); + return; + } + } + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_PROCESSING_MESSAGE); +} + +static void adapter_open(struct hpi_message *phm, struct hpi_response *phr); +static void adapter_close(struct hpi_message *phm, struct hpi_response *phr); + +static void mixer_open(struct hpi_message *phm, struct hpi_response *phr); +static void mixer_close(struct hpi_message *phm, struct hpi_response *phr); + +static void outstream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void outstream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void instream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); +static void instream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); + +static void HPIMSGX__reset(u16 adapter_index); +static u16 HPIMSGX__init(struct hpi_message *phm, struct hpi_response *phr); +static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner); + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +struct hpi_subsys_response { + struct hpi_response_header h; + struct hpi_subsys_res s; +}; + +struct hpi_adapter_response { + struct hpi_response_header h; + struct hpi_adapter_res a; +}; + +struct hpi_mixer_response { + struct hpi_response_header h; + struct hpi_mixer_res m; +}; + +struct hpi_stream_response { + struct hpi_response_header h; + struct hpi_stream_res d; +}; + +struct adapter_info { + u16 type; + u16 num_instreams; + u16 num_outstreams; +}; + +struct asi_open_state { + int open_flag; + void *h_owner; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/* Globals */ +static struct hpi_adapter_response rESP_HPI_ADAPTER_OPEN[HPI_MAX_ADAPTERS]; + +static struct hpi_stream_response + rESP_HPI_OSTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct hpi_stream_response + rESP_HPI_ISTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct hpi_mixer_response rESP_HPI_MIXER_OPEN[HPI_MAX_ADAPTERS]; + +static struct hpi_subsys_response gRESP_HPI_SUBSYS_FIND_ADAPTERS; + +static struct adapter_info aDAPTER_INFO[HPI_MAX_ADAPTERS]; + +/* use these to keep track of opens from user mode apps/DLLs */ +static struct asi_open_state + outstream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static struct asi_open_state + instream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS]; + +static void subsys_message(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + switch (phm->function) { + case HPI_SUBSYS_GET_VERSION: + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_VERSION, 0); + phr->u.s.version = HPI_VER >> 8; /* return major.minor */ + phr->u.s.data = HPI_VER; /* return major.minor.release */ + break; + case HPI_SUBSYS_OPEN: + /*do not propagate the message down the chain */ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0); + break; + case HPI_SUBSYS_CLOSE: + /*do not propagate the message down the chain */ + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CLOSE, + 0); + HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner); + break; + case HPI_SUBSYS_DRIVER_LOAD: + /* Initialize this module's internal state */ + hpios_msgxlock_init(&msgx_lock); + memset(&hpi_entry_points, 0, sizeof(hpi_entry_points)); + hpios_locked_mem_init(); + /* Init subsys_findadapters response to no-adapters */ + HPIMSGX__reset(HPIMSGX_ALLADAPTERS); + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_LOAD, 0); + /* individual HPIs dont implement driver load */ + HPI_COMMON(phm, phr); + break; + case HPI_SUBSYS_DRIVER_UNLOAD: + HPI_COMMON(phm, phr); + HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner); + hpios_locked_mem_free_all(); + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_UNLOAD, 0); + return; + + case HPI_SUBSYS_GET_INFO: + HPI_COMMON(phm, phr); + break; + + case HPI_SUBSYS_FIND_ADAPTERS: + memcpy(phr, &gRESP_HPI_SUBSYS_FIND_ADAPTERS, + sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS)); + break; + case HPI_SUBSYS_GET_NUM_ADAPTERS: + memcpy(phr, &gRESP_HPI_SUBSYS_FIND_ADAPTERS, + sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS)); + phr->function = HPI_SUBSYS_GET_NUM_ADAPTERS; + break; + case HPI_SUBSYS_GET_ADAPTER: + { + int count = phm->adapter_index; + int index = 0; + hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_GET_ADAPTER, 0); + + /* This is complicated by the fact that we want to + * "skip" 0's in the adapter list. + * First, make sure we are pointing to a + * non-zero adapter type. + */ + while (gRESP_HPI_SUBSYS_FIND_ADAPTERS. + s.aw_adapter_list[index] == 0) { + index++; + if (index >= HPI_MAX_ADAPTERS) + break; + } + while (count) { + /* move on to the next adapter */ + index++; + if (index >= HPI_MAX_ADAPTERS) + break; + while (gRESP_HPI_SUBSYS_FIND_ADAPTERS. + s.aw_adapter_list[index] == 0) { + index++; + if (index >= HPI_MAX_ADAPTERS) + break; + } + count--; + } + + if (index < HPI_MAX_ADAPTERS) { + phr->u.s.adapter_index = (u16)index; + phr->u.s.aw_adapter_list[0] = + gRESP_HPI_SUBSYS_FIND_ADAPTERS. + s.aw_adapter_list[index]; + } else { + phr->u.s.adapter_index = 0; + phr->u.s.aw_adapter_list[0] = 0; + phr->error = HPI_ERROR_BAD_ADAPTER_NUMBER; + } + break; + } + case HPI_SUBSYS_CREATE_ADAPTER: + HPIMSGX__init(phm, phr); + break; + case HPI_SUBSYS_DELETE_ADAPTER: + HPIMSGX__cleanup(phm->adapter_index, h_owner); + { + struct hpi_message hm; + struct hpi_response hr; + /* call to HPI_ADAPTER_CLOSE */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_CLOSE); + hm.adapter_index = phm->adapter_index; + hw_entry_point(&hm, &hr); + } + hw_entry_point(phm, phr); + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s. + aw_adapter_list[phm->adapter_index] + = 0; + hpi_entry_points[phm->adapter_index] = NULL; + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void adapter_message(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + switch (phm->function) { + case HPI_ADAPTER_OPEN: + adapter_open(phm, phr); + break; + case HPI_ADAPTER_CLOSE: + adapter_close(phm, phr); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void mixer_message(struct hpi_message *phm, struct hpi_response *phr) +{ + switch (phm->function) { + case HPI_MIXER_OPEN: + mixer_open(phm, phr); + break; + case HPI_MIXER_CLOSE: + mixer_close(phm, phr); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void outstream_message(struct hpi_message *phm, + struct hpi_response *phr, void *h_owner) +{ + if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_outstreams) { + hpi_init_response(phr, HPI_OBJ_OSTREAM, phm->function, + HPI_ERROR_INVALID_OBJ_INDEX); + return; + } + + switch (phm->function) { + case HPI_OSTREAM_OPEN: + outstream_open(phm, phr, h_owner); + break; + case HPI_OSTREAM_CLOSE: + outstream_close(phm, phr, h_owner); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +static void instream_message(struct hpi_message *phm, + struct hpi_response *phr, void *h_owner) +{ + if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_instreams) { + hpi_init_response(phr, HPI_OBJ_ISTREAM, phm->function, + HPI_ERROR_INVALID_OBJ_INDEX); + return; + } + + switch (phm->function) { + case HPI_ISTREAM_OPEN: + instream_open(phm, phr, h_owner); + break; + case HPI_ISTREAM_CLOSE: + instream_close(phm, phr, h_owner); + break; + default: + hw_entry_point(phm, phr); + break; + } +} + +/* NOTE: HPI_Message() must be defined in the driver as a wrapper for + * HPI_MessageEx so that functions in hpifunc.c compile. + */ +void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + HPI_DEBUG_MESSAGE(DEBUG, phm); + + if (phm->type != HPI_TYPE_MESSAGE) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_INVALID_TYPE); + return; + } + + if (phm->adapter_index >= HPI_MAX_ADAPTERS + && phm->adapter_index != HPIMSGX_ALLADAPTERS) { + hpi_init_response(phr, phm->object, phm->function, + HPI_ERROR_BAD_ADAPTER_NUMBER); + return; + } + + switch (phm->object) { + case HPI_OBJ_SUBSYSTEM: + subsys_message(phm, phr, h_owner); + break; + + case HPI_OBJ_ADAPTER: + adapter_message(phm, phr, h_owner); + break; + + case HPI_OBJ_MIXER: + mixer_message(phm, phr); + break; + + case HPI_OBJ_OSTREAM: + outstream_message(phm, phr, h_owner); + break; + + case HPI_OBJ_ISTREAM: + instream_message(phm, phr, h_owner); + break; + + default: + hw_entry_point(phm, phr); + break; + } + HPI_DEBUG_RESPONSE(phr); +#if 1 + if (phr->error >= HPI_ERROR_BACKEND_BASE) { + void *ep = NULL; + char *ep_name; + + HPI_DEBUG_MESSAGE(ERROR, phm); + + if (phm->adapter_index < HPI_MAX_ADAPTERS) + ep = hpi_entry_points[phm->adapter_index]; + + /* Don't need this? Have adapter index in debug info + Know at driver load time index->backend mapping */ + if (ep == HPI_6000) + ep_name = "HPI_6000"; + else if (ep == HPI_6205) + ep_name = "HPI_6205"; + else + ep_name = "unknown"; + + HPI_DEBUG_LOG(ERROR, "HPI %s response - error# %d\n", ep_name, + phr->error); + + if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) + hpi_debug_data((u16 *)phm, + sizeof(*phm) / sizeof(u16)); + } +#endif +} + +static void adapter_open(struct hpi_message *phm, struct hpi_response *phr) +{ + HPI_DEBUG_LOG(VERBOSE, "adapter_open\n"); + memcpy(phr, &rESP_HPI_ADAPTER_OPEN[phm->adapter_index], + sizeof(rESP_HPI_ADAPTER_OPEN[0])); +} + +static void adapter_close(struct hpi_message *phm, struct hpi_response *phr) +{ + HPI_DEBUG_LOG(VERBOSE, "adapter_close\n"); + hpi_init_response(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, 0); +} + +static void mixer_open(struct hpi_message *phm, struct hpi_response *phr) +{ + memcpy(phr, &rESP_HPI_MIXER_OPEN[phm->adapter_index], + sizeof(rESP_HPI_MIXER_OPEN[0])); +} + +static void mixer_close(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_init_response(phr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE, 0); +} + +static void instream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (instream_user_open[phm->adapter_index][phm->obj_index].open_flag) + phr->error = HPI_ERROR_OBJ_ALREADY_OPEN; + else if (rESP_HPI_ISTREAM_OPEN[phm->adapter_index] + [phm->obj_index].h.error) + memcpy(phr, + &rESP_HPI_ISTREAM_OPEN[phm->adapter_index][phm-> + obj_index], + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + hpios_msgxlock_un_lock(&msgx_lock); + + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + phr->error = hr.error; + } else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + memcpy(phr, + &rESP_HPI_ISTREAM_OPEN[phm->adapter_index] + [phm->obj_index], + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + } + } + hpios_msgxlock_un_lock(&msgx_lock); +} + +static void instream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE, 0); + + hpios_msgxlock_lock(&msgx_lock); + if (h_owner == + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner) { + /* HPI_DEBUG_LOG(INFO,"closing adapter %d " + "instream %d owned by %p\n", + phm->wAdapterIndex, phm->wObjIndex, hOwner); */ + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + hpios_msgxlock_un_lock(&msgx_lock); + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + phr->error = hr.error; + } else { + instream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + } + } else { + HPI_DEBUG_LOG(WARNING, + "%p trying to close %d instream %d owned by %p\n", + h_owner, phm->adapter_index, phm->obj_index, + instream_user_open[phm->adapter_index][phm-> + obj_index].h_owner); + phr->error = HPI_ERROR_OBJ_NOT_OPEN; + } + hpios_msgxlock_un_lock(&msgx_lock); +} + +static void outstream_open(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (outstream_user_open[phm->adapter_index][phm->obj_index].open_flag) + phr->error = HPI_ERROR_OBJ_ALREADY_OPEN; + else if (rESP_HPI_OSTREAM_OPEN[phm->adapter_index] + [phm->obj_index].h.error) + memcpy(phr, + &rESP_HPI_OSTREAM_OPEN[phm->adapter_index][phm-> + obj_index], + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + hpios_msgxlock_un_lock(&msgx_lock); + + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + phr->error = hr.error; + } else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 1; + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + memcpy(phr, + &rESP_HPI_OSTREAM_OPEN[phm->adapter_index] + [phm->obj_index], + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + } + } + hpios_msgxlock_un_lock(&msgx_lock); +} + +static void outstream_close(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner) +{ + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE, 0); + + hpios_msgxlock_lock(&msgx_lock); + + if (h_owner == + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner) { + /* HPI_DEBUG_LOG(INFO,"closing adapter %d " + "outstream %d owned by %p\n", + phm->wAdapterIndex, phm->wObjIndex, hOwner); */ + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + hpios_msgxlock_un_lock(&msgx_lock); + /* issue a reset */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_RESET); + hm.adapter_index = phm->adapter_index; + hm.obj_index = phm->obj_index; + hw_entry_point(&hm, &hr); + hpios_msgxlock_lock(&msgx_lock); + if (hr.error) { + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = h_owner; + phr->error = hr.error; + } else { + outstream_user_open[phm->adapter_index][phm-> + obj_index].open_flag = 0; + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner = NULL; + } + } else { + HPI_DEBUG_LOG(WARNING, + "%p trying to close %d outstream %d owned by %p\n", + h_owner, phm->adapter_index, phm->obj_index, + outstream_user_open[phm->adapter_index][phm-> + obj_index].h_owner); + phr->error = HPI_ERROR_OBJ_NOT_OPEN; + } + hpios_msgxlock_un_lock(&msgx_lock); +} + +static u16 adapter_prepare(u16 adapter) +{ + struct hpi_message hm; + struct hpi_response hr; + + /* Open the adapter and streams */ + u16 i; + + /* call to HPI_ADAPTER_OPEN */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr, + sizeof(rESP_HPI_ADAPTER_OPEN[0])); + if (hr.error) + return hr.error; + + /* call to HPI_ADAPTER_GET_INFO */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_GET_INFO); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + if (hr.error) + return hr.error; + + aDAPTER_INFO[adapter].num_outstreams = hr.u.a.num_outstreams; + aDAPTER_INFO[adapter].num_instreams = hr.u.a.num_instreams; + aDAPTER_INFO[adapter].type = hr.u.a.adapter_type; + + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list[adapter] = + hr.u.a.adapter_type; + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters++; + if (gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters > HPI_MAX_ADAPTERS) + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters = + HPI_MAX_ADAPTERS; + + /* call to HPI_OSTREAM_OPEN */ + for (i = 0; i < aDAPTER_INFO[adapter].num_outstreams; i++) { + hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN); + hm.adapter_index = adapter; + hm.obj_index = i; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], &hr, + sizeof(rESP_HPI_OSTREAM_OPEN[0][0])); + outstream_user_open[adapter][i].open_flag = 0; + outstream_user_open[adapter][i].h_owner = NULL; + } + + /* call to HPI_ISTREAM_OPEN */ + for (i = 0; i < aDAPTER_INFO[adapter].num_instreams; i++) { + hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN); + hm.adapter_index = adapter; + hm.obj_index = i; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], &hr, + sizeof(rESP_HPI_ISTREAM_OPEN[0][0])); + instream_user_open[adapter][i].open_flag = 0; + instream_user_open[adapter][i].h_owner = NULL; + } + + /* call to HPI_MIXER_OPEN */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN); + hm.adapter_index = adapter; + hw_entry_point(&hm, &hr); + memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr, + sizeof(rESP_HPI_MIXER_OPEN[0])); + + return gRESP_HPI_SUBSYS_FIND_ADAPTERS.h.error; +} + +static void HPIMSGX__reset(u16 adapter_index) +{ + int i; + u16 adapter; + struct hpi_response hr; + + if (adapter_index == HPIMSGX_ALLADAPTERS) { + /* reset all responses to contain errors */ + hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_FIND_ADAPTERS, 0); + memcpy(&gRESP_HPI_SUBSYS_FIND_ADAPTERS, &hr, + sizeof(&gRESP_HPI_SUBSYS_FIND_ADAPTERS)); + + for (adapter = 0; adapter < HPI_MAX_ADAPTERS; adapter++) { + + hpi_init_response(&hr, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN, HPI_ERROR_BAD_ADAPTER); + memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr, + sizeof(rESP_HPI_ADAPTER_OPEN[adapter])); + + hpi_init_response(&hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr, + sizeof(rESP_HPI_MIXER_OPEN[adapter])); + + for (i = 0; i < HPI_MAX_STREAMS; i++) { + hpi_init_response(&hr, HPI_OBJ_OSTREAM, + HPI_OSTREAM_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], + &hr, + sizeof(rESP_HPI_OSTREAM_OPEN[adapter] + [i])); + hpi_init_response(&hr, HPI_OBJ_ISTREAM, + HPI_ISTREAM_OPEN, + HPI_ERROR_INVALID_OBJ); + memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], + &hr, + sizeof(rESP_HPI_ISTREAM_OPEN[adapter] + [i])); + } + } + } else if (adapter_index < HPI_MAX_ADAPTERS) { + rESP_HPI_ADAPTER_OPEN[adapter_index].h.error = + HPI_ERROR_BAD_ADAPTER; + rESP_HPI_MIXER_OPEN[adapter_index].h.error = + HPI_ERROR_INVALID_OBJ; + for (i = 0; i < HPI_MAX_STREAMS; i++) { + rESP_HPI_OSTREAM_OPEN[adapter_index][i].h.error = + HPI_ERROR_INVALID_OBJ; + rESP_HPI_ISTREAM_OPEN[adapter_index][i].h.error = + HPI_ERROR_INVALID_OBJ; + } + if (gRESP_HPI_SUBSYS_FIND_ADAPTERS. + s.aw_adapter_list[adapter_index]) { + gRESP_HPI_SUBSYS_FIND_ADAPTERS. + s.aw_adapter_list[adapter_index] = 0; + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters--; + } + } +} + +static u16 HPIMSGX__init(struct hpi_message *phm, + /* HPI_SUBSYS_CREATE_ADAPTER structure with */ + /* resource list or NULL=find all */ + struct hpi_response *phr + /* response from HPI_ADAPTER_GET_INFO */ + ) +{ + hpi_handler_func *entry_point_func; + struct hpi_response hr; + + if (gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters >= HPI_MAX_ADAPTERS) + return HPI_ERROR_BAD_ADAPTER_NUMBER; + + /* Init response here so we can pass in previous adapter list */ + hpi_init_response(&hr, phm->object, phm->function, + HPI_ERROR_INVALID_OBJ); + memcpy(hr.u.s.aw_adapter_list, + gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list, + sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list)); + + entry_point_func = + hpi_lookup_entry_point_function(phm->u.s.resource.r.pci); + + if (entry_point_func) { + HPI_DEBUG_MESSAGE(DEBUG, phm); + entry_point_func(phm, &hr); + } else { + phr->error = HPI_ERROR_PROCESSING_MESSAGE; + return phr->error; + } + if (hr.error == 0) { + /* the adapter was created succesfully + save the mapping for future use */ + hpi_entry_points[hr.u.s.adapter_index] = entry_point_func; + /* prepare adapter (pre-open streams etc.) */ + HPI_DEBUG_LOG(DEBUG, + "HPI_SUBSYS_CREATE_ADAPTER successful," + " preparing adapter\n"); + adapter_prepare(hr.u.s.adapter_index); + } + memcpy(phr, &hr, hr.size); + return phr->error; +} + +static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner) +{ + int i, adapter, adapter_limit; + + if (!h_owner) + return; + + if (adapter_index == HPIMSGX_ALLADAPTERS) { + adapter = 0; + adapter_limit = HPI_MAX_ADAPTERS; + } else { + adapter = adapter_index; + adapter_limit = adapter + 1; + } + + for (; adapter < adapter_limit; adapter++) { + /* printk(KERN_INFO "Cleanup adapter #%d\n",wAdapter); */ + for (i = 0; i < HPI_MAX_STREAMS; i++) { + if (h_owner == + outstream_user_open[adapter][i].h_owner) { + struct hpi_message hm; + struct hpi_response hr; + + HPI_DEBUG_LOG(DEBUG, + "close adapter %d ostream %d\n", + adapter, i); + + hpi_init_message_response(&hm, &hr, + HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET); + hm.adapter_index = (u16)adapter; + hm.obj_index = (u16)i; + hw_entry_point(&hm, &hr); + + hm.function = HPI_OSTREAM_HOSTBUFFER_FREE; + hw_entry_point(&hm, &hr); + + hm.function = HPI_OSTREAM_GROUP_RESET; + hw_entry_point(&hm, &hr); + + outstream_user_open[adapter][i].open_flag = 0; + outstream_user_open[adapter][i].h_owner = + NULL; + } + if (h_owner == instream_user_open[adapter][i].h_owner) { + struct hpi_message hm; + struct hpi_response hr; + + HPI_DEBUG_LOG(DEBUG, + "close adapter %d istream %d\n", + adapter, i); + + hpi_init_message_response(&hm, &hr, + HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET); + hm.adapter_index = (u16)adapter; + hm.obj_index = (u16)i; + hw_entry_point(&hm, &hr); + + hm.function = HPI_ISTREAM_HOSTBUFFER_FREE; + hw_entry_point(&hm, &hr); + + hm.function = HPI_ISTREAM_GROUP_RESET; + hw_entry_point(&hm, &hr); + + instream_user_open[adapter][i].open_flag = 0; + instream_user_open[adapter][i].h_owner = NULL; + } + } + } +} diff --git a/sound/pci/asihpi/hpimsgx.h b/sound/pci/asihpi/hpimsgx.h new file mode 100644 index 00000000000..fd49e7542a8 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.h @@ -0,0 +1,36 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + HPI Extended Message Handler Functions + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ + +#ifndef _HPIMSGX_H_ +#define _HPIMSGX_H_ + +#include "hpi_internal.h" + +#define HPIMSGX_ALLADAPTERS (0xFFFF) + +void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr, + void *h_owner); + +#define HPI_MESSAGE_LOWER_LAYER hpi_send_recv_ex + +#endif /* _HPIMSGX_H_ */ diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c new file mode 100644 index 00000000000..7396ac54e99 --- /dev/null +++ b/sound/pci/asihpi/hpioctl.c @@ -0,0 +1,484 @@ +/******************************************************************************* + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 Linux HPI ioctl and module probe/remove functions +*******************************************************************************/ +#define SOURCEFILE_NAME "hpioctl.c" + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpimsgx.h" +#include "hpioctl.h" + +#include +#include +#include +#include +#include + +#ifdef MODULE_FIRMWARE +MODULE_FIRMWARE("asihpi/dsp5000.bin"); +MODULE_FIRMWARE("asihpi/dsp6200.bin"); +MODULE_FIRMWARE("asihpi/dsp6205.bin"); +MODULE_FIRMWARE("asihpi/dsp6400.bin"); +MODULE_FIRMWARE("asihpi/dsp6600.bin"); +MODULE_FIRMWARE("asihpi/dsp8700.bin"); +MODULE_FIRMWARE("asihpi/dsp8900.bin"); +#endif + +static int prealloc_stream_buf; +module_param(prealloc_stream_buf, int, S_IRUGO); +MODULE_PARM_DESC(prealloc_stream_buf, + "preallocate size for per-adapter stream buffer"); + +/* Allow the debug level to be changed after module load. + E.g. echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel +*/ +module_param(hpi_debug_level, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5"); + +/* List of adapters found */ +static struct hpi_adapter adapters[HPI_MAX_ADAPTERS]; + +/* Wrapper function to HPI_Message to enable dumping of the + message and response types. +*/ +static void hpi_send_recv_f(struct hpi_message *phm, struct hpi_response *phr, + struct file *file) +{ + int adapter = phm->adapter_index; + + if ((adapter >= HPI_MAX_ADAPTERS || adapter < 0) + && (phm->object != HPI_OBJ_SUBSYSTEM)) + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + else + hpi_send_recv_ex(phm, phr, file); +} + +/* This is called from hpifunc.c functions, called by ALSA + * (or other kernel process) In this case there is no file descriptor + * available for the message cache code + */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_send_recv_f(phm, phr, HOWNER_KERNEL); +} + +EXPORT_SYMBOL(hpi_send_recv); +/* for radio-asihpi */ + +int asihpi_hpi_release(struct file *file) +{ + struct hpi_message hm; + struct hpi_response hr; + +/* HPI_DEBUG_LOG(INFO,"hpi_release file %p, pid %d\n", file, current->pid); */ + /* close the subsystem just in case the application forgot to. */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CLOSE); + hpi_send_recv_ex(&hm, &hr, file); + return 0; +} + +long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct hpi_ioctl_linux __user *phpi_ioctl_data; + void __user *puhm; + void __user *puhr; + union hpi_message_buffer_v1 *hm; + union hpi_response_buffer_v1 *hr; + u16 res_max_size; + u32 uncopied_bytes; + struct hpi_adapter *pa = NULL; + int err = 0; + + if (cmd != HPI_IOCTL_LINUX) + return -EINVAL; + + hm = kmalloc(sizeof(*hm), GFP_KERNEL); + hr = kmalloc(sizeof(*hr), GFP_KERNEL); + if (!hm || !hr) { + err = -ENOMEM; + goto out; + } + + phpi_ioctl_data = (struct hpi_ioctl_linux __user *)arg; + + /* Read the message and response pointers from user space. */ + get_user(puhm, &phpi_ioctl_data->phm); + get_user(puhr, &phpi_ioctl_data->phr); + + /* Now read the message size and data from user space. */ + get_user(hm->h.size, (u16 __user *)puhm); + if (hm->h.size > sizeof(*hm)) + hm->h.size = sizeof(*hm); + + /*printk(KERN_INFO "message size %d\n", hm->h.wSize); */ + + uncopied_bytes = copy_from_user(hm, puhm, hm->h.size); + if (uncopied_bytes) { + HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes); + err = -EFAULT; + goto out; + } + + get_user(res_max_size, (u16 __user *)puhr); + /* printk(KERN_INFO "user response size %d\n", res_max_size); */ + if (res_max_size < sizeof(struct hpi_response_header)) { + HPI_DEBUG_LOG(WARNING, "small res size %d\n", res_max_size); + err = -EFAULT; + goto out; + } + + pa = &adapters[hm->h.adapter_index]; + hr->h.size = 0; + if (hm->h.object == HPI_OBJ_SUBSYSTEM) { + switch (hm->h.function) { + case HPI_SUBSYS_CREATE_ADAPTER: + case HPI_SUBSYS_DELETE_ADAPTER: + /* Application must not use these functions! */ + hr->h.size = sizeof(hr->h); + hr->h.error = HPI_ERROR_INVALID_OPERATION; + hr->h.function = hm->h.function; + uncopied_bytes = copy_to_user(puhr, hr, hr->h.size); + if (uncopied_bytes) + err = -EFAULT; + else + err = 0; + goto out; + + default: + hpi_send_recv_f(&hm->m0, &hr->r0, file); + } + } else { + u16 __user *ptr = NULL; + u32 size = 0; + + /* -1=no data 0=read from user mem, 1=write to user mem */ + int wrflag = -1; + u32 adapter = hm->h.adapter_index; + + if ((hm->h.adapter_index > HPI_MAX_ADAPTERS) || (!pa->type)) { + hpi_init_response(&hr->r0, HPI_OBJ_ADAPTER, + HPI_ADAPTER_OPEN, + HPI_ERROR_BAD_ADAPTER_NUMBER); + + uncopied_bytes = + copy_to_user(puhr, hr, sizeof(hr->h)); + if (uncopied_bytes) + err = -EFAULT; + else + err = 0; + goto out; + } + + if (mutex_lock_interruptible(&adapters[adapter].mutex)) { + err = -EINTR; + goto out; + } + + /* Dig out any pointers embedded in the message. */ + switch (hm->h.function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_READ:{ + /* Yes, sparse, this is correct. */ + ptr = (u16 __user *)hm->m0.u.d.u.data.pb_data; + size = hm->m0.u.d.u.data.data_size; + + /* Allocate buffer according to application request. + ?Is it better to alloc/free for the duration + of the transaction? + */ + if (pa->buffer_size < size) { + HPI_DEBUG_LOG(DEBUG, + "realloc adapter %d stream " + "buffer from %zd to %d\n", + hm->h.adapter_index, + pa->buffer_size, size); + if (pa->p_buffer) { + pa->buffer_size = 0; + vfree(pa->p_buffer); + } + pa->p_buffer = vmalloc(size); + if (pa->p_buffer) + pa->buffer_size = size; + else { + HPI_DEBUG_LOG(ERROR, + "HPI could not allocate " + "stream buffer size %d\n", + size); + + mutex_unlock(&adapters + [adapter].mutex); + err = -EINVAL; + goto out; + } + } + + hm->m0.u.d.u.data.pb_data = pa->p_buffer; + if (hm->h.function == HPI_ISTREAM_READ) + /* from card, WRITE to user mem */ + wrflag = 1; + else + wrflag = 0; + break; + } + + default: + size = 0; + break; + } + + if (size && (wrflag == 0)) { + uncopied_bytes = + copy_from_user(pa->p_buffer, ptr, size); + if (uncopied_bytes) + HPI_DEBUG_LOG(WARNING, + "missed %d of %d " + "bytes from user\n", uncopied_bytes, + size); + } + + hpi_send_recv_f(&hm->m0, &hr->r0, file); + + if (size && (wrflag == 1)) { + uncopied_bytes = + copy_to_user(ptr, pa->p_buffer, size); + if (uncopied_bytes) + HPI_DEBUG_LOG(WARNING, + "missed %d of %d " "bytes to user\n", + uncopied_bytes, size); + } + + mutex_unlock(&adapters[adapter].mutex); + } + + /* on return response size must be set */ + /*printk(KERN_INFO "response size %d\n", hr->h.wSize); */ + + if (!hr->h.size) { + HPI_DEBUG_LOG(ERROR, "response zero size\n"); + err = -EFAULT; + goto out; + } + + if (hr->h.size > res_max_size) { + HPI_DEBUG_LOG(ERROR, "response too big %d %d\n", hr->h.size, + res_max_size); + /*HPI_DEBUG_MESSAGE(ERROR, hm); */ + err = -EFAULT; + goto out; + } + + uncopied_bytes = copy_to_user(puhr, hr, hr->h.size); + if (uncopied_bytes) { + HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes); + err = -EFAULT; + goto out; + } + +out: + kfree(hm); + kfree(hr); + return err; +} + +int __devinit asihpi_adapter_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + int err, idx, nm; + unsigned int memlen; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter adapter; + struct hpi_pci pci; + + memset(&adapter, 0, sizeof(adapter)); + + printk(KERN_DEBUG "probe PCI device (%04x:%04x,%04x:%04x,%04x)\n", + pci_dev->vendor, pci_dev->device, pci_dev->subsystem_vendor, + pci_dev->subsystem_device, pci_dev->devfn); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CREATE_ADAPTER); + hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CREATE_ADAPTER, + HPI_ERROR_PROCESSING_MESSAGE); + + hm.adapter_index = -1; /* an invalid index */ + + /* fill in HPI_PCI information from kernel provided information */ + adapter.pci = pci_dev; + + nm = HPI_MAX_ADAPTER_MEM_SPACES; + + for (idx = 0; idx < nm; idx++) { + HPI_DEBUG_LOG(INFO, "resource %d %s %08llx-%08llx %04llx\n", + idx, pci_dev->resource[idx].name, + (unsigned long long)pci_resource_start(pci_dev, idx), + (unsigned long long)pci_resource_end(pci_dev, idx), + (unsigned long long)pci_resource_flags(pci_dev, idx)); + + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + adapter.ap_remapped_mem_base[idx] = + ioremap(pci_resource_start(pci_dev, idx), + memlen); + if (!adapter.ap_remapped_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); + /* unmap previously mapped pci mem space */ + goto err; + } + } + + pci.ap_mem_base[idx] = adapter.ap_remapped_mem_base[idx]; + } + + /* could replace Pci with direct pointer to pci_dev for linux + Instead wrap accessor functions for IDs etc. + Would it work for windows? + */ + pci.bus_number = pci_dev->bus->number; + pci.vendor_id = (u16)pci_dev->vendor; + pci.device_id = (u16)pci_dev->device; + pci.subsys_vendor_id = (u16)(pci_dev->subsystem_vendor & 0xffff); + pci.subsys_device_id = (u16)(pci_dev->subsystem_device & 0xffff); + pci.device_number = pci_dev->devfn; + pci.interrupt = pci_dev->irq; + pci.p_os_data = pci_dev; + + hm.u.s.resource.bus_type = HPI_BUS_PCI; + hm.u.s.resource.r.pci = &pci; + + /* call CreateAdapterObject on the relevant hpi module */ + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + if (hr.error) + goto err; + + if (prealloc_stream_buf) { + adapter.p_buffer = vmalloc(prealloc_stream_buf); + if (!adapter.p_buffer) { + HPI_DEBUG_LOG(ERROR, + "HPI could not allocate " + "kernel buffer size %d\n", + prealloc_stream_buf); + goto err; + } + } + + adapter.index = hr.u.s.adapter_index; + adapter.type = hr.u.s.aw_adapter_list[adapter.index]; + hm.adapter_index = adapter.index; + + err = hpi_adapter_open(NULL, adapter.index); + if (err) + goto err; + + adapter.snd_card_asihpi = NULL; + /* WARNING can't init mutex in 'adapter' + * and then copy it to adapters[] ?!?! + */ + adapters[hr.u.s.adapter_index] = adapter; + mutex_init(&adapters[adapter.index].mutex); + pci_set_drvdata(pci_dev, &adapters[adapter.index]); + + printk(KERN_INFO "probe found adapter ASI%04X HPI index #%d.\n", + adapter.type, adapter.index); + + return 0; + +err: + for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) { + if (adapter.ap_remapped_mem_base[idx]) { + iounmap(adapter.ap_remapped_mem_base[idx]); + adapter.ap_remapped_mem_base[idx] = NULL; + } + } + + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); + } + + HPI_DEBUG_LOG(ERROR, "adapter_probe failed\n"); + return -ENODEV; +} + +void __devexit asihpi_adapter_remove(struct pci_dev *pci_dev) +{ + int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; + pa = (struct hpi_adapter *)pci_get_drvdata(pci_dev); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DELETE_ADAPTER); + hm.adapter_index = pa->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + + /* unmap PCI memory space, mapped during device init. */ + for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) { + if (pa->ap_remapped_mem_base[idx]) { + iounmap(pa->ap_remapped_mem_base[idx]); + pa->ap_remapped_mem_base[idx] = NULL; + } + } + + if (pa->p_buffer) { + pa->buffer_size = 0; + vfree(pa->p_buffer); + } + + pci_set_drvdata(pci_dev, NULL); + /* + printk(KERN_INFO "PCI device (%04x:%04x,%04x:%04x,%04x)," + " HPI index # %d, removed.\n", + pci_dev->vendor, pci_dev->device, + pci_dev->subsystem_vendor, + pci_dev->subsystem_device, pci_dev->devfn, + pa->index); + */ +} + +void __init asihpi_init(void) +{ + struct hpi_message hm; + struct hpi_response hr; + + memset(adapters, 0, sizeof(adapters)); + + printk(KERN_INFO "ASIHPI driver %d.%02d.%02d\n", + HPI_VER_MAJOR(HPI_VER), HPI_VER_MINOR(HPI_VER), + HPI_VER_RELEASE(HPI_VER)); + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_LOAD); + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); +} + +void asihpi_exit(void) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_DRIVER_UNLOAD); + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); +} diff --git a/sound/pci/asihpi/hpioctl.h b/sound/pci/asihpi/hpioctl.h new file mode 100644 index 00000000000..847f72f03fe --- /dev/null +++ b/sound/pci/asihpi/hpioctl.h @@ -0,0 +1,38 @@ +/******************************************************************************* + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +Linux HPI ioctl, and shared module init functions +*******************************************************************************/ + +int __devinit asihpi_adapter_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id); +void __devexit asihpi_adapter_remove(struct pci_dev *pci_dev); +void __init asihpi_init(void); +void __exit asihpi_exit(void); + +int asihpi_hpi_release(struct file *file); + +long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +/* This is called from hpifunc.c functions, called by ALSA + * (or other kernel process) In this case there is no file descriptor + * available for the message cache code + */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr); + +#define HOWNER_KERNEL ((void *)-1) diff --git a/sound/pci/asihpi/hpios.c b/sound/pci/asihpi/hpios.c new file mode 100644 index 00000000000..de615cfdb95 --- /dev/null +++ b/sound/pci/asihpi/hpios.c @@ -0,0 +1,114 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +HPI Operating System function implementation for Linux + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#define SOURCEFILE_NAME "hpios.c" +#include "hpi_internal.h" +#include "hpidebug.h" +#include +#include + +void hpios_delay_micro_seconds(u32 num_micro_sec) +{ + if ((usecs_to_jiffies(num_micro_sec) > 1) && !in_interrupt()) { + /* MUST NOT SCHEDULE IN INTERRUPT CONTEXT! */ + schedule_timeout_uninterruptible(usecs_to_jiffies + (num_micro_sec)); + } else if (num_micro_sec <= 2000) + udelay(num_micro_sec); + else + mdelay(num_micro_sec / 1000); + +} + +void hpios_locked_mem_init(void) +{ +} + +/** Allocated an area of locked memory for bus master DMA operations. + +On error, return -ENOMEM, and *pMemArea.size = 0 +*/ +u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_mem_area, u32 size, + struct pci_dev *pdev) +{ + /*?? any benefit in using managed dmam_alloc_coherent? */ + p_mem_area->vaddr = + dma_alloc_coherent(&pdev->dev, size, &p_mem_area->dma_handle, + GFP_DMA32 | GFP_KERNEL); + + if (p_mem_area->vaddr) { + HPI_DEBUG_LOG(DEBUG, "allocated %d bytes, dma 0x%x vma %p\n", + size, (unsigned int)p_mem_area->dma_handle, + p_mem_area->vaddr); + p_mem_area->pdev = &pdev->dev; + p_mem_area->size = size; + return 0; + } else { + HPI_DEBUG_LOG(WARNING, + "failed to allocate %d bytes locked memory\n", size); + p_mem_area->size = 0; + return -ENOMEM; + } +} + +u16 hpios_locked_mem_free(struct consistent_dma_area *p_mem_area) +{ + if (p_mem_area->size) { + dma_free_coherent(p_mem_area->pdev, p_mem_area->size, + p_mem_area->vaddr, p_mem_area->dma_handle); + HPI_DEBUG_LOG(DEBUG, "freed %lu bytes, dma 0x%x vma %p\n", + (unsigned long)p_mem_area->size, + (unsigned int)p_mem_area->dma_handle, + p_mem_area->vaddr); + p_mem_area->size = 0; + return 0; + } else { + return 1; + } +} + +void hpios_locked_mem_free_all(void) +{ +} + +void __iomem *hpios_map_io(struct pci_dev *pci_dev, int idx, + unsigned int length) +{ + HPI_DEBUG_LOG(DEBUG, "mapping %d %s %08llx-%08llx %04llx len 0x%x\n", + idx, pci_dev->resource[idx].name, + (unsigned long long)pci_resource_start(pci_dev, idx), + (unsigned long long)pci_resource_end(pci_dev, idx), + (unsigned long long)pci_resource_flags(pci_dev, idx), length); + + if (!(pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM)) { + HPI_DEBUG_LOG(ERROR, "not an io memory resource\n"); + return NULL; + } + + if (length > pci_resource_len(pci_dev, idx)) { + HPI_DEBUG_LOG(ERROR, "resource too small for requested %d \n", + length); + return NULL; + } + + return ioremap(pci_resource_start(pci_dev, idx), length); +} diff --git a/sound/pci/asihpi/hpios.h b/sound/pci/asihpi/hpios.h new file mode 100644 index 00000000000..a62c3f1e5f0 --- /dev/null +++ b/sound/pci/asihpi/hpios.h @@ -0,0 +1,178 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + +HPI Operating System Specific macros for Linux Kernel driver + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#ifndef _HPIOS_H_ +#define _HPIOS_H_ + +#undef HPI_OS_LINUX_KERNEL +#define HPI_OS_LINUX_KERNEL + +#define HPI_OS_DEFINED +#define HPI_KERNEL_MODE + +#define HPI_REASSIGN_DUPLICATE_ADAPTER_IDX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HPI_NO_OS_FILE_OPS + +#ifdef CONFIG_64BIT +#define HPI64BIT +#endif + +/** Details of a memory area allocated with pci_alloc_consistent +Need all info for parameters to pci_free_consistent +*/ +struct consistent_dma_area { + struct device *pdev; + /* looks like dma-mapping dma_devres ?! */ + size_t size; + void *vaddr; + dma_addr_t dma_handle; +}; + +static inline u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area + *locked_mem_handle, u32 *p_physical_addr) +{ + *p_physical_addr = locked_mem_handle->dma_handle; + return 0; +} + +static inline u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area + *locked_mem_handle, void **pp_virtual_addr) +{ + *pp_virtual_addr = locked_mem_handle->vaddr; + return 0; +} + +static inline u16 hpios_locked_mem_valid(struct consistent_dma_area + *locked_mem_handle) +{ + return locked_mem_handle->size != 0; +} + +struct hpi_ioctl_linux { + void __user *phm; + void __user *phr; +}; + +/* Conflict?: H is already used by a number of drivers hid, bluetooth hci, + and some sound drivers sb16, hdsp, emu10k. AFAIK 0xFC is ununsed command +*/ +#define HPI_IOCTL_LINUX _IOWR('H', 0xFC, struct hpi_ioctl_linux) + +#define HPI_DEBUG_FLAG_ERROR KERN_ERR +#define HPI_DEBUG_FLAG_WARNING KERN_WARNING +#define HPI_DEBUG_FLAG_NOTICE KERN_NOTICE +#define HPI_DEBUG_FLAG_INFO KERN_INFO +#define HPI_DEBUG_FLAG_DEBUG KERN_DEBUG +#define HPI_DEBUG_FLAG_VERBOSE KERN_DEBUG /* kernel has no verbose */ + +#include + +#define HPI_LOCKING + +struct hpios_spinlock { + spinlock_t lock; /* SEE hpios_spinlock */ + int lock_context; +}; + +/* The reason for all this evilness is that ALSA calls some of a drivers + * operators in atomic context, and some not. But all our functions channel + * through the HPI_Message conduit, so we can't handle the different context + * per function + */ +#define IN_LOCK_BH 1 +#define IN_LOCK_IRQ 0 +static inline void cond_lock(struct hpios_spinlock *l) +{ + if (irqs_disabled()) { + /* NO bh or isr can execute on this processor, + so ordinary lock will do + */ + spin_lock(&((l)->lock)); + l->lock_context = IN_LOCK_IRQ; + } else { + spin_lock_bh(&((l)->lock)); + l->lock_context = IN_LOCK_BH; + } +} + +static inline void cond_unlock(struct hpios_spinlock *l) +{ + if (l->lock_context == IN_LOCK_BH) + spin_unlock_bh(&((l)->lock)); + else + spin_unlock(&((l)->lock)); +} + +#define hpios_msgxlock_init(obj) spin_lock_init(&(obj)->lock) +#define hpios_msgxlock_lock(obj) cond_lock(obj) +#define hpios_msgxlock_un_lock(obj) cond_unlock(obj) + +#define hpios_dsplock_init(obj) spin_lock_init(&(obj)->dsp_lock.lock) +#define hpios_dsplock_lock(obj) cond_lock(&(obj)->dsp_lock) +#define hpios_dsplock_unlock(obj) cond_unlock(&(obj)->dsp_lock) + +#ifdef CONFIG_SND_DEBUG +#define HPI_DEBUG +#endif + +#define HPI_ALIST_LOCKING +#define hpios_alistlock_init(obj) spin_lock_init(&((obj)->list_lock.lock)) +#define hpios_alistlock_lock(obj) spin_lock(&((obj)->list_lock.lock)) +#define hpios_alistlock_un_lock(obj) spin_unlock(&((obj)->list_lock.lock)) + +struct hpi_adapter { + /* mutex prevents contention for one card + between multiple user programs (via ioctl) */ + struct mutex mutex; + u16 index; + u16 type; + + /* ALSA card structure */ + void *snd_card_asihpi; + + char *p_buffer; + size_t buffer_size; + struct pci_dev *pci; + void __iomem *ap_remapped_mem_base[HPI_MAX_ADAPTER_MEM_SPACES]; +}; + +static inline void hpios_unmap_io(void __iomem *addr, + unsigned long size) +{ + iounmap(addr); +} + +void __iomem *hpios_map_io(struct pci_dev *pci_dev, int idx, + unsigned int length); + +#endif diff --git a/sound/pci/asihpi/hpipcida.h b/sound/pci/asihpi/hpipcida.h new file mode 100644 index 00000000000..bb30868ce1a --- /dev/null +++ b/sound/pci/asihpi/hpipcida.h @@ -0,0 +1,37 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2010 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation; + + 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 + + Array initializer for PCI card IDs + +(C) Copyright AudioScience Inc. 1998-2003 +*******************************************************************************/ + +/*NOTE: when adding new lines to this header file + they MUST be grouped by HPI entry point. +*/ + +{ +HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, + (kernel_ulong_t) HPI_6205} +, { +HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040, + HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0, + (kernel_ulong_t) HPI_6000} +, { +0} -- cgit v1.2.3-18-g5258 From 20133d4cd329af7a02ee5af36bba1796d5ff7b1d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 21 Apr 2010 11:04:07 -0400 Subject: ALSA: snd-meastro3: Document hardware volume control a bit While working on a fix for the volume being muted on the allegro in my Compaq EVO N600C after suspend, I've learned a few things about the hardware volume control worth documenting. The actual fix for the suspend / resume issue is in the next patch in this set. Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/maestro3.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index b56e3367678..53d2a5d61ba 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -1598,6 +1598,10 @@ static void snd_m3_update_ptr(struct snd_m3 *chip, struct m3_dma *s) } } +/* The m3's hardware volume works by incrementing / decrementing 2 counters + (without wrap around) in response to volume button presses and then + generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7 + of a byte wide register. The meaning of bits 0 and 4 is unknown. */ static void snd_m3_update_hw_volume(unsigned long private_data) { struct snd_m3 *chip = (struct snd_m3 *) private_data; @@ -1609,7 +1613,15 @@ static void snd_m3_update_hw_volume(unsigned long private_data) values. */ x = inb(chip->iobase + SHADOW_MIX_REG_VOICE) & 0xee; - /* Reset the volume control registers. */ + /* Reset the volume counters to 4. Tests on the allegro integrated + into a Compaq N600C laptop, have revealed that: + 1) Writing any value will result in the 2 counters being reset to + 4 so writing 0x88 is not strictly necessary + 2) Writing to any of the 4 involved registers will reset all 4 + of them (and reading them always returns the same value for all + of them) + It could be that a maestro deviates from this, so leave the code + as is. */ outb(0x88, chip->iobase + SHADOW_MIX_REG_VOICE); outb(0x88, chip->iobase + HW_VOL_COUNTER_VOICE); outb(0x88, chip->iobase + SHADOW_MIX_REG_MASTER); @@ -1629,7 +1641,9 @@ static void snd_m3_update_hw_volume(unsigned long private_data) val = chip->ac97->regs[AC97_MASTER_VOL]; switch (x) { case 0x88: - /* mute */ + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ val ^= 0x8000; chip->ac97->regs[AC97_MASTER_VOL] = val; outw(val, chip->iobase + CODEC_DATA); @@ -1638,7 +1652,7 @@ static void snd_m3_update_hw_volume(unsigned long private_data) &chip->master_switch->id); break; case 0xaa: - /* volume up */ + /* counters increased by 1 -> volume up */ if ((val & 0x7f) > 0) val--; if ((val & 0x7f00) > 0) @@ -1650,7 +1664,7 @@ static void snd_m3_update_hw_volume(unsigned long private_data) &chip->master_volume->id); break; case 0x66: - /* volume down */ + /* counters decreased by 1 -> volume down */ if ((val & 0x7f) < 0x1f) val++; if ((val & 0x7f00) < 0x1f00) -- cgit v1.2.3-18-g5258 From eb581adf25fe9e42197e591926de85459e68b9fd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 23 Apr 2010 05:26:42 -0400 Subject: ALSA: snd-maestro3: Make hardware volume buttons an input device (rev2) While working on the sound suspend / resume problems with my laptop I noticed that the hardware volume handling code in essence just detects key presses, and then does some hardcoded modification of the master volume based on which key is pressed. This made me think that clearly the right thing to do here is just report these keypresses to userspace and let userspace decide what to with them. This patch adds a Kconfig option which when enabled reports the volume buttons as keypresses using an input device. When enabled this option also gets rid of the ugly direct ac97 writes from the tasklet, the ac97lock and the need for using a tasklet in general. As an added bonus the keys now work identical to volume keys on a (usb) keyboard with multimedia keys, providing visual feedback of the volume level change, and a better range of the volume control (with a properly configured desktop environment). Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/Kconfig | 10 +++++ sound/pci/maestro3.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 517ae65ffc8..ebcf065ad2c 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -667,6 +667,16 @@ config SND_MAESTRO3 To compile this driver as a module, choose M here: the module will be called snd-maestro3. +config SND_MAESTRO3_INPUT + bool "Enable input device for maestro3 volume buttons" + depends on SND_MAESTRO3 + depends on INPUT=y || INPUT=SND_MAESTRO3 + help + If you say Y here, you will get an input device which reports + keypresses for the volume buttons connected to the maestro3 chip. + If you say N the buttons will directly control the master volume. + It is recommended to say Y. + config SND_MIXART tristate "Digigram miXart" select SND_HWDEP diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index 53d2a5d61ba..217a4dcb259 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -844,11 +845,17 @@ struct snd_m3 { struct m3_dma *substreams; spinlock_t reg_lock; - spinlock_t ac97_lock; +#ifdef CONFIG_SND_MAESTRO3_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ +#else + spinlock_t ac97_lock; struct snd_kcontrol *master_switch; struct snd_kcontrol *master_volume; struct tasklet_struct hwvol_tq; +#endif + unsigned int in_suspend; #ifdef CONFIG_PM @@ -1606,7 +1613,9 @@ static void snd_m3_update_hw_volume(unsigned long private_data) { struct snd_m3 *chip = (struct snd_m3 *) private_data; int x, val; +#ifndef CONFIG_SND_MAESTRO3_INPUT unsigned long flags; +#endif /* Figure out which volume control button was pushed, based on differences from the default register @@ -1632,6 +1641,7 @@ static void snd_m3_update_hw_volume(unsigned long private_data) if (chip->in_suspend) return; +#ifndef CONFIG_SND_MAESTRO3_INPUT if (!chip->master_switch || !chip->master_volume) return; @@ -1677,6 +1687,35 @@ static void snd_m3_update_hw_volume(unsigned long private_data) break; } spin_unlock_irqrestore(&chip->ac97_lock, flags); +#else + if (!chip->input_dev) + return; + + val = 0; + switch (x) { + case 0x88: + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ + val = KEY_MUTE; + break; + case 0xaa: + /* counters increased by 1 -> volume up */ + val = KEY_VOLUMEUP; + break; + case 0x66: + /* counters decreased by 1 -> volume down */ + val = KEY_VOLUMEDOWN; + break; + } + + if (val) { + input_report_key(chip->input_dev, val, 1); + input_sync(chip->input_dev); + input_report_key(chip->input_dev, val, 0); + input_sync(chip->input_dev); + } +#endif } static irqreturn_t snd_m3_interrupt(int irq, void *dev_id) @@ -1691,7 +1730,11 @@ static irqreturn_t snd_m3_interrupt(int irq, void *dev_id) return IRQ_NONE; if (status & HV_INT_PENDING) +#ifdef CONFIG_SND_MAESTRO3_INPUT + snd_m3_update_hw_volume((unsigned long)chip); +#else tasklet_schedule(&chip->hwvol_tq); +#endif /* * ack an assp int if its running @@ -1957,18 +2000,24 @@ static unsigned short snd_m3_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { struct snd_m3 *chip = ac97->private_data; +#ifndef CONFIG_SND_MAESTRO3_INPUT unsigned long flags; +#endif unsigned short data = 0xffff; if (snd_m3_ac97_wait(chip)) goto fail; +#ifndef CONFIG_SND_MAESTRO3_INPUT spin_lock_irqsave(&chip->ac97_lock, flags); +#endif snd_m3_outb(chip, 0x80 | (reg & 0x7f), CODEC_COMMAND); if (snd_m3_ac97_wait(chip)) goto fail_unlock; data = snd_m3_inw(chip, CODEC_DATA); fail_unlock: +#ifndef CONFIG_SND_MAESTRO3_INPUT spin_unlock_irqrestore(&chip->ac97_lock, flags); +#endif fail: return data; } @@ -1977,14 +2026,20 @@ static void snd_m3_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { struct snd_m3 *chip = ac97->private_data; +#ifndef CONFIG_SND_MAESTRO3_INPUT unsigned long flags; +#endif if (snd_m3_ac97_wait(chip)) return; +#ifndef CONFIG_SND_MAESTRO3_INPUT spin_lock_irqsave(&chip->ac97_lock, flags); +#endif snd_m3_outw(chip, val, CODEC_DATA); snd_m3_outb(chip, reg & 0x7f, CODEC_COMMAND); +#ifndef CONFIG_SND_MAESTRO3_INPUT spin_unlock_irqrestore(&chip->ac97_lock, flags); +#endif } @@ -2091,7 +2146,9 @@ static int __devinit snd_m3_mixer(struct snd_m3 *chip) { struct snd_ac97_bus *pbus; struct snd_ac97_template ac97; +#ifndef CONFIG_SND_MAESTRO3_INPUT struct snd_ctl_elem_id elem_id; +#endif int err; static struct snd_ac97_bus_ops ops = { .write = snd_m3_ac97_write, @@ -2111,6 +2168,7 @@ static int __devinit snd_m3_mixer(struct snd_m3 *chip) schedule_timeout_uninterruptible(msecs_to_jiffies(100)); snd_ac97_write(chip->ac97, AC97_PCM, 0); +#ifndef CONFIG_SND_MAESTRO3_INPUT memset(&elem_id, 0, sizeof(elem_id)); elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(elem_id.name, "Master Playback Switch"); @@ -2119,6 +2177,7 @@ static int __devinit snd_m3_mixer(struct snd_m3 *chip) elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(elem_id.name, "Master Playback Volume"); chip->master_volume = snd_ctl_find_id(chip->card, &elem_id); +#endif return 0; } @@ -2398,6 +2457,11 @@ static int snd_m3_free(struct snd_m3 *chip) struct m3_dma *s; int i; +#ifdef CONFIG_SND_MAESTRO3_INPUT + if (chip->input_dev) + input_unregister_device(chip->input_dev); +#endif + if (chip->substreams) { spin_lock_irq(&chip->reg_lock); for (i = 0; i < chip->num_substreams; i++) { @@ -2524,6 +2588,41 @@ static int m3_resume(struct pci_dev *pci) } #endif /* CONFIG_PM */ +#ifdef CONFIG_SND_MAESTRO3_INPUT +static int __devinit snd_m3_input_register(struct snd_m3 *chip) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0", + pci_name(chip->pci)); + + input_dev->name = chip->card->driver; + input_dev->phys = chip->phys; + input_dev->id.bustype = BUS_PCI; + input_dev->id.vendor = chip->pci->vendor; + input_dev->id.product = chip->pci->device; + input_dev->dev.parent = &chip->pci->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_MUTE, input_dev->keybit); + __set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + __set_bit(KEY_VOLUMEUP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + chip->input_dev = input_dev; + return 0; +} +#endif /* CONFIG_INPUT */ /* */ @@ -2567,7 +2666,9 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, } spin_lock_init(&chip->reg_lock); +#ifndef CONFIG_SND_MAESTRO3_INPUT spin_lock_init(&chip->ac97_lock); +#endif switch (pci->device) { case PCI_DEVICE_ID_ESS_ALLEGRO: @@ -2650,7 +2751,9 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, snd_m3_hv_init(chip); +#ifndef CONFIG_SND_MAESTRO3_INPUT tasklet_init(&chip->hwvol_tq, snd_m3_update_hw_volume, (unsigned long)chip); +#endif if (request_irq(pci->irq, snd_m3_interrupt, IRQF_SHARED, card->driver, chip)) { @@ -2682,7 +2785,16 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, if ((err = snd_m3_pcm(chip, 0)) < 0) return err; - + +#ifdef CONFIG_SND_MAESTRO3_INPUT + if (chip->hv_config & HV_CTRL_ENABLE) { + err = snd_m3_input_register(chip); + if (err) + snd_printk(KERN_WARNING "Input device registration " + "failed with error %i", err); + } +#endif + snd_m3_enable_ints(chip); snd_m3_assp_continue(chip); -- cgit v1.2.3-18-g5258 From 5a5e02e5095ed89a0a1f4031e7440078c209442b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 23 Apr 2010 05:26:43 -0400 Subject: ALSA: snd-es1968: Make hardware volume buttons an input device (rev2) The hardware volume handling code in essence just detects key presses, and then does some hardcoded modification of the master volume based on which key is pressed. Clearly the right thing to do here is just report these keypresses to userspace and let userspace decide what to with them. This patch adds a Kconfig option which when enabled reports the volume buttons as keypresses using an input device. When enabled this option also gets rid of the ugly direct ac97 writes from the tasklet, the ac97lock and the need for using a tasklet in general. Signed-off-by: Hans de Goede Signed-off-by: Takashi Iwai --- sound/pci/Kconfig | 10 +++++ sound/pci/es1968.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 128 insertions(+), 10 deletions(-) diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index ebcf065ad2c..e7a8cd058ef 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -513,6 +513,16 @@ config SND_ES1968 To compile this driver as a module, choose M here: the module will be called snd-es1968. +config SND_ES1968_INPUT + bool "Enable input device for es1968 volume buttons" + depends on SND_ES1968 + depends on INPUT=y || INPUT=SND_ES1968 + help + If you say Y here, you will get an input device which reports + keypresses for the volume buttons connected to the es1968 chip. + If you say N the buttons will directly control the master volume. + It is recommended to say Y. + config SND_FM801 tristate "ForteMedia FM801" select SND_OPL3_LIB diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index ecaea9fb48e..aa973cee815 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -104,6 +104,7 @@ #include #include #include +#include #include #include @@ -517,14 +518,9 @@ struct es1968 { /* ALSA Stuff */ struct snd_ac97 *ac97; - struct snd_kcontrol *master_switch; /* for h/w volume control */ - struct snd_kcontrol *master_volume; - struct snd_rawmidi *rmidi; spinlock_t reg_lock; - spinlock_t ac97_lock; - struct tasklet_struct hwvol_tq; unsigned int in_suspend; /* Maestro Stuff */ @@ -547,6 +543,16 @@ struct es1968 { #ifdef SUPPORT_JOYSTICK struct gameport *gameport; #endif + +#ifdef CONFIG_SND_ES1968_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ +#else + struct snd_kcontrol *master_switch; /* for h/w volume control */ + struct snd_kcontrol *master_volume; + spinlock_t ac97_lock; + struct tasklet_struct hwvol_tq; +#endif }; static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id); @@ -632,28 +638,38 @@ static int snd_es1968_ac97_wait_poll(struct es1968 *chip) static void snd_es1968_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { struct es1968 *chip = ac97->private_data; +#ifndef CONFIG_SND_ES1968_INPUT unsigned long flags; +#endif snd_es1968_ac97_wait(chip); /* Write the bus */ +#ifndef CONFIG_SND_ES1968_INPUT spin_lock_irqsave(&chip->ac97_lock, flags); +#endif outw(val, chip->io_port + ESM_AC97_DATA); /*msleep(1);*/ outb(reg, chip->io_port + ESM_AC97_INDEX); /*msleep(1);*/ +#ifndef CONFIG_SND_ES1968_INPUT spin_unlock_irqrestore(&chip->ac97_lock, flags); +#endif } static unsigned short snd_es1968_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { u16 data = 0; struct es1968 *chip = ac97->private_data; +#ifndef CONFIG_SND_ES1968_INPUT unsigned long flags; +#endif snd_es1968_ac97_wait(chip); +#ifndef CONFIG_SND_ES1968_INPUT spin_lock_irqsave(&chip->ac97_lock, flags); +#endif outb(reg | 0x80, chip->io_port + ESM_AC97_INDEX); /*msleep(1);*/ @@ -661,7 +677,9 @@ static unsigned short snd_es1968_ac97_read(struct snd_ac97 *ac97, unsigned short data = inw(chip->io_port + ESM_AC97_DATA); /*msleep(1);*/ } +#ifndef CONFIG_SND_ES1968_INPUT spin_unlock_irqrestore(&chip->ac97_lock, flags); +#endif return data; } @@ -1874,13 +1892,17 @@ static void snd_es1968_update_pcm(struct es1968 *chip, struct esschan *es) } } -/* - */ +/* The hardware volume works by incrementing / decrementing 2 counters + (without wrap around) in response to volume button presses and then + generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7 + of a byte wide register. The meaning of bits 0 and 4 is unknown. */ static void es1968_update_hw_volume(unsigned long private_data) { struct es1968 *chip = (struct es1968 *) private_data; int x, val; +#ifndef CONFIG_SND_ES1968_INPUT unsigned long flags; +#endif /* Figure out which volume control button was pushed, based on differences from the default register @@ -1895,6 +1917,7 @@ static void es1968_update_hw_volume(unsigned long private_data) if (chip->in_suspend) return; +#ifndef CONFIG_SND_ES1968_INPUT if (! chip->master_switch || ! chip->master_volume) return; @@ -1937,6 +1960,35 @@ static void es1968_update_hw_volume(unsigned long private_data) break; } spin_unlock_irqrestore(&chip->ac97_lock, flags); +#else + if (!chip->input_dev) + return; + + val = 0; + switch (x) { + case 0x88: + /* The counters have not changed, yet we've received a HV + interrupt. According to tests run by various people this + happens when pressing the mute button. */ + val = KEY_MUTE; + break; + case 0xaa: + /* counters increased by 1 -> volume up */ + val = KEY_VOLUMEUP; + break; + case 0x66: + /* counters decreased by 1 -> volume down */ + val = KEY_VOLUMEDOWN; + break; + } + + if (val) { + input_report_key(chip->input_dev, val, 1); + input_sync(chip->input_dev); + input_report_key(chip->input_dev, val, 0); + input_sync(chip->input_dev); + } +#endif } /* @@ -1953,7 +2005,11 @@ static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id) outw(inw(chip->io_port + 4) & 1, chip->io_port + 4); if (event & ESM_HWVOL_IRQ) +#ifdef CONFIG_SND_ES1968_INPUT + es1968_update_hw_volume((unsigned long)chip); +#else tasklet_schedule(&chip->hwvol_tq); /* we'll do this later */ +#endif /* else ack 'em all, i imagine */ outb(0xFF, chip->io_port + 0x1A); @@ -1993,7 +2049,9 @@ snd_es1968_mixer(struct es1968 *chip) { struct snd_ac97_bus *pbus; struct snd_ac97_template ac97; +#ifndef CONFIG_SND_ES1968_INPUT struct snd_ctl_elem_id elem_id; +#endif int err; static struct snd_ac97_bus_ops ops = { .write = snd_es1968_ac97_write, @@ -2009,6 +2067,7 @@ snd_es1968_mixer(struct es1968 *chip) if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0) return err; +#ifndef CONFIG_SND_ES1968_INPUT /* attach master switch / volumes for h/w volume control */ memset(&elem_id, 0, sizeof(elem_id)); elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; @@ -2018,6 +2077,7 @@ snd_es1968_mixer(struct es1968 *chip) elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(elem_id.name, "Master Playback Volume"); chip->master_volume = snd_ctl_find_id(chip->card, &elem_id); +#endif return 0; } @@ -2474,8 +2534,49 @@ static inline int snd_es1968_create_gameport(struct es1968 *chip, int dev) { ret static inline void snd_es1968_free_gameport(struct es1968 *chip) { } #endif +#ifdef CONFIG_SND_ES1968_INPUT +static int __devinit snd_es1968_input_register(struct es1968 *chip) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0", + pci_name(chip->pci)); + + input_dev->name = chip->card->driver; + input_dev->phys = chip->phys; + input_dev->id.bustype = BUS_PCI; + input_dev->id.vendor = chip->pci->vendor; + input_dev->id.product = chip->pci->device; + input_dev->dev.parent = &chip->pci->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_MUTE, input_dev->keybit); + __set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + __set_bit(KEY_VOLUMEUP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + chip->input_dev = input_dev; + return 0; +} +#endif /* CONFIG_SND_ES1968_INPUT */ + static int snd_es1968_free(struct es1968 *chip) { +#ifdef CONFIG_SND_ES1968_INPUT + if (chip->input_dev) + input_unregister_device(chip->input_dev); +#endif + if (chip->io_port) { if (chip->irq >= 0) synchronize_irq(chip->irq); @@ -2486,8 +2587,6 @@ static int snd_es1968_free(struct es1968 *chip) if (chip->irq >= 0) free_irq(chip->irq, chip); snd_es1968_free_gameport(chip); - chip->master_switch = NULL; - chip->master_volume = NULL; pci_release_regions(chip->pci); pci_disable_device(chip->pci); kfree(chip); @@ -2558,9 +2657,11 @@ static int __devinit snd_es1968_create(struct snd_card *card, spin_lock_init(&chip->substream_lock); INIT_LIST_HEAD(&chip->buf_list); INIT_LIST_HEAD(&chip->substream_list); - spin_lock_init(&chip->ac97_lock); mutex_init(&chip->memory_mutex); +#ifndef CONFIG_SND_ES1968_INPUT + spin_lock_init(&chip->ac97_lock); tasklet_init(&chip->hwvol_tq, es1968_update_hw_volume, (unsigned long)chip); +#endif chip->card = card; chip->pci = pci; chip->irq = -1; @@ -2713,6 +2814,13 @@ static int __devinit snd_es1968_probe(struct pci_dev *pci, snd_es1968_create_gameport(chip, dev); +#ifdef CONFIG_SND_ES1968_INPUT + err = snd_es1968_input_register(chip); + if (err) + snd_printk(KERN_WARNING "Input device registration " + "failed with error %i", err); +#endif + snd_es1968_start_irq(chip); chip->clock = clock[dev]; -- cgit v1.2.3-18-g5258 From d6bdc0f7fe561a4b08b280c2b2add2db3747e64b Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Wed, 21 Apr 2010 17:36:48 +0800 Subject: ASoC: ad193x: fix wrong register setting in ad193x_set_dai_fmt Signed-off-by: Barry Song <21cnbao@gmail.com> Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ad193x.c | 48 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index 7ed787e2e51..45dfc90540b 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -163,9 +163,10 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - int adc_reg, dac_reg; + int adc_reg1, adc_reg2, dac_reg; - adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2); + adc_reg1 = snd_soc_read(codec, AD193X_ADC_CTRL1); + adc_reg2 = snd_soc_read(codec, AD193X_ADC_CTRL2); dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1); /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S @@ -173,12 +174,12 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - adc_reg &= ~AD193X_ADC_SERFMT_MASK; - adc_reg |= AD193X_ADC_SERFMT_TDM; + adc_reg1 &= ~AD193X_ADC_SERFMT_MASK; + adc_reg1 |= AD193X_ADC_SERFMT_TDM; break; case SND_SOC_DAIFMT_DSP_A: - adc_reg &= ~AD193X_ADC_SERFMT_MASK; - adc_reg |= AD193X_ADC_SERFMT_AUX; + adc_reg1 &= ~AD193X_ADC_SERFMT_MASK; + adc_reg1 |= AD193X_ADC_SERFMT_AUX; break; default: return -EINVAL; @@ -186,27 +187,27 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ - adc_reg &= ~AD193X_ADC_LEFT_HIGH; - adc_reg &= ~AD193X_ADC_BCLK_INV; + adc_reg2 &= ~AD193X_ADC_LEFT_HIGH; + adc_reg2 &= ~AD193X_ADC_BCLK_INV; dac_reg &= ~AD193X_DAC_LEFT_HIGH; dac_reg &= ~AD193X_DAC_BCLK_INV; break; case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ - adc_reg |= AD193X_ADC_LEFT_HIGH; - adc_reg &= ~AD193X_ADC_BCLK_INV; + adc_reg2 |= AD193X_ADC_LEFT_HIGH; + adc_reg2 &= ~AD193X_ADC_BCLK_INV; dac_reg |= AD193X_DAC_LEFT_HIGH; dac_reg &= ~AD193X_DAC_BCLK_INV; break; case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ - adc_reg &= ~AD193X_ADC_LEFT_HIGH; - adc_reg |= AD193X_ADC_BCLK_INV; + adc_reg2 &= ~AD193X_ADC_LEFT_HIGH; + adc_reg2 |= AD193X_ADC_BCLK_INV; dac_reg &= ~AD193X_DAC_LEFT_HIGH; dac_reg |= AD193X_DAC_BCLK_INV; break; case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ - adc_reg |= AD193X_ADC_LEFT_HIGH; - adc_reg |= AD193X_ADC_BCLK_INV; + adc_reg2 |= AD193X_ADC_LEFT_HIGH; + adc_reg2 |= AD193X_ADC_BCLK_INV; dac_reg |= AD193X_DAC_LEFT_HIGH; dac_reg |= AD193X_DAC_BCLK_INV; break; @@ -216,26 +217,26 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ - adc_reg |= AD193X_ADC_LCR_MASTER; - adc_reg |= AD193X_ADC_BCLK_MASTER; + adc_reg2 |= AD193X_ADC_LCR_MASTER; + adc_reg2 |= AD193X_ADC_BCLK_MASTER; dac_reg |= AD193X_DAC_LCR_MASTER; dac_reg |= AD193X_DAC_BCLK_MASTER; break; case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ - adc_reg |= AD193X_ADC_LCR_MASTER; - adc_reg &= ~AD193X_ADC_BCLK_MASTER; + adc_reg2 |= AD193X_ADC_LCR_MASTER; + adc_reg2 &= ~AD193X_ADC_BCLK_MASTER; dac_reg |= AD193X_DAC_LCR_MASTER; dac_reg &= ~AD193X_DAC_BCLK_MASTER; break; case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ - adc_reg &= ~AD193X_ADC_LCR_MASTER; - adc_reg |= AD193X_ADC_BCLK_MASTER; + adc_reg2 &= ~AD193X_ADC_LCR_MASTER; + adc_reg2 |= AD193X_ADC_BCLK_MASTER; dac_reg &= ~AD193X_DAC_LCR_MASTER; dac_reg |= AD193X_DAC_BCLK_MASTER; break; case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ - adc_reg &= ~AD193X_ADC_LCR_MASTER; - adc_reg &= ~AD193X_ADC_BCLK_MASTER; + adc_reg2 &= ~AD193X_ADC_LCR_MASTER; + adc_reg2 &= ~AD193X_ADC_BCLK_MASTER; dac_reg &= ~AD193X_DAC_LCR_MASTER; dac_reg &= ~AD193X_DAC_BCLK_MASTER; break; @@ -243,7 +244,8 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg); + snd_soc_write(codec, AD193X_ADC_CTRL1, adc_reg1); + snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg2); snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg); return 0; -- cgit v1.2.3-18-g5258 From ba0a24e7387bb56425da07ed858ada37896ed36a Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Wed, 21 Apr 2010 17:36:49 +0800 Subject: ASoC: ad193x: fix typo, delete redundant space Signed-off-by: Barry Song <21cnbao@gmail.com> Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ad193x.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index 45dfc90540b..c8ca1142b2f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -46,13 +46,13 @@ static const struct soc_enum ad193x_deemp_enum = static const struct snd_kcontrol_new ad193x_snd_controls[] = { /* DAC volume control */ - SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL, + SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL, AD193X_DAC_R1_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL, + SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL, AD193X_DAC_R2_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL, + SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL, AD193X_DAC_R3_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL, + SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL, AD193X_DAC_R4_VOL, 0, 0xFF, 1), /* ADC switch control */ -- cgit v1.2.3-18-g5258 From 3a278a0c65c924950c4744ba451a110998998044 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 29 Mar 2010 20:31:14 +0100 Subject: ASoC: Allow reporting of NULL jacks Follow the core jack implementation and allow reporting on the status of NULL jacks, avoiding the need to check in detection implementations. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-jack.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index f8fd22cc70b..29159e1781d 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -64,10 +64,9 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) int enable; int oldstatus; - if (!jack) { - WARN_ON_ONCE(!jack); + if (!jack) return; - } + codec = jack->card->codec; mutex_lock(&codec->mutex); -- cgit v1.2.3-18-g5258 From f4d593280652025f797d432e90f49d49b9334689 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Apr 2010 10:09:57 +0300 Subject: ASoC: tlv320dac33: Fix for early interrupt in FIFO Mode1 Alarm threshold interrupt is triggered right after the playback start. This interrupt is recieved during the first burst period, and caused the state machine to write additional nSample command, which has to be avoided. To fix this issue move the DAC33 interrupt unmasking after we configured the PREFILL register with a small delay. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 824bb354ebc..520377bdb61 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -557,9 +557,13 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) switch (dac33->fifo_mode) { case DAC33_FIFO_MODE1: dac33_write16(codec, DAC33_NSAMPLE_MSB, - DAC33_THRREG(dac33->nsample)); + DAC33_THRREG(dac33->nsample + dac33->alarm_threshold)); dac33_write16(codec, DAC33_PREFILL_MSB, DAC33_THRREG(dac33->alarm_threshold)); + /* Enable Alarm Threshold IRQ with a delay */ + udelay(SAMPLES_TO_US(dac33->burst_rate, + dac33->alarm_threshold)); + dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT); break; case DAC33_FIFO_MODE7: dac33_write16(codec, DAC33_PREFILL_MSB, @@ -782,7 +786,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) case DAC33_FIFO_MODE1: dac33_write(codec, DAC33_FIFO_IRQ_MODE_B, DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL)); - dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT); break; case DAC33_FIFO_MODE7: /* Disable all interrupts */ -- cgit v1.2.3-18-g5258 From 55abb59c9a03d3aceda61e97b5ff19d9274fc611 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Apr 2010 10:09:58 +0300 Subject: ASoC: tlv320dac33: Skip calculations in FIFO Bypass mode There is no need for calculations for FIFO bypass mode. Just in case set the nsample maximum limit, which has been done in the calculation phase. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 520377bdb61..b101cbd9574 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -889,6 +889,10 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int nsample_limit; + /* In bypass mode we don't need to calculate */ + if (!dac33->fifo_mode) + return; + /* Number of samples (16bit, stereo) in one period */ dac33->nsample_min = snd_pcm_lib_period_bytes(substream) / 4; @@ -1244,6 +1248,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, dac33->keep_bclk = pdata->keep_bclk; dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; + dac33->nsample_max = NSAMPLE_MAX; /* Disable FIFO use by default */ dac33->fifo_mode = DAC33_FIFO_BYPASS; -- cgit v1.2.3-18-g5258 From 4260393e71f1e99c2f4fcde10fcb98fe00188d21 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Apr 2010 10:09:59 +0300 Subject: ASoC: tlv320dac33: Change magic numbers used in Mode7 Upper and Lower threshold values are used as magic numbers. Replace them with defines for later use. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index b101cbd9574..fa1994262c9 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -50,6 +50,9 @@ #define LATENCY_TIME_MS 20 +#define MODE7_LTHR 10 +#define MODE7_UTHR (DAC33_BUFFER_SIZE_SAMPLES - 10) + static struct snd_soc_codec *tlv320dac33_codec; enum dac33_state { @@ -567,7 +570,7 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) break; case DAC33_FIFO_MODE7: dac33_write16(codec, DAC33_PREFILL_MSB, - DAC33_THRREG(10)); + DAC33_THRREG(MODE7_LTHR)); break; default: dev_warn(codec->dev, "Unhandled FIFO mode: %d\n", @@ -867,10 +870,8 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) * Configure the threshold levels, and leave 10 sample space * at the bottom, and also at the top of the FIFO */ - dac33_write16(codec, DAC33_UTHR_MSB, - DAC33_THRREG(DAC33_BUFFER_SIZE_SAMPLES - 10)); - dac33_write16(codec, DAC33_LTHR_MSB, - DAC33_THRREG(10)); + dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(MODE7_UTHR)); + dac33_write16(codec, DAC33_LTHR_MSB, DAC33_THRREG(MODE7_LTHR)); break; default: break; -- cgit v1.2.3-18-g5258 From 76f471274dc9acacd521f151ac9171fd7bbc34c3 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Apr 2010 10:10:00 +0300 Subject: ASoC: tlv320dac33: Calculate the interface speed during bursts When the DAC33 FIFO is in use the dai interface is running in much higher speed than the sampling frequency. Calculate the rate based on the internal base frequency and the bclk divider. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index fa1994262c9..a8eb19b9b6c 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -53,6 +53,8 @@ #define MODE7_LTHR 10 #define MODE7_UTHR (DAC33_BUFFER_SIZE_SAMPLES - 10) +#define BURST_BASEFREQ_HZ 49152000 + static struct snd_soc_codec *tlv320dac33_codec; enum dac33_state { @@ -95,6 +97,7 @@ struct tlv320dac33_priv { enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */ unsigned int nsample; /* burst read amount from host */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ + unsigned int burst_rate; /* Interface speed in Burst modes */ int keep_bclk; /* Keep the BCLK continuously running * in FIFO modes */ @@ -1246,6 +1249,8 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, dac33->power_gpio = pdata->power_gpio; dac33->burst_bclkdiv = pdata->burst_bclkdiv; + /* Pre calculate the burst rate */ + dac33->burst_rate = BURST_BASEFREQ_HZ / dac33->burst_bclkdiv / 32; dac33->keep_bclk = pdata->keep_bclk; dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; -- cgit v1.2.3-18-g5258 From f57d2cfaad0d6858d700b5671e01cf3aba6ef779 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 23 Apr 2010 10:10:01 +0300 Subject: ASoC: tlv320dac33: FIFO caused delay reporting Delay reporting for the three implemented DAC33 FIFO modes. DAC33 has FIFO depth status register(s), but it can not be used, since inside of pcm_pointer we can not send I2C commands. Timestamp based estimation need to be used. The method of calculating the delay depends on the active FIFO mode. Bypass mode: FIFO is bypassed, report 0 as delay Mode1: nSample fill mode. In this mode I need to use two timestamp ts1: taken when the interrupt has been received ts2: taken before writing to nSample register. Interrupts are coming when DAC33 FIFO depth goes under alarm threshold. Phase1: when we received the alarm threshold, but our workqueue has not been executed (safeguard phase). Just count the played out samples since ts1 and subtract it from the alarm threshold value. Phase2: During nSample burst (after writing to nSample register), count the played out samples since ts1, count the samples received since ts2 (in a burst). Estimate the FIFO depth using these and alarm threshold value. Phase3: Draining phase (after the burst read), count the played out samples since ts1. Estimate the FIFO depth using the nSample configuration and the alarm threshold value. Mode7: Threshold based fill mode. In this mode one timestamp is enough. ts1: taken when the interrupt has been received Interrupts are coming when DAC33 FIFO depth reaches upper threshold. Phase1: Draining phase (after the burst), counting the played out samples since ts1, and subtract it from the upper threshold value. Phase2: During burst operation. Using the pre calculated time needed to play out samples from the buffer during the drain period (from upper to lower threshold), move the time window to cover the estimated time from the burst start to the current time. Calculate the samples played out since lower threshold and also the samples received during the same time. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 222 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 217 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index a8eb19b9b6c..3eddaec728c 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -55,6 +55,13 @@ #define BURST_BASEFREQ_HZ 49152000 +#define SAMPLES_TO_US(rate, samples) \ + (1000000000 / ((rate * 1000) / samples)) + +#define US_TO_SAMPLES(rate, us) \ + (rate / (1000000 / us)) + + static struct snd_soc_codec *tlv320dac33_codec; enum dac33_state { @@ -101,6 +108,14 @@ struct tlv320dac33_priv { int keep_bclk; /* Keep the BCLK continuously running * in FIFO modes */ + spinlock_t lock; + unsigned long long t_stamp1; /* Time stamp for FIFO modes to */ + unsigned long long t_stamp2; /* calculate the FIFO caused delay */ + + unsigned int mode1_us_burst; /* Time to burst read n number of + * samples */ + unsigned int mode7_us_to_lthr; /* Time to reach lthr from uthr */ + enum dac33_state state; }; @@ -390,10 +405,14 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol, return 0; if (ucontrol->value.integer.value[0] < dac33->nsample_min || - ucontrol->value.integer.value[0] > dac33->nsample_max) + ucontrol->value.integer.value[0] > dac33->nsample_max) { ret = -EINVAL; - else + } else { dac33->nsample = ucontrol->value.integer.value[0]; + /* Re calculate the burst time */ + dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate, + dac33->nsample); + } return ret; } @@ -564,6 +583,13 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) case DAC33_FIFO_MODE1: dac33_write16(codec, DAC33_NSAMPLE_MSB, DAC33_THRREG(dac33->nsample + dac33->alarm_threshold)); + + /* Take the timestamps */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + dac33->t_stamp1 = dac33->t_stamp2; + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_PREFILL_MSB, DAC33_THRREG(dac33->alarm_threshold)); /* Enable Alarm Threshold IRQ with a delay */ @@ -572,8 +598,18 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT); break; case DAC33_FIFO_MODE7: + /* Take the timestamp */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + /* Move back the timestamp with drain time */ + dac33->t_stamp1 -= dac33->mode7_us_to_lthr; + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_PREFILL_MSB, DAC33_THRREG(MODE7_LTHR)); + + /* Enable Upper Threshold IRQ */ + dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MUT); break; default: dev_warn(codec->dev, "Unhandled FIFO mode: %d\n", @@ -590,6 +626,11 @@ static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) switch (dac33->fifo_mode) { case DAC33_FIFO_MODE1: + /* Take the timestamp */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_NSAMPLE_MSB, DAC33_THRREG(dac33->nsample)); break; @@ -642,7 +683,13 @@ static irqreturn_t dac33_interrupt_handler(int irq, void *dev) struct snd_soc_codec *codec = dev; struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - queue_work(dac33->dac33_wq, &dac33->work); + spin_lock(&dac33->lock); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + spin_unlock(&dac33->lock); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) + queue_work(dac33->dac33_wq, &dac33->work); return IRQ_HANDLED; } @@ -794,8 +841,8 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL)); break; case DAC33_FIFO_MODE7: - /* Disable all interrupts */ - dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0); + dac33_write(codec, DAC33_FIFO_IRQ_MODE_A, + DAC33_UTM(DAC33_FIFO_IRQ_MODE_LEVEL)); break; default: /* in FIFO bypass mode, the interrupts are not used */ @@ -930,6 +977,24 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) if (dac33->nsample > dac33->nsample_max) dac33->nsample = dac33->nsample_max; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate, + dac33->nsample); + dac33->t_stamp1 = 0; + dac33->t_stamp2 = 0; + break; + case DAC33_FIFO_MODE7: + dac33->mode7_us_to_lthr = + SAMPLES_TO_US(substream->runtime->rate, + MODE7_UTHR - MODE7_LTHR + 1); + dac33->t_stamp1 = 0; + break; + default: + break; + } + } static int dac33_pcm_prepare(struct snd_pcm_substream *substream, @@ -974,6 +1039,151 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, return ret; } +static snd_pcm_sframes_t dac33_dai_delay( + 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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + unsigned long long t0, t1, t_now; + unsigned int time_delta; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: + spin_lock(&dac33->lock); + t0 = dac33->t_stamp1; + t1 = dac33->t_stamp2; + spin_unlock(&dac33->lock); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) + goto out; + + if (t0 > t1) { + /* + * Phase 1: + * After Alarm threshold, and before nSample write + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + if (likely(dac33->alarm_threshold > samples_out)) + delay = dac33->alarm_threshold - samples_out; + else + delay = 0; + } else if ((t_now - t1) <= dac33->mode1_us_burst) { + /* + * Phase 2: + * After nSample write (during burst operation) + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + time_delta = t_now - t1; + samples_in = time_delta ? US_TO_SAMPLES( + dac33->burst_rate, + time_delta) : 0; + + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples; + else + delay = 0; + } else { + /* + * Phase 3: + * After burst operation, before next alarm threshold + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + samples_in = dac33->nsample; + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples > DAC33_BUFFER_SIZE_SAMPLES ? + DAC33_BUFFER_SIZE_SAMPLES : samples; + else + delay = 0; + } + break; + case DAC33_FIFO_MODE7: + spin_lock(&dac33->lock); + t0 = dac33->t_stamp1; + spin_unlock(&dac33->lock); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) + goto out; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ + delay = MODE7_UTHR; + goto out; + } + + time_delta = t_now - t0; + if (time_delta <= dac33->mode7_us_to_lthr) { + /* + * Phase 1: + * After burst (draining phase) + */ + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + + if (likely(MODE7_UTHR > samples_out)) + delay = MODE7_UTHR - samples_out; + else + delay = 0; + } else { + /* + * Phase 2: + * During burst operation + */ + time_delta = time_delta - dac33->mode7_us_to_lthr; + + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + samples_in = US_TO_SAMPLES( + dac33->burst_rate, + time_delta); + delay = MODE7_LTHR + samples_in - samples_out; + + if (unlikely(delay > MODE7_UTHR)) + delay = MODE7_UTHR; + } + break; + default: + dev_warn(codec->dev, "Unhandled FIFO mode: %d\n", + dac33->fifo_mode); + break; + } +out: + return delay; +} + static int dac33_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { @@ -1185,6 +1395,7 @@ static struct snd_soc_dai_ops dac33_dai_ops = { .hw_params = dac33_hw_params, .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, + .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, .set_fmt = dac33_set_dai_fmt, }; @@ -1225,6 +1436,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, mutex_init(&codec->mutex); mutex_init(&dac33->mutex); + spin_lock_init(&dac33->lock); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); -- cgit v1.2.3-18-g5258 From 759512fbacfbb6fd9b7f77f9806fb4995775848f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 23 Apr 2010 17:39:23 +0100 Subject: ASoC: Correct inversion of speaker mixer PCM switch Reported-by: Anti Sullin Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8974.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 1e4da32c407..270eb956294 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -180,7 +180,7 @@ SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0), static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0), SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0), -SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0), }; /* Mono Output Mixer */ -- cgit v1.2.3-18-g5258 From 5e5e2bef287b96ab29d2d93e89ecf0888323d7bf Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 25 Apr 2010 12:20:30 +0100 Subject: ASoC: Warn on low WM8994 AIFCLK Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8994.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index cdfbfb633f3..f04a116e678 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1769,6 +1769,11 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif) dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n", aif + 1, rate); } + + if (rate && rate < 3000000) + dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n", + aif + 1, rate); + wm8994->aifclk[aif] = rate; snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset, -- cgit v1.2.3-18-g5258 From b28528a124d0235d84e6c9b3edeedd70f8767740 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 26 Apr 2010 14:56:57 +0400 Subject: ASoC: UDA134X: Add UDA1345 CODEC support This patch adds support for Philips UDA1345 CODEC. The CODEC has only volume control, de-emphasis, mute, DC filtering and power control features. Signed-off-by: Vladimir Zapolskiy Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/uda134x.h | 1 + sound/soc/codecs/uda134x.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/sound/uda134x.h b/include/sound/uda134x.h index 475ef8bb7dc..509efb05017 100644 --- a/include/sound/uda134x.h +++ b/include/sound/uda134x.h @@ -21,6 +21,7 @@ struct uda134x_platform_data { #define UDA134X_UDA1340 1 #define UDA134X_UDA1341 2 #define UDA134X_UDA1344 3 +#define UDA134X_UDA1345 4 }; #endif /* _UDA134X_H */ diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 20deaca8e10..2f4d7287fa3 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -431,6 +431,14 @@ SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), }; +static const struct snd_kcontrol_new uda1345_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), + +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), + +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + static struct snd_soc_dai_ops uda134x_dai_ops = { .startup = uda134x_startup, .shutdown = uda134x_shutdown, @@ -486,6 +494,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) case UDA134X_UDA1340: case UDA134X_UDA1341: case UDA134X_UDA1344: + case UDA134X_UDA1345: break; default: printk(KERN_ERR "UDA134X SoC codec: " @@ -551,6 +560,10 @@ static int uda134x_soc_probe(struct platform_device *pdev) ret = snd_soc_add_controls(codec, uda1341_snd_controls, ARRAY_SIZE(uda1341_snd_controls)); break; + case UDA134X_UDA1345: + ret = snd_soc_add_controls(codec, uda1345_snd_controls, + ARRAY_SIZE(uda1345_snd_controls)); + break; default: printk(KERN_ERR "%s unknown codec type: %d", __func__, pd->model); -- cgit v1.2.3-18-g5258 From c6de6e03009efaa56baeb98da8bbcce0c6c889a6 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Mon, 26 Apr 2010 15:49:11 +0300 Subject: ASoC: tlv320aic3x: Remove unused version string Signed-off-by: Jarkko Nikula Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320aic3x.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 556123b4059..260a2bef72c 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -49,8 +49,6 @@ #include "tlv320aic3x.h" -#define AIC3X_VERSION "0.2" - /* codec private data */ struct aic3x_priv { struct snd_soc_codec codec; -- cgit v1.2.3-18-g5258 From d3235c4ac17b7f2a9c64cc3ebc71e23d1d1fa466 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Mon, 26 Apr 2010 15:49:12 +0300 Subject: ASoC: tlv320aic3x: Remove needless power off from aic3x_set_bias_level These ADC, DAC and output pin power off commands are needless in aic3x_set_bias_level since they are not enabled in aic3x_init and they are defined in aic3x_dapm_widgets so the ASoC DAPM will take care of them anyway. Signed-off-by: Jarkko Nikula Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320aic3x.c | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 260a2bef72c..19602eca492 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1012,41 +1012,7 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, * all power is driven by DAPM system, * so output power is safe if bypass was set */ - if (aic3x->master) { - /* disable pll */ - reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); - aic3x_write(codec, AIC3X_PLL_PROGA_REG, - reg & ~PLL_ENABLE); - } - break; case SND_SOC_BIAS_OFF: - /* force all power off */ - reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); - aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); - reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); - aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON); - - reg = aic3x_read_reg_cache(codec, DAC_PWR); - aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON)); - - reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); - aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON); - reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); - aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON); - - reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); - aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON); - reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); - aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON); - - reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); - aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON); - - reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); - aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON); - reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); - aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON); - if (aic3x->master) { /* disable pll */ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); -- cgit v1.2.3-18-g5258 From db13802e51f29c40b404038485121a3973c0947d Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Mon, 26 Apr 2010 15:49:13 +0300 Subject: ASoC: tlv320aic3x: Change bias management semantics Move PLL enable from BIAS_ON state to BIAS_PREPARE to be pair with BIAS_STANDBY where PLL is disabled. Remove also old comments about power control. Signed-off-by: Jarkko Nikula Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320aic3x.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 19602eca492..6b74ad808a0 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -997,7 +997,8 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: - /* all power is driven by DAPM system */ + break; + case SND_SOC_BIAS_PREPARE: if (aic3x->master) { /* enable pll */ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); @@ -1005,13 +1006,8 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, reg | PLL_ENABLE); } break; - case SND_SOC_BIAS_PREPARE: - break; case SND_SOC_BIAS_STANDBY: - /* - * all power is driven by DAPM system, - * so output power is safe if bypass was set - */ + /* fall through and disable pll */ case SND_SOC_BIAS_OFF: if (aic3x->master) { /* disable pll */ -- cgit v1.2.3-18-g5258 From 07779fdd1a236145b5f5dc6916c6b84d9712b305 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Mon, 26 Apr 2010 15:49:14 +0300 Subject: ASoC: tlv320aic3x: Add basic regulator support This patch adds the TLV320AIC3x supplies and enables all of them for the entire lifetime of the device. Signed-off-by: Jarkko Nikula Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320aic3x.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 6b74ad808a0..584bc1e67f7 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -49,9 +50,18 @@ #include "tlv320aic3x.h" +#define AIC3X_NUM_SUPPLIES 4 +static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = { + "IOVDD", /* I/O Voltage */ + "DVDD", /* Digital Core Voltage */ + "AVDD", /* Analog DAC Voltage */ + "DRVDD", /* ADC Analog and Output Driver Voltage */ +}; + /* codec private data */ struct aic3x_priv { struct snd_soc_codec codec; + struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; unsigned int sysclk; int master; }; @@ -1268,6 +1278,9 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec); + regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + kfree(aic3x); aic3x_codec = NULL; @@ -1289,6 +1302,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, { struct snd_soc_codec *codec; struct aic3x_priv *aic3x; + int ret, i; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { @@ -1304,7 +1318,30 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, aic3x); + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + aic3x->supplies[i].supply = aic3x_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err_get; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_enable; + } + return aic3x_register(codec); + +err_enable: + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); +err_get: + kfree(aic3x); + return ret; } static int aic3x_i2c_remove(struct i2c_client *client) -- cgit v1.2.3-18-g5258 From dde3a7e9cb187e25deeac0269733116d4840f91e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 27 Apr 2010 20:01:56 +0100 Subject: ASoC: Remove redundant WM8960 SYSCLKSEL clkdiv option The SYSCLK source is automatically managed when configuring the PLL. Signed-off-by: Mark Brown Acked-by: Mark Brown --- sound/soc/codecs/wm8960.c | 4 ---- sound/soc/codecs/wm8960.h | 1 - 2 files changed, 5 deletions(-) diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index eba3ac01311..50b2376680c 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -738,10 +738,6 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, u16 reg; switch (div_id) { - case WM8960_SYSCLKSEL: - reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe; - snd_soc_write(codec, WM8960_CLOCK1, reg | div); - break; case WM8960_SYSCLKDIV: reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; snd_soc_write(codec, WM8960_CLOCK1, reg | div); diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h index d67bfe1300d..a5ef65481b8 100644 --- a/sound/soc/codecs/wm8960.h +++ b/sound/soc/codecs/wm8960.h @@ -76,7 +76,6 @@ #define WM8960_OPCLKDIV 2 #define WM8960_DCLKDIV 3 #define WM8960_TOCLKSEL 4 -#define WM8960_SYSCLKSEL 5 #define WM8960_SYSCLK_DIV_1 (0 << 1) #define WM8960_SYSCLK_DIV_2 (2 << 1) -- cgit v1.2.3-18-g5258 From 1beb91f004e0efe83b933ca6c84a8b9935f4cf53 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 22 Mar 2010 19:30:54 +0000 Subject: ASoC: pandora - update DAPM pins Remove bogus TWL4030 pins. Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/omap3pandora.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index de10f76bade..87ce842fa2e 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -188,8 +188,6 @@ static int omap3pandora_out_init(struct snd_soc_codec *codec) int ret; /* All TWL4030 output pins are floating */ - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); -- cgit v1.2.3-18-g5258 From 1849235876b046e26a07e33972906bd23fbb8705 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 22 Mar 2010 19:35:06 +0000 Subject: ASoC: zoom2 - update DAPM pins Remove bogus twl4030 pins Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/zoom2.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index f90a2ac888c..50a94ee76ec 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -181,9 +181,6 @@ static int zoom2_twl4030_init(struct snd_soc_codec *codec) snd_soc_dapm_nc_pin(codec, "CARKITMIC"); snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); - - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); -- cgit v1.2.3-18-g5258 From cf134d5bfb19cdee922b95738ce3cfe86c0e8f7a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 26 Mar 2010 20:05:54 +0000 Subject: ASoC: tlv320dac33 - disable regulators at i2c remove() Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 3eddaec728c..54b2a0508a1 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1584,6 +1584,7 @@ static int __devexit dac33_i2c_remove(struct i2c_client *client) if (dac33->irq >= 0) free_irq(dac33->irq, &dac33->codec); + regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); destroy_workqueue(dac33->dac33_wq); -- cgit v1.2.3-18-g5258 From 39b8eab7e7fe429d8d57f18c0ebdb7c25df55f5c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 28 Apr 2010 18:36:10 +0100 Subject: ASoC: Add WM9090 amplifier driver The WM9090 is a high performance low power audio subsystem, including headphone and class D speaker drivers. Note that this driver is a standalone CODEC driver and so is only immediately suitable for use with the WM9090 as a standalone sound card taking line inputs, or with a DAC with no software control. The pending ASoC multi-CODEC support will expand the range of systems that can use the driver, or system-specific adaptations can be made. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm9090.h | 28 ++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm9090.c | 773 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm9090.h | 715 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1522 insertions(+) create mode 100644 include/sound/wm9090.h create mode 100644 sound/soc/codecs/wm9090.c create mode 100644 sound/soc/codecs/wm9090.h diff --git a/include/sound/wm9090.h b/include/sound/wm9090.h new file mode 100644 index 00000000000..3718928cde1 --- /dev/null +++ b/include/sound/wm9090.h @@ -0,0 +1,28 @@ +/* + * linux/sound/wm9090.h -- Platform data for WM9090 + * + * Copyright 2009, 2010 Wolfson Microelectronics. PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_WM9090_H +#define __LINUX_SND_WM9090_H + +struct wm9090_platform_data { + /* Line inputs 1 & 2 can optionally be differential */ + unsigned int lin1_diff:1; + unsigned int lin2_diff:1; + + /* AGC configuration. This is intended to protect the speaker + * against overdriving and will therefore depend on the + * hardware setup with incorrect runtime configuration + * potentially causing hardware damage. + */ + unsigned int agc_ena:1; + u16 agc[3]; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bc0ab47e156..31ac5538fe7 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -66,6 +66,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8993 if I2C select SND_SOC_WM8994 if MFD_WM8994 select SND_SOC_WM9081 if I2C + select SND_SOC_WM9090 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS @@ -277,3 +278,6 @@ config SND_SOC_TPA6130A2 config SND_SOC_WM2000 tristate + +config SND_SOC_WM9090 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 33790416735..91429eab070 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -61,6 +61,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-wm2000-objs := wm2000.o +snd-soc-wm9090-objs := wm9090.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o @@ -125,3 +126,4 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o +obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c new file mode 100644 index 00000000000..1592250daec --- /dev/null +++ b/sound/soc/codecs/wm9090.c @@ -0,0 +1,773 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009, 2010 Wolfson Microelectronics + * + * Author: Mark Brown + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm9090.h" + +static struct snd_soc_codec *wm9090_codec; + +static const u16 wm9090_reg_defaults[] = { + 0x9093, /* R0 - Software Reset */ + 0x0006, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x0000, /* R4 */ + 0x0000, /* R5 */ + 0x01C0, /* R6 - Clocking 1 */ + 0x0000, /* R7 */ + 0x0000, /* R8 */ + 0x0000, /* R9 */ + 0x0000, /* R10 */ + 0x0000, /* R11 */ + 0x0000, /* R12 */ + 0x0000, /* R13 */ + 0x0000, /* R14 */ + 0x0000, /* R15 */ + 0x0000, /* R16 */ + 0x0000, /* R17 */ + 0x0000, /* R18 */ + 0x0000, /* R19 */ + 0x0000, /* R20 */ + 0x0000, /* R21 */ + 0x0003, /* R22 - IN1 Line Control */ + 0x0003, /* R23 - IN2 Line Control */ + 0x0083, /* R24 - IN1 Line Input A Volume */ + 0x0083, /* R25 - IN1 Line Input B Volume */ + 0x0083, /* R26 - IN2 Line Input A Volume */ + 0x0083, /* R27 - IN2 Line Input B Volume */ + 0x002D, /* R28 - Left Output Volume */ + 0x002D, /* R29 - Right Output Volume */ + 0x0000, /* R30 */ + 0x0000, /* R31 */ + 0x0000, /* R32 */ + 0x0000, /* R33 */ + 0x0100, /* R34 - SPKMIXL Attenuation */ + 0x0000, /* R35 */ + 0x0010, /* R36 - SPKOUT Mixers */ + 0x0140, /* R37 - ClassD3 */ + 0x0039, /* R38 - Speaker Volume Left */ + 0x0000, /* R39 */ + 0x0000, /* R40 */ + 0x0000, /* R41 */ + 0x0000, /* R42 */ + 0x0000, /* R43 */ + 0x0000, /* R44 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0100, /* R47 - Output Mixer3 */ + 0x0100, /* R48 - Output Mixer4 */ + 0x0000, /* R49 */ + 0x0000, /* R50 */ + 0x0000, /* R51 */ + 0x0000, /* R52 */ + 0x0000, /* R53 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x000D, /* R57 - AntiPOP2 */ + 0x0000, /* R58 */ + 0x0000, /* R59 */ + 0x0000, /* R60 */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x0000, /* R64 */ + 0x0000, /* R65 */ + 0x0000, /* R66 */ + 0x0000, /* R67 */ + 0x0000, /* R68 */ + 0x0000, /* R69 */ + 0x0000, /* R70 - Write Sequencer 0 */ + 0x0000, /* R71 - Write Sequencer 1 */ + 0x0000, /* R72 - Write Sequencer 2 */ + 0x0000, /* R73 - Write Sequencer 3 */ + 0x0000, /* R74 - Write Sequencer 4 */ + 0x0000, /* R75 - Write Sequencer 5 */ + 0x1F25, /* R76 - Charge Pump 1 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x0000, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 - DC Servo 0 */ + 0x054A, /* R85 - DC Servo 1 */ + 0x0000, /* R86 */ + 0x0000, /* R87 - DC Servo 3 */ + 0x0000, /* R88 - DC Servo Readback 0 */ + 0x0000, /* R89 - DC Servo Readback 1 */ + 0x0000, /* R90 - DC Servo Readback 2 */ + 0x0000, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0100, /* R96 - Analogue HP 0 */ + 0x0000, /* R97 */ + 0x8640, /* R98 - AGC Control 0 */ + 0xC000, /* R99 - AGC Control 1 */ + 0x0200, /* R100 - AGC Control 2 */ +}; + +/* This struct is used to save the context */ +struct wm9090_priv { + /* We're not really registering as a CODEC since ASoC core + * does not yet support multiple CODECs but having the CODEC + * structure means we can reuse some of the ASoC core + * features. + */ + struct snd_soc_codec codec; + struct mutex mutex; + u16 reg_cache[WM9090_MAX_REGISTER + 1]; + struct wm9090_platform_data pdata; +}; + +static int wm9090_volatile(unsigned int reg) +{ + switch (reg) { + case WM9090_SOFTWARE_RESET: + case WM9090_DC_SERVO_0: + case WM9090_DC_SERVO_READBACK_0: + case WM9090_DC_SERVO_READBACK_1: + case WM9090_DC_SERVO_READBACK_2: + return 1; + + default: + return 0; + } +} + +static void wait_for_dc_servo(struct snd_soc_codec *codec) +{ + unsigned int reg; + int count = 0; + + dev_dbg(codec->dev, "Waiting for DC servo...\n"); + do { + count++; + msleep(1); + reg = snd_soc_read(codec, WM9090_DC_SERVO_READBACK_0); + dev_dbg(codec->dev, "DC servo status: %x\n", reg); + } while ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK && count < 1000); + + if ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK) + dev_err(codec->dev, "Timed out waiting for DC Servo\n"); +} + +static const unsigned int in_tlv[] = { + TLV_DB_RANGE_HEAD(6), + 0, 0, TLV_DB_SCALE_ITEM(-600, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(-350, 350, 0), + 4, 6, TLV_DB_SCALE_ITEM(600, 600, 0), +}; +static const unsigned int mix_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 2, TLV_DB_SCALE_ITEM(-1200, 300, 0), + 3, 3, TLV_DB_SCALE_ITEM(0, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const unsigned int spkboost_tlv[] = { + TLV_DB_RANGE_HEAD(7), + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), +}; + +static const struct snd_kcontrol_new wm9090_controls[] = { +SOC_SINGLE_TLV("IN1A Volume", WM9090_IN1_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1A Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1A ZC Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("IN2A Volume", WM9090_IN2_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2A Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2A ZC Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE("MIXOUTL Switch", WM9090_OUTPUT_MIXER3, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTL IN1A Volume", WM9090_OUTPUT_MIXER3, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2A Volume", WM9090_OUTPUT_MIXER3, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("MIXOUTR Switch", WM9090_OUTPUT_MIXER4, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTR IN1A Volume", WM9090_OUTPUT_MIXER4, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2A Volume", WM9090_OUTPUT_MIXER4, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("SPKMIX Switch", WM9090_SPKMIXL_ATTENUATION, 8, 1, 1), +SOC_SINGLE_TLV("SPKMIX IN1A Volume", WM9090_SPKMIXL_ATTENUATION, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("SPKMIX IN2A Volume", WM9090_SPKMIXL_ATTENUATION, 2, 3, 1, + mix_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 0, 63, 0, out_tlv), +SOC_DOUBLE_R("Headphone Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 6, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("Speaker Volume", WM9090_SPEAKER_VOLUME_LEFT, 0, 63, 0, + out_tlv), +SOC_SINGLE("Speaker Switch", WM9090_SPEAKER_VOLUME_LEFT, 6, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9090_SPEAKER_VOLUME_LEFT, 7, 1, 0), +SOC_SINGLE_TLV("Speaker Boost Volume", WM9090_CLASSD3, 3, 7, 0, spkboost_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in1_se_controls[] = { +SOC_SINGLE_TLV("IN1B Volume", WM9090_IN1_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1B Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1B ZC Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN1B Volume", WM9090_SPKMIXL_ATTENUATION, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN1B Volume", WM9090_OUTPUT_MIXER3, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN1B Volume", WM9090_OUTPUT_MIXER4, 4, 3, 1, + mix_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in2_se_controls[] = { +SOC_SINGLE_TLV("IN2B Volume", WM9090_IN2_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2B Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2B ZC Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN2B Volume", WM9090_SPKMIXL_ATTENUATION, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2B Volume", WM9090_OUTPUT_MIXER3, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2B Volume", WM9090_OUTPUT_MIXER4, 0, 3, 1, + mix_tlv), +}; + +static int hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = snd_soc_read(codec, WM9090_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, WM9090_CP_ENA); + + msleep(5); + + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA); + + reg |= WM9090_HPOUT1L_DLY | WM9090_HPOUT1R_DLY; + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + + /* Start the DC servo. We don't currently use the + * ability to save the state since we don't have full + * control of the analogue paths and they can change + * DC offsets; see the WM8904 driver for an example of + * doing so. + */ + snd_soc_write(codec, WM9090_DC_SERVO_0, + WM9090_DCS_ENA_CHAN_0 | + WM9090_DCS_ENA_CHAN_1 | + WM9090_DCS_TRIG_STARTUP_1 | + WM9090_DCS_TRIG_STARTUP_0); + wait_for_dc_servo(codec); + + reg |= WM9090_HPOUT1R_OUTP | WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1L_OUTP | WM9090_HPOUT1L_RMV_SHORT; + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~(WM9090_HPOUT1L_RMV_SHORT | + WM9090_HPOUT1L_DLY | + WM9090_HPOUT1L_OUTP | + WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1R_DLY | + WM9090_HPOUT1R_OUTP); + + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + + snd_soc_write(codec, WM9090_DC_SERVO_0, 0); + + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + 0); + + snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, 0); + break; + } + + return 0; +} + +static const struct snd_kcontrol_new spkmix[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_SPEAKER_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new spkout[] = { +SOC_DAPM_SINGLE("Mixer Switch", WM9090_SPKOUT_MIXERS, 4, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutl[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER1, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutr[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER2, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm9090_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1+"), +SND_SOC_DAPM_INPUT("IN1-"), +SND_SOC_DAPM_INPUT("IN2+"), +SND_SOC_DAPM_INPUT("IN2-"), + +SND_SOC_DAPM_SUPPLY("OSC", WM9090_POWER_MANAGEMENT_1, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("IN1A PGA", WM9090_POWER_MANAGEMENT_2, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN1B PGA", WM9090_POWER_MANAGEMENT_2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2A PGA", WM9090_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2B PGA", WM9090_POWER_MANAGEMENT_2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("SPKMIX", WM9090_POWER_MANAGEMENT_3, 3, 0, + spkmix, ARRAY_SIZE(spkmix)), +SND_SOC_DAPM_MIXER("MIXOUTL", WM9090_POWER_MANAGEMENT_3, 5, 0, + mixoutl, ARRAY_SIZE(mixoutl)), +SND_SOC_DAPM_MIXER("MIXOUTR", WM9090_POWER_MANAGEMENT_3, 4, 0, + mixoutr, ARRAY_SIZE(mixoutr)), + +SND_SOC_DAPM_PGA_E("HP PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + hp_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA("SPKPGA", WM9090_POWER_MANAGEMENT_3, 8, 0, NULL, 0), +SND_SOC_DAPM_MIXER("SPKOUT", WM9090_POWER_MANAGEMENT_1, 12, 0, + spkout, ARRAY_SIZE(spkout)), + +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "IN1A PGA", NULL, "IN1+" }, + { "IN2A PGA", NULL, "IN2+" }, + + { "SPKMIX", "IN1A Switch", "IN1A PGA" }, + { "SPKMIX", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTL", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTL", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTR", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTR", "IN2A Switch", "IN2A PGA" }, + + { "HP PGA", NULL, "OSC" }, + { "HP PGA", NULL, "MIXOUTL" }, + { "HP PGA", NULL, "MIXOUTR" }, + + { "HPL", NULL, "HP PGA" }, + { "HPR", NULL, "HP PGA" }, + + { "SPKPGA", NULL, "OSC" }, + { "SPKPGA", NULL, "SPKMIX" }, + + { "SPKOUT", "Mixer Switch", "SPKPGA" }, + + { "Speaker", NULL, "SPKOUT" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_se[] = { + { "IN1B PGA", NULL, "IN1-" }, + + { "SPKMIX", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTL", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTR", "IN1B Switch", "IN1B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_diff[] = { + { "IN1A PGA", NULL, "IN1-" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_se[] = { + { "IN2B PGA", NULL, "IN2-" }, + + { "SPKMIX", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTL", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTR", "IN2B Switch", "IN2B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_diff[] = { + { "IN2A PGA", NULL, "IN2-" }, +}; + +static int wm9090_add_controls(struct snd_soc_codec *codec) +{ + struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec); + int i; + + snd_soc_dapm_new_controls(codec, wm9090_dapm_widgets, + ARRAY_SIZE(wm9090_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_add_controls(codec, wm9090_controls, + ARRAY_SIZE(wm9090_controls)); + + if (wm9090->pdata.lin1_diff) { + snd_soc_dapm_add_routes(codec, audio_map_in1_diff, + ARRAY_SIZE(audio_map_in1_diff)); + } else { + snd_soc_dapm_add_routes(codec, audio_map_in1_se, + ARRAY_SIZE(audio_map_in1_se)); + snd_soc_add_controls(codec, wm9090_in1_se_controls, + ARRAY_SIZE(wm9090_in1_se_controls)); + } + + if (wm9090->pdata.lin2_diff) { + snd_soc_dapm_add_routes(codec, audio_map_in2_diff, + ARRAY_SIZE(audio_map_in2_diff)); + } else { + snd_soc_dapm_add_routes(codec, audio_map_in2_se, + ARRAY_SIZE(audio_map_in2_se)); + snd_soc_add_controls(codec, wm9090_in2_se_controls, + ARRAY_SIZE(wm9090_in2_se_controls)); + } + + if (wm9090->pdata.agc_ena) { + for (i = 0; i < ARRAY_SIZE(wm9090->pdata.agc); i++) + snd_soc_write(codec, WM9090_AGC_CONTROL_0 + i, + wm9090->pdata.agc[i]); + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, WM9090_AGC_ENA); + } else { + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, 0); + } + + return 0; + +} + +/* + * The machine driver should call this from their set_bias_level; if there + * isn't one then this can just be set as the set_bias_level function. + */ +static int wm9090_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 *reg_cache = codec->reg_cache; + int i, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, WM9090_ANTIPOP2, WM9090_VMID_ENA, + WM9090_VMID_ENA); + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | + WM9090_VMID_RES_MASK, + WM9090_BIAS_ENA | + 1 << WM9090_VMID_RES_SHIFT); + msleep(1); /* Probably an overestimate */ + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Restore the register cache */ + for (i = 1; i < codec->reg_cache_size; i++) { + if (reg_cache[i] == wm9090_reg_defaults[i]) + continue; + if (wm9090_volatile(i)) + continue; + + ret = snd_soc_write(codec, i, reg_cache[i]); + if (ret != 0) + dev_warn(codec->dev, + "Failed to restore register %d: %d\n", + i, ret); + } + } + + /* We keep VMID off during standby since the combination of + * ground referenced outputs and class D speaker mean that + * latency is not an issue. + */ + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | WM9090_VMID_RES_MASK, 0); + snd_soc_update_bits(codec, WM9090_ANTIPOP2, + WM9090_VMID_ENA, 0); + break; + + case SND_SOC_BIAS_OFF: + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm9090_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm9090_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm9090_codec; + codec = wm9090_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + wm9090_add_controls(codec); + + return 0; + +pcm_err: + return ret; +} + +#ifdef CONFIG_PM +static int wm9090_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; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm9090_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm9090_suspend NULL +#define wm9090_resume NULL +#endif + +static int wm9090_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_wm9090 = { + .probe = wm9090_probe, + .remove = wm9090_remove, + .suspend = wm9090_suspend, + .resume = wm9090_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9090); + +static int wm9090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9090_priv *wm9090; + struct snd_soc_codec *codec; + int ret; + + wm9090 = kzalloc(sizeof(*wm9090), GFP_KERNEL); + if (wm9090 == NULL) { + dev_err(&i2c->dev, "Can not allocate memory\n"); + return -ENOMEM; + } + codec = &wm9090->codec; + + if (i2c->dev.platform_data) + memcpy(&wm9090->pdata, i2c->dev.platform_data, + sizeof(wm9090->pdata)); + + wm9090_codec = codec; + + i2c_set_clientdata(i2c, wm9090); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->control_data = i2c; + snd_soc_codec_set_drvdata(codec, wm9090); + codec->dev = &i2c->dev; + codec->name = "WM9090"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm9090_set_bias_level, + codec->reg_cache_size = WM9090_MAX_REGISTER + 1; + codec->reg_cache = &wm9090->reg_cache; + codec->volatile_register = wm9090_volatile; + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + memcpy(&wm9090->reg_cache, wm9090_reg_defaults, + sizeof(wm9090->reg_cache)); + + ret = snd_soc_read(codec, WM9090_SOFTWARE_RESET); + if (ret < 0) + goto err; + if (ret != wm9090_reg_defaults[WM9090_SOFTWARE_RESET]) { + dev_err(&i2c->dev, "Device is not a WM9090, ID=%x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = snd_soc_write(codec, WM9090_SOFTWARE_RESET, 0); + if (ret < 0) + goto err; + + /* Configure some defaults; they will be written out when we + * bring the bias up. + */ + wm9090->reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU + | WM9090_IN1A_ZC; + wm9090->reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU + | WM9090_IN1B_ZC; + wm9090->reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU + | WM9090_IN2A_ZC; + wm9090->reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU + | WM9090_IN2B_ZC; + wm9090->reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |= + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC; + wm9090->reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |= + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC; + wm9090->reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |= + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC; + + wm9090->reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + goto err_bias; + } + + return 0; + +err_bias: + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); +err: + kfree(wm9090); + i2c_set_clientdata(i2c, NULL); + wm9090_codec = NULL; + + return ret; +} + +static int wm9090_i2c_remove(struct i2c_client *i2c) +{ + struct wm9090_priv *wm9090 = i2c_get_clientdata(i2c); + struct snd_soc_codec *codec = &wm9090->codec; + + snd_soc_unregister_codec(codec); + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); + kfree(wm9090); + wm9090_codec = NULL; + + return 0; +} + +static const struct i2c_device_id wm9090_id[] = { + { "wm9090", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9090_id); + +static struct i2c_driver wm9090_i2c_driver = { + .driver = { + .name = "wm9090", + .owner = THIS_MODULE, + }, + .probe = wm9090_i2c_probe, + .remove = __devexit_p(wm9090_i2c_remove), + .id_table = wm9090_id, +}; + +static int __init wm9090_init(void) +{ + return i2c_add_driver(&wm9090_i2c_driver); +} +module_init(wm9090_init); + +static void __exit wm9090_exit(void) +{ + i2c_del_driver(&wm9090_i2c_driver); +} +module_exit(wm9090_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM9090 ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9090.h b/sound/soc/codecs/wm9090.h new file mode 100644 index 00000000000..b08eab932a5 --- /dev/null +++ b/sound/soc/codecs/wm9090.h @@ -0,0 +1,715 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009 Wolfson Microelectronics + * + * Author: Mark Brown + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __WM9090_H +#define __WM9090_H + +extern struct snd_soc_codec_device soc_codec_dev_wm9090; + +/* + * Register values. + */ +#define WM9090_SOFTWARE_RESET 0x00 +#define WM9090_POWER_MANAGEMENT_1 0x01 +#define WM9090_POWER_MANAGEMENT_2 0x02 +#define WM9090_POWER_MANAGEMENT_3 0x03 +#define WM9090_CLOCKING_1 0x06 +#define WM9090_IN1_LINE_CONTROL 0x16 +#define WM9090_IN2_LINE_CONTROL 0x17 +#define WM9090_IN1_LINE_INPUT_A_VOLUME 0x18 +#define WM9090_IN1_LINE_INPUT_B_VOLUME 0x19 +#define WM9090_IN2_LINE_INPUT_A_VOLUME 0x1A +#define WM9090_IN2_LINE_INPUT_B_VOLUME 0x1B +#define WM9090_LEFT_OUTPUT_VOLUME 0x1C +#define WM9090_RIGHT_OUTPUT_VOLUME 0x1D +#define WM9090_SPKMIXL_ATTENUATION 0x22 +#define WM9090_SPKOUT_MIXERS 0x24 +#define WM9090_CLASSD3 0x25 +#define WM9090_SPEAKER_VOLUME_LEFT 0x26 +#define WM9090_OUTPUT_MIXER1 0x2D +#define WM9090_OUTPUT_MIXER2 0x2E +#define WM9090_OUTPUT_MIXER3 0x2F +#define WM9090_OUTPUT_MIXER4 0x30 +#define WM9090_SPEAKER_MIXER 0x36 +#define WM9090_ANTIPOP2 0x39 +#define WM9090_WRITE_SEQUENCER_0 0x46 +#define WM9090_WRITE_SEQUENCER_1 0x47 +#define WM9090_WRITE_SEQUENCER_2 0x48 +#define WM9090_WRITE_SEQUENCER_3 0x49 +#define WM9090_WRITE_SEQUENCER_4 0x4A +#define WM9090_WRITE_SEQUENCER_5 0x4B +#define WM9090_CHARGE_PUMP_1 0x4C +#define WM9090_DC_SERVO_0 0x54 +#define WM9090_DC_SERVO_1 0x55 +#define WM9090_DC_SERVO_3 0x57 +#define WM9090_DC_SERVO_READBACK_0 0x58 +#define WM9090_DC_SERVO_READBACK_1 0x59 +#define WM9090_DC_SERVO_READBACK_2 0x5A +#define WM9090_ANALOGUE_HP_0 0x60 +#define WM9090_AGC_CONTROL_0 0x62 +#define WM9090_AGC_CONTROL_1 0x63 +#define WM9090_AGC_CONTROL_2 0x64 + +#define WM9090_REGISTER_COUNT 40 +#define WM9090_MAX_REGISTER 0x64 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9090_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM9090_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM9090_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM9090_OSC_ENA 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_MASK 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_SHIFT 3 /* OSC_ENA */ +#define WM9090_OSC_ENA_WIDTH 1 /* OSC_ENA */ +#define WM9090_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM9090_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM9090_TSHUT 0x8000 /* TSHUT */ +#define WM9090_TSHUT_MASK 0x8000 /* TSHUT */ +#define WM9090_TSHUT_SHIFT 15 /* TSHUT */ +#define WM9090_TSHUT_WIDTH 1 /* TSHUT */ +#define WM9090_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9090_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */ +#define WM9090_IN1A_ENA 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_MASK 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_SHIFT 7 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_WIDTH 1 /* IN1A_ENA */ +#define WM9090_IN1B_ENA 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_MASK 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_SHIFT 6 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_WIDTH 1 /* IN1B_ENA */ +#define WM9090_IN2A_ENA 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_MASK 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_SHIFT 5 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_WIDTH 1 /* IN2A_ENA */ +#define WM9090_IN2B_ENA 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_MASK 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_SHIFT 4 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_WIDTH 1 /* IN2B_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM9090_AGC_ENA 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_MASK 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_SHIFT 14 /* AGC_ENA */ +#define WM9090_AGC_ENA_WIDTH 1 /* AGC_ENA */ +#define WM9090_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */ +#define WM9090_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ +#define WM9090_SPKMIX_ENA 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_MASK 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_SHIFT 3 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_WIDTH 1 /* SPKMIX_ENA */ + +/* + * R6 (0x06) - Clocking 1 + */ +#define WM9090_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM9090_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ + +/* + * R22 (0x16) - IN1 Line Control + */ +#define WM9090_IN1_DIFF 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_MASK 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_SHIFT 1 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_WIDTH 1 /* IN1_DIFF */ +#define WM9090_IN1_CLAMP 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_MASK 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_SHIFT 0 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_WIDTH 1 /* IN1_CLAMP */ + +/* + * R23 (0x17) - IN2 Line Control + */ +#define WM9090_IN2_DIFF 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_MASK 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_SHIFT 1 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_WIDTH 1 /* IN2_DIFF */ +#define WM9090_IN2_CLAMP 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_MASK 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_SHIFT 0 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_WIDTH 1 /* IN2_CLAMP */ + +/* + * R24 (0x18) - IN1 Line Input A Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1A_MUTE 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_MASK 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_SHIFT 7 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_WIDTH 1 /* IN1A_MUTE */ +#define WM9090_IN1A_ZC 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_MASK 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_SHIFT 6 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_WIDTH 1 /* IN1A_ZC */ +#define WM9090_IN1A_VOL_MASK 0x0007 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_SHIFT 0 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_WIDTH 3 /* IN1A_VOL - [2:0] */ + +/* + * R25 (0x19) - IN1 Line Input B Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1B_MUTE 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_MASK 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_SHIFT 7 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_WIDTH 1 /* IN1B_MUTE */ +#define WM9090_IN1B_ZC 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_MASK 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_SHIFT 6 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_WIDTH 1 /* IN1B_ZC */ +#define WM9090_IN1B_VOL_MASK 0x0007 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_SHIFT 0 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_WIDTH 3 /* IN1B_VOL - [2:0] */ + +/* + * R26 (0x1A) - IN2 Line Input A Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2A_MUTE 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_MASK 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_SHIFT 7 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_WIDTH 1 /* IN2A_MUTE */ +#define WM9090_IN2A_ZC 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_MASK 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_SHIFT 6 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_WIDTH 1 /* IN2A_ZC */ +#define WM9090_IN2A_VOL_MASK 0x0007 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_SHIFT 0 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_WIDTH 3 /* IN2A_VOL - [2:0] */ + +/* + * R27 (0x1B) - IN2 Line Input B Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2B_MUTE 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_MASK 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_SHIFT 7 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_WIDTH 1 /* IN2B_MUTE */ +#define WM9090_IN2B_ZC 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_MASK 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_SHIFT 6 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_WIDTH 1 /* IN2B_ZC */ +#define WM9090_IN2B_VOL_MASK 0x0007 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_SHIFT 0 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_WIDTH 3 /* IN2B_VOL - [2:0] */ + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_MUTE 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_MASK 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_SHIFT 6 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_WIDTH 1 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */ + +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_MUTE 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_MASK 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_SHIFT 6 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_WIDTH 1 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */ + +/* + * R34 (0x22) - SPKMIXL Attenuation + */ +#define WM9090_SPKMIX_MUTE 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_MASK 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_SHIFT 8 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_WIDTH 1 /* SPKMIX_MUTE */ +#define WM9090_IN1A_SPKMIX_VOL_MASK 0x00C0 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_SHIFT 6 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_WIDTH 2 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1B_SPKMIX_VOL_MASK 0x0030 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_SHIFT 4 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_WIDTH 2 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN2A_SPKMIX_VOL_MASK 0x000C /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_SHIFT 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_WIDTH 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2B_SPKMIX_VOL_MASK 0x0003 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_SHIFT 0 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_WIDTH 2 /* IN2B_SPKMIX_VOL - [1:0] */ + +/* + * R36 (0x24) - SPKOUT Mixers + */ +#define WM9090_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */ + +/* + * R37 (0x25) - ClassD3 + */ +#define WM9090_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */ + +/* + * R38 (0x26) - Speaker Volume Left + */ +#define WM9090_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM9090_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_MUTE 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_MASK 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_SHIFT 6 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_WIDTH 1 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM9090_IN1A_TO_MIXOUTL 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_MASK 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_SHIFT 6 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_WIDTH 1 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_MASK 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_SHIFT 2 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_WIDTH 1 /* IN2A_TO_MIXOUTL */ + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM9090_IN1A_TO_MIXOUTR 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_MASK 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_SHIFT 6 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_WIDTH 1 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_MASK 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_SHIFT 4 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_WIDTH 1 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_MASK 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_SHIFT 2 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_WIDTH 1 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_MASK 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_SHIFT 0 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_WIDTH 1 /* IN2B_TO_MIXOUTR */ + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM9090_MIXOUTL_MUTE 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_MASK 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_SHIFT 8 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_WIDTH 1 /* MIXOUTL_MUTE */ +#define WM9090_IN1A_MIXOUTL_VOL_MASK 0x00C0 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_SHIFT 6 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_WIDTH 2 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN2A_MIXOUTL_VOL_MASK 0x000C /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_SHIFT 2 /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_WIDTH 2 /* IN2A_MIXOUTL_VOL - [3:2] */ + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM9090_MIXOUTR_MUTE 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_MASK 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_SHIFT 8 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_WIDTH 1 /* MIXOUTR_MUTE */ +#define WM9090_IN1A_MIXOUTR_VOL_MASK 0x00C0 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_SHIFT 6 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_WIDTH 2 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1B_MIXOUTR_VOL_MASK 0x0030 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_SHIFT 4 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_WIDTH 2 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN2A_MIXOUTR_VOL_MASK 0x000C /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_SHIFT 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_WIDTH 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2B_MIXOUTR_VOL_MASK 0x0003 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_SHIFT 0 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_WIDTH 2 /* IN2B_MIXOUTR_VOL - [1:0] */ + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM9090_IN1A_TO_SPKMIX 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_MASK 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_SHIFT 6 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_WIDTH 1 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_MASK 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_SHIFT 4 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_WIDTH 1 /* IN1B_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_MASK 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_SHIFT 2 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_WIDTH 1 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_MASK 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_SHIFT 0 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_WIDTH 1 /* IN2B_TO_SPKMIX */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM9090_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9090_VMID_ENA 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_MASK 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_SHIFT 0 /* VMID_ENA */ +#define WM9090_VMID_ENA_WIDTH 1 /* VMID_ENA */ + +/* + * R70 (0x46) - Write Sequencer 0 + */ +#define WM9090_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9090_WSEQ_WRITE_INDEX_MASK 0x000F /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_WIDTH 4 /* WSEQ_WRITE_INDEX - [3:0] */ + +/* + * R71 (0x47) - Write Sequencer 1 + */ +#define WM9090_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R72 (0x48) - Write Sequencer 2 + */ +#define WM9090_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM9090_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R73 (0x49) - Write Sequencer 3 + */ +#define WM9090_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9090_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9090_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9090_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R74 (0x4A) - Write Sequencer 4 + */ +#define WM9090_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R75 (0x4B) - Write Sequencer 5 + */ +#define WM9090_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */ + +/* + * R76 (0x4C) - Charge Pump 1 + */ +#define WM9090_CP_ENA 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM9090_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R84 (0x54) - DC Servo 0 + */ +#define WM9090_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R85 (0x55) - DC Servo 1 + */ +#define WM9090_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R87 (0x57) - DC Servo 3 + */ +#define WM9090_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R88 (0x58) - DC Servo Readback 0 + */ +#define WM9090_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */ + +/* + * R89 (0x59) - DC Servo Readback 1 + */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_SHIFT 0 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_WIDTH 8 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ + +/* + * R90 (0x5A) - DC Servo Readback 2 + */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_SHIFT 0 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_WIDTH 8 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ + +/* + * R96 (0x60) - Analogue HP 0 + */ +#define WM9090_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R98 (0x62) - AGC Control 0 + */ +#define WM9090_AGC_CLIP_ENA 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_MASK 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_SHIFT 15 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_WIDTH 1 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_THR_MASK 0x0F00 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_SHIFT 8 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_WIDTH 4 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_ATK_MASK 0x0070 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_SHIFT 4 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_WIDTH 3 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_DCY_MASK 0x0007 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_SHIFT 0 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_WIDTH 3 /* AGC_CLIP_DCY - [2:0] */ + +/* + * R99 (0x63) - AGC Control 1 + */ +#define WM9090_AGC_PWR_ENA 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_MASK 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_SHIFT 15 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_WIDTH 1 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_AVG 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_MASK 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_SHIFT 12 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_WIDTH 1 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_THR_MASK 0x0F00 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_SHIFT 8 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_WIDTH 4 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_ATK_MASK 0x0070 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_SHIFT 4 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_WIDTH 3 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_DCY_MASK 0x0007 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_SHIFT 0 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_WIDTH 3 /* AGC_PWR_DCY - [2:0] */ + +/* + * R100 (0x64) - AGC Control 2 + */ +#define WM9090_AGC_RAMP 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_MASK 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_SHIFT 8 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_WIDTH 1 /* AGC_RAMP */ +#define WM9090_AGC_MINGAIN_MASK 0x003F /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_SHIFT 0 /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_WIDTH 6 /* AGC_MINGAIN - [5:0] */ + +#endif -- cgit v1.2.3-18-g5258 From 7b4c734eead5ef0b1c95ec336ddd28e58e648676 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 29 Apr 2010 10:58:08 +0300 Subject: ASoC: TWL4030: AIF/APLL fix in DAPM domain This patch orders the APLL and AIF power sequence in case of HiFi (audio in TWL4030 terms) playback/capture. We also need to make sure that the AIF is running during playback/capture, when there is no valid DAPM route available. For this purpose I introduce these virtual widgets: /* To have complete playback route all the time */ DAPM_OUTPUT("Virtual HiFi OUT") /* Will keep AIF/APLL enabled */ /* To have complete capture route all the time */ DAPM_INPUT("Virtual HiFi IN") /* Will keep AIF/APLL enabled */ /* To have complete playback route for the voice module */ DAPM_OUTPUT("Virtual Voice OUT") /* Will keep APLL enabled */ The DAPM_SUPPLY widgets for APLL and AIF are placed in a way, that during any audio activity the needed configuration of AIF and APLL will be enabled (playback, capture, analog loopback, digital loopback, and voice activity). The apll reference counting code has been lifted, and modified from Liam Girdwood's earlier patch. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 82 +++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 2e025a3a261..12931f6d445 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -123,6 +123,8 @@ struct twl4030_priv { struct snd_soc_codec codec; unsigned int codec_powered; + + /* reference counts of AIF/APLL users */ unsigned int apll_enabled; struct snd_pcm_substream *master_substream; @@ -259,22 +261,22 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); - int status; - - if (enable == twl4030->apll_enabled) - return; + int status = -1; - if (enable) - /* Enable PLL */ - status = twl4030_codec_enable_resource(TWL4030_CODEC_RES_APLL); - else - /* Disable PLL */ - status = twl4030_codec_disable_resource(TWL4030_CODEC_RES_APLL); + if (enable) { + twl4030->apll_enabled++; + if (twl4030->apll_enabled == 1) + status = twl4030_codec_enable_resource( + TWL4030_CODEC_RES_APLL); + } else { + twl4030->apll_enabled--; + if (!twl4030->apll_enabled) + status = twl4030_codec_disable_resource( + TWL4030_CODEC_RES_APLL); + } if (status >= 0) twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status); - - twl4030->apll_enabled = enable; } static void twl4030_power_up(struct snd_soc_codec *codec) @@ -672,6 +674,31 @@ static int apll_event(struct snd_soc_dapm_widget *w, return 0; } +static int aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u8 audio_if; + + audio_if = twl4030_read_reg_cache(w->codec, TWL4030_REG_AUDIO_IF); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable AIF */ + /* enable the PLL before we use it to clock the DAI */ + twl4030_apll_enable(w->codec, 1); + + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if | TWL4030_AIF_EN); + break; + case SND_SOC_DAPM_POST_PMD: + /* disable the DAI before we stop it's source PLL */ + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if & ~TWL4030_AIF_EN); + twl4030_apll_enable(w->codec, 0); + break; + } + return 0; +} + static void headset_ramp(struct snd_soc_codec *codec, int ramp) { struct snd_soc_device *socdev = codec->socdev; @@ -1180,6 +1207,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HFR"), SND_SOC_DAPM_OUTPUT("VIBRA"), + /* AIF and APLL clocks for running DAIs (including loopback) */ + SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"), + SND_SOC_DAPM_INPUT("Virtual HiFi IN"), + SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"), + /* DACs */ SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback", SND_SOC_NOPM, 0, 0), @@ -1243,7 +1275,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event, SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("AIF Enable", TWL4030_REG_AUDIO_IF, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), /* Output MIXER controls */ /* Earpiece */ @@ -1373,10 +1406,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, /* Supply for the digital part (APLL) */ - {"Digital R1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital R2 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L2 Playback Mixer", NULL, "APLL Enable"}, {"Digital Voice Playback Mixer", NULL, "APLL Enable"}, {"Digital R1 Playback Mixer", NULL, "AIF Enable"}, @@ -1450,6 +1479,14 @@ static const struct snd_soc_dapm_route intercon[] = { {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ + /* Must be always connected (for AIF and APLL) */ + {"Virtual HiFi OUT", NULL, "Digital L1 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital R1 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital L2 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital R2 Playback Mixer"}, + /* Must be always connected (for APLL) */ + {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, + /* Physical outputs */ {"OUTL", NULL, "Analog L2 Playback Mixer"}, {"OUTR", NULL, "Analog R2 Playback Mixer"}, {"EARPIECE", NULL, "Earpiece PGA"}, @@ -1465,6 +1502,12 @@ static const struct snd_soc_dapm_route intercon[] = { {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ + /* Must be always connected (for AIF and APLL) */ + {"ADC Virtual Left1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Left2", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right2", NULL, "Virtual HiFi IN"}, + /* Physical inputs */ {"Analog Left", "Main Mic Capture Switch", "MAINMIC"}, {"Analog Left", "Headset Mic Capture Switch", "HSMIC"}, {"Analog Left", "AUXL Capture Switch", "AUXL"}, @@ -1497,11 +1540,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Virtual Left2", NULL, "TX2 Capture Route"}, {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, - {"ADC Virtual Left1", NULL, "APLL Enable"}, - {"ADC Virtual Right1", NULL, "APLL Enable"}, - {"ADC Virtual Left2", NULL, "APLL Enable"}, - {"ADC Virtual Right2", NULL, "APLL Enable"}, - {"ADC Virtual Left1", NULL, "AIF Enable"}, {"ADC Virtual Right1", NULL, "AIF Enable"}, {"ADC Virtual Left2", NULL, "AIF Enable"}, -- cgit v1.2.3-18-g5258 From 1b7c9afbfbfde93d4da89dcebfd2314f7d79c064 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 29 Apr 2010 10:58:09 +0300 Subject: ASoC: TWL4030: Remove OUTL/R outputs OUTL/R are leftovers from the original driver, and they are no longer needed. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 12931f6d445..b717a03dfac 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1194,8 +1194,6 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DIGIMIC1"), /* Outputs */ - SND_SOC_DAPM_OUTPUT("OUTL"), - SND_SOC_DAPM_OUTPUT("OUTR"), SND_SOC_DAPM_OUTPUT("EARPIECE"), SND_SOC_DAPM_OUTPUT("PREDRIVEL"), SND_SOC_DAPM_OUTPUT("PREDRIVER"), @@ -1487,8 +1485,6 @@ static const struct snd_soc_dapm_route intercon[] = { /* Must be always connected (for APLL) */ {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, /* Physical outputs */ - {"OUTL", NULL, "Analog L2 Playback Mixer"}, - {"OUTR", NULL, "Analog R2 Playback Mixer"}, {"EARPIECE", NULL, "Earpiece PGA"}, {"PREDRIVEL", NULL, "PredriveL PGA"}, {"PREDRIVER", NULL, "PredriveR PGA"}, -- cgit v1.2.3-18-g5258 From ef909d67299498010f07889bd0980c829ae78990 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:33 +0300 Subject: ASoC: tlv320dac33: Optimize power up, and restore On power up we only need to initialize the codec, and restore only registers, which are not in either in DAPM nor in the playback start sequence. These are mostly gain related registers. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 106 +++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 67 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 54b2a0508a1..329a97f6e0f 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -284,45 +284,49 @@ static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, return ret; } -static void dac33_restore_regs(struct snd_soc_codec *codec) +static void dac33_init_chip(struct snd_soc_codec *codec) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - u8 *cache = codec->reg_cache; - u8 data[2]; - int i, ret; - if (!dac33->chip_power) + if (unlikely(!dac33->chip_power)) return; - for (i = DAC33_PWR_CTRL; i <= DAC33_INTP_CTRL_B; i++) { - data[0] = i; - data[1] = cache[i]; - /* Skip the read only registers */ - if ((i >= DAC33_INT_OSC_STATUS && - i <= DAC33_INT_OSC_FREQ_RAT_READ_B) || - (i >= DAC33_FIFO_WPTR_MSB && i <= DAC33_FIFO_IRQ_FLAG) || - i == DAC33_DAC_STATUS_FLAGS || - i == DAC33_SRC_EST_REF_CLK_RATIO_A || - i == DAC33_SRC_EST_REF_CLK_RATIO_B) - continue; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LDAC_PWR_CTRL; i <= DAC33_LINEL_TO_LLO_VOL; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LINER_TO_RLO_VOL; i <= DAC33_OSC_TRIM; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } + /* 44-46: DAC Control Registers */ + /* A : DAC sample rate Fsref/1.5 */ + dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); + /* B : DAC src=normal, not muted */ + dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | + DAC33_DACSRCL_LEFT); + /* C : (defaults) */ + dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); + + /* 64-65 : L&R DAC power control + Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ + dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); + dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); + + /* 73 : volume soft stepping control, + clock source = internal osc (?) */ + dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); + + /* 66 : LOP/LOM Modes */ + dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); + + /* 68 : LOM inverted from LOP */ + dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); + + dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); + + /* Restore only selected registers (gains mostly) */ + dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL)); + dac33_write(codec, DAC33_RDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(codec, DAC33_RDAC_DIG_VOL_CTRL)); + + dac33_write(codec, DAC33_LINEL_TO_LLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINEL_TO_LLO_VOL)); + dac33_write(codec, DAC33_LINER_TO_RLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL)); } static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) @@ -358,8 +362,7 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) dac33->chip_power = 1; - /* Restore registers */ - dac33_restore_regs(codec); + dac33_init_chip(codec); dac33_soft_power(codec, 1); } else { @@ -1269,35 +1272,6 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static void dac33_init_chip(struct snd_soc_codec *codec) -{ - /* 44-46: DAC Control Registers */ - /* A : DAC sample rate Fsref/1.5 */ - dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); - /* B : DAC src=normal, not muted */ - dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | - DAC33_DACSRCL_LEFT); - /* C : (defaults) */ - dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); - - /* 64-65 : L&R DAC power control - Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ - dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - - /* 73 : volume soft stepping control, - clock source = internal osc (?) */ - dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - - /* 66 : LOP/LOM Modes */ - dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); - - /* 68 : LOM inverted from LOP */ - dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); - - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); -} - static int dac33_soc_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -1313,8 +1287,6 @@ static int dac33_soc_probe(struct platform_device *pdev) /* Power up the codec */ dac33_hard_power(codec, 1); - /* Set default configuration */ - dac33_init_chip(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); -- cgit v1.2.3-18-g5258 From 239fe55c7fe17d67403cb1e9222fcaea84248974 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:34 +0300 Subject: ASoC: tlv320dac33: Revised module loading, and DAC33 ID read Optimize the way how tlv320dac33 is powered uppon module and soc initialization. Also read the DAC33 ID registers, and update the reg_cache to reflect it. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 329a97f6e0f..9944721a055 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -329,6 +329,15 @@ static void dac33_init_chip(struct snd_soc_codec *codec) dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL)); } +static inline void dac33_read_id(struct snd_soc_codec *codec) +{ + u8 reg; + + dac33_read(codec, DAC33_DEVICE_ID_MSB, ®); + dac33_read(codec, DAC33_DEVICE_ID_LSB, ®); + dac33_read(codec, DAC33_DEVICE_REV_ID, ®); +} + static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) { u8 reg; @@ -1285,9 +1294,6 @@ static int dac33_soc_probe(struct platform_device *pdev) socdev->card->codec = codec; dac33 = snd_soc_codec_get_drvdata(codec); - /* Power up the codec */ - dac33_hard_power(codec, 1); - /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1307,9 +1313,6 @@ static int dac33_soc_probe(struct platform_device *pdev) /* power on device */ dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* Bias level configuration has enabled regulator an extra time */ - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); - return 0; pcm_err: @@ -1459,8 +1462,6 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_gpio; } gpio_direction_output(dac33->power_gpio, 0); - } else { - dac33->chip_power = 1; } /* Check if the IRQ number is valid and request it */ @@ -1498,12 +1499,14 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto err_get; } - ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), - dac33->supplies); + /* Read the tlv320dac33 ID registers */ + ret = dac33_hard_power(codec, 1); if (ret != 0) { - dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); - goto err_enable; + dev_err(codec->dev, "Failed to power up codec: %d\n", ret); + goto error_codec; } + dac33_read_id(codec); + dac33_hard_power(codec, 0); ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -1518,14 +1521,9 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_codec; } - /* Shut down the codec for now */ - dac33_hard_power(codec, 0); - return ret; error_codec: - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); -err_enable: regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); err_get: if (dac33->irq >= 0) { @@ -1549,14 +1547,15 @@ static int __devexit dac33_i2c_remove(struct i2c_client *client) struct tlv320dac33_priv *dac33; dac33 = i2c_get_clientdata(client); - dac33_hard_power(&dac33->codec, 0); + + if (unlikely(dac33->chip_power)) + dac33_hard_power(&dac33->codec, 0); if (dac33->power_gpio >= 0) gpio_free(dac33->power_gpio); if (dac33->irq >= 0) free_irq(dac33->irq, &dac33->codec); - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); destroy_workqueue(dac33->dac33_wq); -- cgit v1.2.3-18-g5258 From 0b61d2b9f2f78fc55faaedcc37f622ffd4103d14 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:35 +0300 Subject: ASoC: tlv320dac33: Manage a pointer for snd_pcm_substream in private structure As a preparation for supporting codec to be turned off, when we are in BIAS_STANDBY. The substream must be easily available in other places than pcm_* callbacks. Manage a pointer in _startup, and _shutdown for this. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 9944721a055..50d152215ab 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -91,6 +91,7 @@ struct tlv320dac33_priv { struct work_struct work; struct snd_soc_codec codec; struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES]; + struct snd_pcm_substream *substream; int power_gpio; int chip_power; int irq; @@ -720,6 +721,31 @@ static void dac33_oscwait(struct snd_soc_codec *codec) "internal oscillator calibration failed\n"); } +static int dac33_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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + /* Stream started, save the substream pointer */ + dac33->substream = substream; + + return 0; +} + +static void dac33_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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + dac33->substream = NULL; +} + static int dac33_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1367,6 +1393,8 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320dac33); #define DAC33_FORMATS SNDRV_PCM_FMTBIT_S16_LE static struct snd_soc_dai_ops dac33_dai_ops = { + .startup = dac33_startup, + .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, -- cgit v1.2.3-18-g5258 From ad05c03b1c4c1fb4db066a7bd502b674148ccd89 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:36 +0300 Subject: ASoC: tlv320dac33: Support for turning off the codec Let the codec to hit OFF instead of STANDBY, when there is no activity. When the codec is off, than the associated regulator can be also turned off (if the number of users on the regulator is 0). After initialization, the codec remains in power off, it is only turned on for reading the ID registers (also testing the regulators). The codec power is enabled, when the codec is moving from BIAS_OFF to BIAS_STANDBY. The codec is turned off, when it hits BIAS_OFF. There are few scenarios, which has to be taken care:: 1. Analog bypass caused BIAS_OFF -> BIAS_ON We need to power on the codec, and do the chip init, but we does not need to execute the playback related configuration 2. Playback caused BIAS_OFF -> BIAS_ON We need to power on the codec, and do the chip init, and also we need to execute the playback related configuration. 3. Playback start, while Analog bypass is on (BIAS_ON -> BIAS_ON) We need to execute the playback related configuration. The codec is already on. 4. Analog bypass enable, while playback (BIAS_ON -> BIAS_ON) Nothing need to be done. 5. Playback start withing soc power down timeout (BIAS_ON -> BIAS_ON) We need to execute the playback related configuration. The codec is still on. Since the power up, and the codec init is optimized, the added overhead in stream start is minimal. Withing this patch, the hard_power function is now only doing what it supposed to: only handle the powers, and GPIO reset line. The codec initialization and state restore has been moved out. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 66 ++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 50d152215ab..68b7ccbf2e7 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -61,6 +61,8 @@ #define US_TO_SAMPLES(rate, us) \ (rate / (1000000 / us)) +static void dac33_calculate_times(struct snd_pcm_substream *substream); +static int dac33_prepare_chip(struct snd_pcm_substream *substream); static struct snd_soc_codec *tlv320dac33_codec; @@ -355,9 +357,17 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) static int dac33_hard_power(struct snd_soc_codec *codec, int power) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - int ret; + int ret = 0; mutex_lock(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_warn(codec->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); + goto exit; + } + if (power) { ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), dac33->supplies); @@ -371,10 +381,6 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) gpio_set_value(dac33->power_gpio, 1); dac33->chip_power = 1; - - dac33_init_chip(codec); - - dac33_soft_power(codec, 1); } else { dac33_soft_power(codec, 0); if (dac33->power_gpio >= 0) @@ -396,6 +402,22 @@ exit: return ret; } +static int playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(w->codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (likely(dac33->substream)) { + dac33_calculate_times(dac33->substream); + dac33_prepare_chip(dac33->substream); + } + break; + } + return 0; +} + static int dac33_get_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -525,6 +547,8 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0), SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amp Power", DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), + + SND_SOC_DAPM_PRE("Prepare Playback", playback_event), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -567,18 +591,18 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Coming from OFF, switch on the codec */ ret = dac33_hard_power(codec, 1); if (ret != 0) return ret; - } - dac33_soft_power(codec, 0); + dac33_init_chip(codec); + } break; case SND_SOC_BIAS_OFF: ret = dac33_hard_power(codec, 0); if (ret != 0) return ret; - break; } codec->bias_level = level; @@ -829,6 +853,16 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) } mutex_lock(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ + mutex_unlock(&dac33->mutex); + return 0; + } + dac33_soft_power(codec, 0); dac33_soft_power(codec, 1); @@ -1035,15 +1069,6 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) } -static int dac33_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - dac33_calculate_times(substream); - dac33_prepare_chip(substream); - - return 0; -} - static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { @@ -1336,9 +1361,6 @@ static int dac33_soc_probe(struct platform_device *pdev) dac33_add_widgets(codec); - /* power on device */ - dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; pcm_err: @@ -1375,6 +1397,8 @@ static int dac33_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + dac33_set_bias_level(codec, SND_SOC_BIAS_PREPARE); dac33_set_bias_level(codec, codec->suspend_bias_level); return 0; @@ -1396,7 +1420,6 @@ static struct snd_soc_dai_ops dac33_dai_ops = { .startup = dac33_startup, .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, - .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, @@ -1450,6 +1473,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, codec->hw_write = (hw_write_t) i2c_master_send; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = dac33_set_bias_level; + codec->idle_bias_off = 1; codec->dai = &dac33_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(dac33_reg); -- cgit v1.2.3-18-g5258 From e5e5b31e8c729b6bae569bec0790c655ee0121a1 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 4 May 2010 11:08:18 +0300 Subject: ASoC: tpa6130a2: TLV mapping for tpa6140a2 Both tpa6130a2, and tpa6140a2 is supported by the same driver, but the gain dB scaling is different on the amplifiers. Provide different mixer control for the chips with correct TLV mapping. User space will see: "TPA6130A2 Headphone Playback Volume" in case of 6130 "TPA6140A2 Headphone Playback Volume" in case of 6140 The way machine drivers are using this amplifier remained the same. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 958d49c969a..0cf3e3862e7 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -53,6 +53,7 @@ struct tpa6130a2_data { struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES]; int power_gpio; unsigned char power_state; + enum tpa_model id; }; static int tpa6130a2_i2c_read(int reg) @@ -263,6 +264,20 @@ static const struct snd_kcontrol_new tpa6130a2_controls[] = { tpa6130_tlv), }; +static const unsigned int tpa6140_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0), + 9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0), + 17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0), +}; + +static const struct snd_kcontrol_new tpa6140a2_controls[] = { + SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, + tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6140_tlv), +}; + /* * Enable or disable channel (left or right) * The bit number for mute and amplifier are the same per channel: @@ -368,13 +383,22 @@ static const struct snd_soc_dapm_route audio_map[] = { int tpa6130a2_add_controls(struct snd_soc_codec *codec) { + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + data = i2c_get_clientdata(tpa6130a2_client); + snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets, ARRAY_SIZE(tpa6130a2_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - return snd_soc_add_controls(codec, tpa6130a2_controls, - ARRAY_SIZE(tpa6130a2_controls)); + if (data->id == TPA6140A2) + return snd_soc_add_controls(codec, tpa6140a2_controls, + ARRAY_SIZE(tpa6140a2_controls)); + else + return snd_soc_add_controls(codec, tpa6130a2_controls, + ARRAY_SIZE(tpa6130a2_controls)); } EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); @@ -407,6 +431,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, pdata = client->dev.platform_data; data->power_gpio = pdata->power_gpio; + data->id = pdata->id; mutex_init(&data->mutex); @@ -425,7 +450,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, gpio_direction_output(data->power_gpio, 0); } - switch (pdata->id) { + switch (data->id) { case TPA6130A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; -- cgit v1.2.3-18-g5258 From 985d8c4c9eb18bd7b295c73a5ab1fc990e5ed182 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 May 2010 16:25:52 +0100 Subject: ASoC: Add debug output tracing all cache register writes Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-cache.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 9dfe9a58a31..472af38188c 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -44,6 +44,8 @@ static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 2); if (ret == 2) return 0; @@ -112,6 +114,8 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 2); if (ret == 2) return 0; @@ -171,6 +175,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) return 0; else @@ -205,6 +211,8 @@ static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) return 0; else @@ -362,6 +370,8 @@ static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 3); if (ret == 3) return 0; @@ -472,6 +482,8 @@ static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 4); if (ret == 4) return 0; -- cgit v1.2.3-18-g5258 From d07e7ce9b6fd1a4ff0a6b40c74fbecdc584a135b Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:55:21 +0900 Subject: ASoC: S3C: I2Sv2: Move defines closer to driver The header for I2Sv2 linux/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h contains only controller specific definitions and nothing SoC specific. So, it could be moved to sound/soc/s3c24xx/ Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- .../plat-samsung/include/plat/regs-s3c2412-iis.h | 82 ---------------------- sound/soc/s3c24xx/regs-i2s-v2.h | 82 ++++++++++++++++++++++ sound/soc/s3c24xx/s3c-i2s-v2.c | 3 +- sound/soc/s3c24xx/s3c2412-i2s.c | 3 +- sound/soc/s3c24xx/s3c64xx-i2s.c | 2 +- 5 files changed, 85 insertions(+), 87 deletions(-) delete mode 100644 arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h create mode 100644 sound/soc/s3c24xx/regs-i2s-v2.h diff --git a/arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h b/arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h deleted file mode 100644 index abf2fbc2eb2..00000000000 --- a/arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h +++ /dev/null @@ -1,82 +0,0 @@ -/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h - * - * Copyright 2007 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * - * 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. - * - * S3C2412 IIS register definition -*/ - -#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H -#define __ASM_ARCH_REGS_S3C2412_IIS_H - -#define S3C2412_IISCON (0x00) -#define S3C2412_IISMOD (0x04) -#define S3C2412_IISFIC (0x08) -#define S3C2412_IISPSR (0x0C) -#define S3C2412_IISTXD (0x10) -#define S3C2412_IISRXD (0x14) - -#define S3C2412_IISCON_LRINDEX (1 << 11) -#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) -#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) -#define S3C2412_IISCON_TXFIFO_FULL (1 << 8) -#define S3C2412_IISCON_RXFIFO_FULL (1 << 7) -#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6) -#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5) -#define S3C2412_IISCON_TXCH_PAUSE (1 << 4) -#define S3C2412_IISCON_RXCH_PAUSE (1 << 3) -#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2) -#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) -#define S3C2412_IISCON_IIS_ACTIVE (1 << 0) - -#define S3C64XX_IISMOD_BLC_16BIT (0 << 13) -#define S3C64XX_IISMOD_BLC_8BIT (1 << 13) -#define S3C64XX_IISMOD_BLC_24BIT (2 << 13) -#define S3C64XX_IISMOD_BLC_MASK (3 << 13) - -#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) -#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) - -#define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) -#define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) -#define S3C2412_IISMOD_SLAVE (2 << 10) -#define S3C2412_IISMOD_MASTER_MASK (3 << 10) -#define S3C2412_IISMOD_MODE_TXONLY (0 << 8) -#define S3C2412_IISMOD_MODE_RXONLY (1 << 8) -#define S3C2412_IISMOD_MODE_TXRX (2 << 8) -#define S3C2412_IISMOD_MODE_MASK (3 << 8) -#define S3C2412_IISMOD_LR_LLOW (0 << 7) -#define S3C2412_IISMOD_LR_RLOW (1 << 7) -#define S3C2412_IISMOD_SDF_IIS (0 << 5) -#define S3C2412_IISMOD_SDF_MSB (1 << 5) -#define S3C2412_IISMOD_SDF_LSB (2 << 5) -#define S3C2412_IISMOD_SDF_MASK (3 << 5) -#define S3C2412_IISMOD_RCLK_256FS (0 << 3) -#define S3C2412_IISMOD_RCLK_512FS (1 << 3) -#define S3C2412_IISMOD_RCLK_384FS (2 << 3) -#define S3C2412_IISMOD_RCLK_768FS (3 << 3) -#define S3C2412_IISMOD_RCLK_MASK (3 << 3) -#define S3C2412_IISMOD_BCLK_32FS (0 << 1) -#define S3C2412_IISMOD_BCLK_48FS (1 << 1) -#define S3C2412_IISMOD_BCLK_16FS (2 << 1) -#define S3C2412_IISMOD_BCLK_24FS (3 << 1) -#define S3C2412_IISMOD_BCLK_MASK (3 << 1) -#define S3C2412_IISMOD_8BIT (1 << 0) - -#define S3C64XX_IISMOD_CDCLKCON (1 << 12) - -#define S3C2412_IISPSR_PSREN (1 << 15) - -#define S3C2412_IISFIC_TXFLUSH (1 << 15) -#define S3C2412_IISFIC_RXFLUSH (1 << 7) -#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) -#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) - - - -#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ - diff --git a/sound/soc/s3c24xx/regs-i2s-v2.h b/sound/soc/s3c24xx/regs-i2s-v2.h new file mode 100644 index 00000000000..abf2fbc2eb2 --- /dev/null +++ b/sound/soc/s3c24xx/regs-i2s-v2.h @@ -0,0 +1,82 @@ +/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h + * + * Copyright 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * 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. + * + * S3C2412 IIS register definition +*/ + +#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H +#define __ASM_ARCH_REGS_S3C2412_IIS_H + +#define S3C2412_IISCON (0x00) +#define S3C2412_IISMOD (0x04) +#define S3C2412_IISFIC (0x08) +#define S3C2412_IISPSR (0x0C) +#define S3C2412_IISTXD (0x10) +#define S3C2412_IISRXD (0x14) + +#define S3C2412_IISCON_LRINDEX (1 << 11) +#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) +#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) +#define S3C2412_IISCON_TXFIFO_FULL (1 << 8) +#define S3C2412_IISCON_RXFIFO_FULL (1 << 7) +#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6) +#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5) +#define S3C2412_IISCON_TXCH_PAUSE (1 << 4) +#define S3C2412_IISCON_RXCH_PAUSE (1 << 3) +#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2) +#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) +#define S3C2412_IISCON_IIS_ACTIVE (1 << 0) + +#define S3C64XX_IISMOD_BLC_16BIT (0 << 13) +#define S3C64XX_IISMOD_BLC_8BIT (1 << 13) +#define S3C64XX_IISMOD_BLC_24BIT (2 << 13) +#define S3C64XX_IISMOD_BLC_MASK (3 << 13) + +#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) +#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) + +#define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) +#define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) +#define S3C2412_IISMOD_SLAVE (2 << 10) +#define S3C2412_IISMOD_MASTER_MASK (3 << 10) +#define S3C2412_IISMOD_MODE_TXONLY (0 << 8) +#define S3C2412_IISMOD_MODE_RXONLY (1 << 8) +#define S3C2412_IISMOD_MODE_TXRX (2 << 8) +#define S3C2412_IISMOD_MODE_MASK (3 << 8) +#define S3C2412_IISMOD_LR_LLOW (0 << 7) +#define S3C2412_IISMOD_LR_RLOW (1 << 7) +#define S3C2412_IISMOD_SDF_IIS (0 << 5) +#define S3C2412_IISMOD_SDF_MSB (1 << 5) +#define S3C2412_IISMOD_SDF_LSB (2 << 5) +#define S3C2412_IISMOD_SDF_MASK (3 << 5) +#define S3C2412_IISMOD_RCLK_256FS (0 << 3) +#define S3C2412_IISMOD_RCLK_512FS (1 << 3) +#define S3C2412_IISMOD_RCLK_384FS (2 << 3) +#define S3C2412_IISMOD_RCLK_768FS (3 << 3) +#define S3C2412_IISMOD_RCLK_MASK (3 << 3) +#define S3C2412_IISMOD_BCLK_32FS (0 << 1) +#define S3C2412_IISMOD_BCLK_48FS (1 << 1) +#define S3C2412_IISMOD_BCLK_16FS (2 << 1) +#define S3C2412_IISMOD_BCLK_24FS (3 << 1) +#define S3C2412_IISMOD_BCLK_MASK (3 << 1) +#define S3C2412_IISMOD_8BIT (1 << 0) + +#define S3C64XX_IISMOD_CDCLKCON (1 << 12) + +#define S3C2412_IISPSR_PSREN (1 << 15) + +#define S3C2412_IISFIC_TXFLUSH (1 << 15) +#define S3C2412_IISFIC_RXFLUSH (1 << 7) +#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) + + + +#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ + diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 865f93143bf..cb3652ed057 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -24,10 +24,9 @@ #include #include -#include - #include +#include "regs-i2s-v2.h" #include "s3c-i2s-v2.h" #include "s3c-dma.h" diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index f3148f98b41..7134465693e 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -32,12 +32,11 @@ #include #include -#include - #include #include #include "s3c-dma.h" +#include "regs-i2s-v2.h" #include "s3c2412-i2s.h" #define S3C2412_I2S_DEBUG 0 diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index ab1fa159d3a..b7e951fe30a 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -18,7 +18,6 @@ #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include "s3c-dma.h" +#include "regs-i2s-v2.h" #include "s3c64xx-i2s.h" /* The value should be set to maximum of the total number -- cgit v1.2.3-18-g5258 From b720d56294187ea4167a20e59c4ea0bb4f064132 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:03 +0900 Subject: ASoC: SAMSUNG: I2S: Add bit definitions Define more bit definitions in the order of mainline support for the SoC. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/regs-i2s-v2.h | 42 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/sound/soc/s3c24xx/regs-i2s-v2.h b/sound/soc/s3c24xx/regs-i2s-v2.h index abf2fbc2eb2..f3d90289e7b 100644 --- a/sound/soc/s3c24xx/regs-i2s-v2.h +++ b/sound/soc/s3c24xx/regs-i2s-v2.h @@ -20,6 +20,24 @@ #define S3C2412_IISTXD (0x10) #define S3C2412_IISRXD (0x14) +#define S5PC1XX_IISFICS 0x18 +#define S5PC1XX_IISTXDS 0x1C + +#define S5PC1XX_IISCON_SW_RST (1 << 31) +#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26) +#define S5PC1XX_IISCON_FRXORINTEN (1 << 25) +#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24) +#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23) +#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20) +#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18) + +#define S3C64XX_IISCON_FTXURSTATUS (1 << 17) +#define S3C64XX_IISCON_FTXURINTEN (1 << 16) +#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15) +#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14) +#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13) +#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12) + #define S3C2412_IISCON_LRINDEX (1 << 11) #define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) #define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) @@ -33,6 +51,23 @@ #define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) #define S3C2412_IISCON_IIS_ACTIVE (1 << 0) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30) +#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30) +#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30) +#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30) +#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ +#define S5PC1XX_IISMOD_BLCS_MASK 0x3 +#define S5PC1XX_IISMOD_BLCS_SHIFT 26 +#define S5PC1XX_IISMOD_BLCP_MASK 0x3 +#define S5PC1XX_IISMOD_BLCP_SHIFT 24 + +#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19) +#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18) +#define S3C64XX_IISMOD_DC2_EN (1 << 17) +#define S3C64XX_IISMOD_DC1_EN (1 << 16) #define S3C64XX_IISMOD_BLC_16BIT (0 << 13) #define S3C64XX_IISMOD_BLC_8BIT (1 << 13) #define S3C64XX_IISMOD_BLC_24BIT (2 << 13) @@ -71,12 +106,15 @@ #define S3C2412_IISPSR_PSREN (1 << 15) +#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf) + #define S3C2412_IISFIC_TXFLUSH (1 << 15) #define S3C2412_IISFIC_RXFLUSH (1 << 7) #define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) #define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) - +#define S5PC1XX_IISFICS_TXFLUSH (1 << 15) +#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f) #endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ - -- cgit v1.2.3-18-g5258 From ce76f9fd34d4d39fbda330f14b327273551e2f54 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:27 +0900 Subject: ASoC: S3C2412: I2S: Debug IMS field The IMS field of s3c2412/13 is essentially the same as that of s3c64xx. That is, the IISMOD[11] bit decides Master/Slave mode and IISMOD[10] bit selects source clock for signal generation. For that reason, remove improper defines for IISMOD[11:10] field mask and define two 1bit fields that can be set independent of each other. As a consequence, corresponding fields for PLAT_S3C64XX too get to use these new defines. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/regs-i2s-v2.h | 6 ++---- sound/soc/s3c24xx/s3c-i2s-v2.c | 25 ++----------------------- sound/soc/s3c24xx/s3c2412-i2s.c | 8 ++------ 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/sound/soc/s3c24xx/regs-i2s-v2.h b/sound/soc/s3c24xx/regs-i2s-v2.h index f3d90289e7b..484858a5f33 100644 --- a/sound/soc/s3c24xx/regs-i2s-v2.h +++ b/sound/soc/s3c24xx/regs-i2s-v2.h @@ -76,10 +76,8 @@ #define S3C64XX_IISMOD_IMS_PCLK (0 << 10) #define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) -#define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) -#define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) -#define S3C2412_IISMOD_SLAVE (2 << 10) -#define S3C2412_IISMOD_MASTER_MASK (3 << 10) +#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10) +#define S3C2412_IISMOD_SLAVE (1 << 11) #define S3C2412_IISMOD_MODE_TXONLY (0 << 8) #define S3C2412_IISMOD_MODE_RXONLY (1 << 8) #define S3C2412_IISMOD_MODE_TXRX (2 << 8) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index cb3652ed057..b8803a182cc 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -265,35 +265,14 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("hw_params r: IISMOD: %x \n", iismod); -#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) -#define IISMOD_MASTER_MASK S3C2412_IISMOD_MASTER_MASK -#define IISMOD_SLAVE S3C2412_IISMOD_SLAVE -#define IISMOD_MASTER S3C2412_IISMOD_MASTER_INTERNAL -#endif - -#if defined(CONFIG_PLAT_S3C64XX) -/* From Rev1.1 datasheet, we have two master and two slave modes: - * IMS[11:10]: - * 00 = master mode, fed from PCLK - * 01 = master mode, fed from CLKAUDIO - * 10 = slave mode, using PCLK - * 11 = slave mode, using I2SCLK - */ -#define IISMOD_MASTER_MASK (1 << 11) -#define IISMOD_SLAVE (1 << 11) -#define IISMOD_MASTER (0 << 11) -#endif - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: i2s->master = 0; - iismod &= ~IISMOD_MASTER_MASK; - iismod |= IISMOD_SLAVE; + iismod |= S3C2412_IISMOD_SLAVE; break; case SND_SOC_DAIFMT_CBS_CFS: i2s->master = 1; - iismod &= ~IISMOD_MASTER_MASK; - iismod |= IISMOD_MASTER; + iismod &= ~S3C2412_IISMOD_SLAVE; break; default: pr_err("unknwon master/slave format\n"); diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 7134465693e..cf0c15482c3 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -78,14 +78,10 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, switch (clk_id) { case S3C2412_CLKSRC_PCLK: - s3c2412_i2s.master = 1; - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_MASTER_INTERNAL; + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; break; case S3C2412_CLKSRC_I2SCLK: - s3c2412_i2s.master = 0; - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_MASTER_EXTERNAL; + iismod |= S3C2412_IISMOD_IMS_SYSMUX; break; default: return -EINVAL; -- cgit v1.2.3-18-g5258 From d79696ff443eb16a1a13bd7db030a25fee9f44a7 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:34 +0900 Subject: ASoC: S3C2412: I2S: Return correct source clock Until now, s3c2412_get_iisclk would return NULL since iis_clk was never initialized. Return appropriate pointer as per the selection made for source clock. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c2412-i2s.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index cf0c15482c3..ce2daacc4eb 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -94,7 +94,13 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, struct clk *s3c2412_get_iisclk(void) { - return s3c2412_i2s.iis_clk; + struct s3c_i2sv2_info *i2s = &s3c2412_i2s; + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; } EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); -- cgit v1.2.3-18-g5258 From 21a7ad08e274ce7b669b165d65e0bab8391552a6 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:39 +0900 Subject: ASoC: S3C: I2Sv2: Discard redundant field iis_clk No need to keep redundant field iis_clk in s3c_i2sv2_info. iis_cclk and iis_pclk is all we need. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.h | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h index b094d3c23cb..ea56467f5ad 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -49,7 +49,6 @@ struct s3c_i2sv2_info { struct clk *iis_pclk; struct clk *iis_cclk; - struct clk *iis_clk; unsigned char master; -- cgit v1.2.3-18-g5258 From 5728242789d3538abcfdcb28e01798801efd9988 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:45 +0900 Subject: ASoC: S3C: I2Sv2: Unify i2s_get_clock callback Now that we have two callbacks s3c2412_i2s_get_clock & s3c64xx_i2s_get_clock doing exactly the same thing, we can define one generic s3c_i2sv2_get_clock and discard other two copies. Also, switch the users to make calls to the newly defined and generic s3c_i2sv2_get_clock Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/jive_wm8750.c | 2 +- sound/soc/s3c24xx/s3c-i2s-v2.c | 12 ++++++++++++ sound/soc/s3c24xx/s3c-i2s-v2.h | 2 ++ sound/soc/s3c24xx/s3c2412-i2s.c | 13 ------------- sound/soc/s3c24xx/s3c2412-i2s.h | 2 -- sound/soc/s3c24xx/s3c64xx-i2s.c | 12 ------------ sound/soc/s3c24xx/s3c64xx-i2s.h | 2 -- 7 files changed, 15 insertions(+), 30 deletions(-) diff --git a/sound/soc/s3c24xx/jive_wm8750.c b/sound/soc/s3c24xx/jive_wm8750.c index 97d8ff3196b..8c108b121c1 100644 --- a/sound/soc/s3c24xx/jive_wm8750.c +++ b/sound/soc/s3c24xx/jive_wm8750.c @@ -70,7 +70,7 @@ static int jive_hw_params(struct snd_pcm_substream *substream, } s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params), - s3c2412_get_iisclk()); + s3c_i2sv2_get_clock(cpu_dai)); /* set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index b8803a182cc..ac153ca9c62 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -516,6 +516,18 @@ static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, return delay; } +struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock); + /* default table of all avaialable root fs divisors */ static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h index ea56467f5ad..39a6db6a565 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -60,6 +60,8 @@ struct s3c_i2sv2_info { u32 suspend_iispsr; }; +extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai); + struct s3c_i2sv2_rate_calc { unsigned int clk_div; /* for prescaler */ unsigned int fs_div; /* for root frame clock */ diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index ce2daacc4eb..6b884d75235 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -91,19 +91,6 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, return 0; } - -struct clk *s3c2412_get_iisclk(void) -{ - struct s3c_i2sv2_info *i2s = &s3c2412_i2s; - u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - - if (iismod & S3C2412_IISMOD_IMS_SYSMUX) - return i2s->iis_cclk; - else - return i2s->iis_pclk; -} -EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); - static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) { return cpu_dai->private_data; diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h index 60cac002a83..0b5686b4d5c 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -24,8 +24,6 @@ #define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK #define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS -extern struct clk *s3c2412_get_iisclk(void); - extern struct snd_soc_dai s3c2412_i2s_dai; #endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */ diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index b7e951fe30a..97327ef09de 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -91,18 +91,6 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, return 0; } -struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai) -{ - struct s3c_i2sv2_info *i2s = to_info(dai); - u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - - if (iismod & S3C64XX_IISMOD_IMS_SYSMUX) - return i2s->iis_cclk; - else - return i2s->iis_pclk; -} -EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock); - static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index 53d2a0a0df3..f27ed50e4d8 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -38,6 +38,4 @@ struct clk; extern struct snd_soc_dai s3c64xx_i2s_dai[]; -extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai); - #endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ -- cgit v1.2.3-18-g5258 From d47ef9c79dd46c1b33696f01b6cf744b77150379 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:51 +0900 Subject: ASoC: S3C64XX: I2S: Use s3c2412 defines Now that the fields are defined for s3c2412, use them and avoid having multiple copies of same defines. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/regs-i2s-v2.h | 3 --- sound/soc/s3c24xx/s3c64xx-i2s.c | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/s3c24xx/regs-i2s-v2.h b/sound/soc/s3c24xx/regs-i2s-v2.h index 484858a5f33..5e5e5680580 100644 --- a/sound/soc/s3c24xx/regs-i2s-v2.h +++ b/sound/soc/s3c24xx/regs-i2s-v2.h @@ -73,9 +73,6 @@ #define S3C64XX_IISMOD_BLC_24BIT (2 << 13) #define S3C64XX_IISMOD_BLC_MASK (3 << 13) -#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) -#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) - #define S3C2412_IISMOD_IMS_SYSMUX (1 << 10) #define S3C2412_IISMOD_SLAVE (1 << 11) #define S3C2412_IISMOD_MODE_TXONLY (0 << 8) diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 97327ef09de..6d49bd93717 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -62,11 +62,11 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, switch (clk_id) { case S3C64XX_CLKSRC_PCLK: - iismod &= ~S3C64XX_IISMOD_IMS_SYSMUX; + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; break; case S3C64XX_CLKSRC_MUX: - iismod |= S3C64XX_IISMOD_IMS_SYSMUX; + iismod |= S3C2412_IISMOD_IMS_SYSMUX; break; case S3C64XX_CLKSRC_CDCLK: -- cgit v1.2.3-18-g5258 From 9e991a4bf3290b0787d33fa72144f6eec656f20d Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:56:56 +0900 Subject: ASoC: S3C: I2Sv2: New field for controller feature In order to make s3c-i2s-v2.c manage controllers with minor quirks and variation in features, we define a per-block flag that indicates the availability/lack of a particular feature to the s3c-i2s-v2.c While adding support for new SoCs' I2S, check for the blocks of older SoCs that have similar feature and set the flag for that feature. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h index 39a6db6a565..766f43a13d8 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -29,10 +29,16 @@ #define S3C_I2SV2_CLKSRC_AUDIOBUS 1 #define S3C_I2SV2_CLKSRC_CDCLK 2 +/* Set this flag for I2S controllers that have the bit IISMOD[12] + * bridge/break RCLK signal and external Xi2sCDCLK pin. + */ +#define S3C_FEATURE_CDCLKCON (1 << 0) + /** * struct s3c_i2sv2_info - S3C I2S-V2 information * @dev: The parent device passed to use from the probe. * @regs: The pointer to the device registe block. + * @feature: Set of bit-flags indicating features of the controller. * @master: True if the I2S core is the I2S bit clock master. * @dma_playback: DMA information for playback channel. * @dma_capture: DMA information for capture channel. @@ -47,6 +53,8 @@ struct s3c_i2sv2_info { struct device *dev; void __iomem *regs; + u32 feature; + struct clk *iis_pclk; struct clk *iis_cclk; -- cgit v1.2.3-18-g5258 From 8a7c251871eff512fb380ea0e86c77ed901520fe Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:57:05 +0900 Subject: ASoC: S3C: I2S: Move set_sysclk to common code Now that we can specify feature of a particular controller, we can avoid multiple copies of same code by defining the CDCLKCON bit feature in controller specific code and detecting that flag in the code common to all controllers. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 47 +++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c2412-i2s.c | 27 ----------------------- sound/soc/s3c24xx/s3c64xx-i2s.c | 43 +++---------------------------------- 3 files changed, 50 insertions(+), 67 deletions(-) diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index ac153ca9c62..13311c8cf96 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -342,6 +342,52 @@ static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, writel(iismod, i2s->regs + S3C2412_IISMOD); pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + pr_debug("Entered %s\n", __func__); + pr_debug("%s r: IISMOD: %x\n", __func__, iismod); + + switch (clk_id) { + case S3C_I2SV2_CLKSRC_PCLK: + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_AUDIOBUS: + iismod |= S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_CDCLK: + /* Error if controller doesn't have the CDCLKCON bit */ + if (!(i2s->feature & S3C_FEATURE_CDCLKCON)) + return -EINVAL; + + switch (dir) { + case SND_SOC_CLOCK_IN: + iismod |= S3C64XX_IISMOD_CDCLKCON; + break; + case SND_SOC_CLOCK_OUT: + iismod &= ~S3C64XX_IISMOD_CDCLKCON; + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s w: IISMOD: %x\n", __func__, iismod); + return 0; } @@ -714,6 +760,7 @@ int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) ops->hw_params = s3c_i2sv2_hw_params; ops->set_fmt = s3c2412_i2s_set_fmt; ops->set_clkdiv = s3c2412_i2s_set_clkdiv; + ops->set_sysclk = s3c_i2sv2_set_sysclk; /* Allow overriding by (for example) IISv4 */ if (!ops->delay) diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 6b884d75235..709adef9d04 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -65,32 +65,6 @@ static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = { static struct s3c_i2sv2_info s3c2412_i2s; -/* - * Set S3C2412 Clock source - */ -static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) -{ - u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - - pr_debug("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id, - freq, dir); - - switch (clk_id) { - case S3C2412_CLKSRC_PCLK: - iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; - break; - case S3C2412_CLKSRC_I2SCLK: - iismod |= S3C2412_IISMOD_IMS_SYSMUX; - break; - default: - return -EINVAL; - } - - writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); - return 0; -} - static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) { return cpu_dai->private_data; @@ -175,7 +149,6 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { - .set_sysclk = s3c2412_i2s_set_sysclk, .hw_params = s3c2412_i2s_hw_params, }; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 6d49bd93717..1d85cb85a7d 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -54,43 +54,6 @@ static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) return cpu_dai->private_data; } -static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) -{ - struct s3c_i2sv2_info *i2s = to_info(cpu_dai); - u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - - switch (clk_id) { - case S3C64XX_CLKSRC_PCLK: - iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; - break; - - case S3C64XX_CLKSRC_MUX: - iismod |= S3C2412_IISMOD_IMS_SYSMUX; - break; - - case S3C64XX_CLKSRC_CDCLK: - switch (dir) { - case SND_SOC_CLOCK_IN: - iismod |= S3C64XX_IISMOD_CDCLKCON; - break; - case SND_SOC_CLOCK_OUT: - iismod &= ~S3C64XX_IISMOD_CDCLKCON; - break; - default: - return -EINVAL; - } - break; - - default: - return -EINVAL; - } - - writel(iismod, i2s->regs + S3C2412_IISMOD); - - return 0; -} - static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { @@ -115,9 +78,7 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, } -static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { - .set_sysclk = s3c64xx_i2s_set_sysclk, -}; +static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops; static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) { @@ -147,6 +108,8 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) dai->probe = s3c64xx_i2s_probe; dai->ops = &s3c64xx_i2s_dai_ops; + i2s->feature |= S3C_FEATURE_CDCLKCON; + i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; -- cgit v1.2.3-18-g5258 From ef5dbbccbbfa7d2211fa8efcc095a9f4a7912dda Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 May 2010 08:40:25 +0200 Subject: ALSA: hda - Remove superfluous external amp setup for ALC888 We had a fixed external amp setup enabled for ALC888, but this seems unnecessary. The amps are controlled rather by GPIOs. Let's remove it now. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 2078a1ac0f1..2b0131fb026 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -10580,9 +10580,6 @@ static int patch_alc882(struct hda_codec *codec) spec->stream_digital_playback = &alc882_pcm_digital_playback; spec->stream_digital_capture = &alc882_pcm_digital_capture; - if (codec->vendor_id == 0x10ec0888) - spec->init_amp = ALC_INIT_DEFAULT; /* always initialize */ - if (!spec->adc_nids && spec->input_mux) { int i, j; spec->num_adc_nids = 0; -- cgit v1.2.3-18-g5258 From 49100c98359a56ea4e8c9a76e3d625cdb25f25f5 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 5 May 2010 11:14:22 +0300 Subject: ASoC: omap: Add basic audio support for Nokia RX-51/N900 This patch adds support for integrated stereo speakers and digital microphone found on Nokia RX-51 hardware. This is a cut down version based on Maemo kernel sources and earlier patchset by Eduardo Valentin et al. http://mailman.alsa-project.org/pipermail/alsa-devel/2009-October/022033.html Signed-off-by: Jarkko Nikula Cc: Eduardo Valentin Cc: Peter Ujfalusi Acked-by: Eduardo Valentin Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/Kconfig | 10 ++ sound/soc/omap/Makefile | 2 + sound/soc/omap/rx51.c | 294 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 sound/soc/omap/rx51.c diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index f11963c2187..83be4a76d2b 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -18,6 +18,16 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. +config SND_OMAP_SOC_RX51 + tristate "SoC Audio support for Nokia RX-51" + depends on SND_OMAP_SOC && MACH_NOKIA_RX51 + select OMAP_MCBSP + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on Nokia RX-51 + hardware. This is also known as Nokia N900 product. + config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" depends on SND_OMAP_SOC && MACH_AMS_DELTA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0bc00ca14b3..3a75755f25e 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-rx51-objs := rx51.o snd-soc-ams-delta-objs := ams-delta.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o @@ -22,6 +23,7 @@ snd-soc-zoom2-objs := zoom2.o snd-soc-igep0020-objs := igep0020.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 00000000000..47d831ef2db --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,294 @@ +/* + * rx51.c -- SoC audio for Nokia RX-51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi + * Eduardo Valentin + * Jarkko Nikula + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic3x.h" + +/* + * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This + * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c + */ +#define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7) + +static int rx51_spk_func; +static int rx51_dmic_func; + +static void rx51_ext_control(struct snd_soc_codec *codec) +{ + if (rx51_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + if (rx51_dmic_func) + snd_soc_dapm_enable_pin(codec, "DMic"); + else + snd_soc_dapm_disable_pin(codec, "DMic"); + + snd_soc_dapm_sync(codec); +} + +static int rx51_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + rx51_ext_control(codec); + + return 0; +} + +static int rx51_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 *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set the codec system clock for DAC and ADC */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); +} + +static struct snd_soc_ops rx51_ops = { + .startup = rx51_startup, + .hw_params = rx51_hw_params, +}; + +static int rx51_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_spk_func; + + return 0; +} + +static int rx51_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_spk_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_spk_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 1); + else + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 0); + + return 0; +} + +static int rx51_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_dmic_func; + + return 0; +} + +static int rx51_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_dmic_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_dmic_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), + SND_SOC_DAPM_MIC("DMic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Ext Spk", NULL, "HPLOUT"}, + {"Ext Spk", NULL, "HPROUT"}, + + {"DMic Rate 64", NULL, "Mic Bias 2V"}, + {"Mic Bias 2V", NULL, "DMic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; + +static const struct soc_enum rx51_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), +}; + +static const struct snd_kcontrol_new aic34_rx51_controls[] = { + SOC_ENUM_EXT("Speaker Function", rx51_enum[0], + rx51_get_spk, rx51_set_spk), + SOC_ENUM_EXT("Input Select", rx51_enum[1], + rx51_get_input, rx51_set_input), +}; + +static int rx51_aic34_init(struct snd_soc_codec *codec) +{ + int err; + + /* Set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); + snd_soc_dapm_nc_pin(codec, "MIC3R"); + snd_soc_dapm_nc_pin(codec, "LINE1R"); + + /* Add RX-51 specific controls */ + err = snd_soc_add_controls(codec, aic34_rx51_controls, + ARRAY_SIZE(aic34_rx51_controls)); + if (err < 0) + return err; + + /* Add RX-51 specific widgets */ + snd_soc_dapm_new_controls(codec, aic34_dapm_widgets, + ARRAY_SIZE(aic34_dapm_widgets)); + + /* Set up RX-51 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = { + { + .name = "TLV320AIC34", + .stream_name = "AIC34", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &aic3x_dai, + .init = rx51_aic34_init, + .ops = &rx51_ops, + }, +}; + +/* Audio private data */ +static struct aic3x_setup_data rx51_aic34_setup = { + .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, + .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, +}; + +/* Audio card */ +static struct snd_soc_card rx51_sound_card = { + .name = "RX-51", + .dai_link = rx51_dai, + .num_links = ARRAY_SIZE(rx51_dai), + .platform = &omap_soc_platform, +}; + +/* Audio subsystem */ +static struct snd_soc_device rx51_snd_devdata = { + .card = &rx51_sound_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &rx51_aic34_setup, +}; + +static struct platform_device *rx51_snd_device; + +static int __init rx51_soc_init(void) +{ + int err; + + if (!machine_is_nokia_rx51()) + return -ENODEV; + + rx51_snd_device = platform_device_alloc("soc-audio", -1); + if (!rx51_snd_device) { + err = -ENOMEM; + goto err1; + } + + platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata); + rx51_snd_devdata.dev = &rx51_snd_device->dev; + *(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + + err = platform_device_add(rx51_snd_device); + if (err) + goto err2; + + return 0; +err2: + platform_device_put(rx51_snd_device); +err1: + + return err; +} + +static void __exit rx51_soc_exit(void) +{ + platform_device_unregister(rx51_snd_device); +} + +module_init(rx51_soc_init); +module_exit(rx51_soc_exit); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX-51"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-18-g5258 From 5193d62f1824cdfd72b5523be2b1cdb8049225ad Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 5 May 2010 13:02:03 +0300 Subject: ASoC: tlv320aic3x: Add platform data and reset gpio handling Handle the reset GPIO within the codec driver in order to follow the startup protocol for the tlv320aic3x codecs. Signed-off-by: Jarkko Nikula Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- include/sound/tlv320aic3x.h | 17 +++++++++++++++++ sound/soc/codecs/tlv320aic3x.c | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 include/sound/tlv320aic3x.h diff --git a/include/sound/tlv320aic3x.h b/include/sound/tlv320aic3x.h new file mode 100644 index 00000000000..b1a5f34e5cf --- /dev/null +++ b/include/sound/tlv320aic3x.h @@ -0,0 +1,17 @@ +/* + * Platform data for Texas Instruments TLV320AIC3x codec + * + * Author: Jarkko Nikula + * + * 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 __TLV320AIC3x_H__ +#define __TLV320AIC3x_H__ + +struct aic3x_pdata { + int gpio_reset; /* < 0 if not used */ +}; + +#endif \ No newline at end of file diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 584bc1e67f7..d57372be7a9 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include "tlv320aic3x.h" @@ -64,6 +66,7 @@ struct aic3x_priv { struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; unsigned int sysclk; int master; + int gpio_reset; }; /* @@ -1278,6 +1281,10 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec); + if (aic3x->gpio_reset >= 0) { + gpio_set_value(aic3x->gpio_reset, 0); + gpio_free(aic3x->gpio_reset); + } regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); @@ -1302,6 +1309,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, { struct snd_soc_codec *codec; struct aic3x_priv *aic3x; + struct aic3x_pdata *pdata = i2c->dev.platform_data; int ret, i; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); @@ -1318,6 +1326,15 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, aic3x); + aic3x->gpio_reset = -1; + if (pdata && pdata->gpio_reset >= 0) { + ret = gpio_request(pdata->gpio_reset, "tlv320aic3x reset"); + if (ret != 0) + goto err_gpio; + aic3x->gpio_reset = pdata->gpio_reset; + gpio_direction_output(aic3x->gpio_reset, 0); + } + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) aic3x->supplies[i].supply = aic3x_supply_names[i]; @@ -1335,11 +1352,19 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, goto err_enable; } + if (aic3x->gpio_reset >= 0) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + return aic3x_register(codec); err_enable: regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); err_get: + if (aic3x->gpio_reset >= 0) + gpio_free(aic3x->gpio_reset); +err_gpio: kfree(aic3x); return ret; } -- cgit v1.2.3-18-g5258 From 6f3991152f20933b77eff30413e893bf1a15e578 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 6 May 2010 10:37:18 +0300 Subject: ASoC: tpa6130a2: Support for limiting gain Add support for platform dependent gain limiting on the tpa6130a2 (and tpa6140a2) Headset amplifier. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- include/sound/tpa6130a2-plat.h | 1 + sound/soc/codecs/tpa6130a2.c | 76 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/include/sound/tpa6130a2-plat.h b/include/sound/tpa6130a2-plat.h index e29fde6b5cb..426f62767da 100644 --- a/include/sound/tpa6130a2-plat.h +++ b/include/sound/tpa6130a2-plat.h @@ -31,6 +31,7 @@ enum tpa_model { struct tpa6130a2_platform_data { enum tpa_model id; int power_gpio; + int limit_gain; }; #endif diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 0cf3e3862e7..31f67b527ca 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -46,6 +46,9 @@ static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { "AVdd", }; +#define TPA6130A2_GAIN_MAX 0x3f +#define TPA6140A2_GAIN_MAX 0x1f + /* This struct is used to save the context */ struct tpa6130a2_data { struct mutex mutex; @@ -54,6 +57,7 @@ struct tpa6130a2_data { int power_gpio; unsigned char power_state; enum tpa_model id; + int gain_limit; }; static int tpa6130a2_i2c_read(int reg) @@ -176,6 +180,40 @@ exit: return ret; } +static int tpa6130a2_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + data = i2c_get_clientdata(tpa6130a2_client); + + mutex_lock(&data->mutex); + switch (mc->reg) { + case TPA6130A2_REG_VOL_MUTE: + if (data->gain_limit != mc->max) + mc->max = data->gain_limit; + break; + default: + dev_err(&tpa6130a2_client->dev, + "Invalid register: 0x02%x\n", mc->reg); + goto out; + } + if (unlikely(mc->max == 1)) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mc->max; +out: + mutex_unlock(&data->mutex); + return 0; +} + static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -239,6 +277,15 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, return 1; } +#define SOC_SINGLE_EXT_TLV_TPA(xname, xreg, xshift, xmax, xinvert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = tpa6130a2_info_volsw, \ + .get = tpa6130a2_get_reg, .put = tpa6130a2_set_reg, \ + .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } + /* * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going * down in gain. @@ -258,10 +305,9 @@ static const unsigned int tpa6130_tlv[] = { }; static const struct snd_kcontrol_new tpa6130a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, - tpa6130_tlv), + SOC_SINGLE_EXT_TLV_TPA("TPA6130A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 0, TPA6130A2_GAIN_MAX, 0, + tpa6130_tlv), }; static const unsigned int tpa6140_tlv[] = { @@ -272,10 +318,9 @@ static const unsigned int tpa6140_tlv[] = { }; static const struct snd_kcontrol_new tpa6140a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, - tpa6140_tlv), + SOC_SINGLE_EXT_TLV_TPA("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, TPA6140A2_GAIN_MAX, 0, + tpa6140_tlv), }; /* @@ -454,16 +499,31 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, case TPA6130A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6130A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6130A2_GAIN_MAX; break; case TPA6140A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6140a2_supply_names[i];; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6140A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6140A2_GAIN_MAX; break; default: dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", pdata->id); for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6130A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6130A2_GAIN_MAX; } ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), -- cgit v1.2.3-18-g5258 From 2f005471e2e2f2c7fa5898153387d421f7d24ad6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 6 May 2010 12:04:25 +0300 Subject: ASoC: tlv320dac33: Use codec defaults for LOM/LOP and DAC power Do not change the codec defaults for the following registers: 0x40, 0x41: Line output gains, do not use amplification 0x42: LOM/LOP Voltage hold, and selection 0x44: LOM inversion control It has been found, that the values configured to these registers can cause amplification, which can make the output of DAC33 distorted. The codec reset values are considered safe in all environmnts. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 68b7ccbf2e7..ad5e2636c94 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -303,21 +303,10 @@ static void dac33_init_chip(struct snd_soc_codec *codec) /* C : (defaults) */ dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); - /* 64-65 : L&R DAC power control - Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ - dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - /* 73 : volume soft stepping control, clock source = internal osc (?) */ dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - /* 66 : LOP/LOM Modes */ - dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); - - /* 68 : LOM inverted from LOP */ - dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); /* Restore only selected registers (gains mostly) */ -- cgit v1.2.3-18-g5258 From c4806174c516d26bf4a72db1789cfc96e4950d07 Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Fri, 7 May 2010 10:21:39 +0900 Subject: ARM: S3C2412: DMA: Remove I2S FIFO address The S3C DMA API doesn't make use of hw_addr.to/from and also the FIFO addresses are provided from the I2S drivers. So these fields are redundant. This patch removes the hw_addr.to/from fields for I2S and the inclusion of header, paving way for the header to be moved closer to the I2S controller drivers. Signed-off-by: Jassi Brar Acked-by: Ben Dooks Signed-off-by: Mark Brown --- arch/arm/mach-s3c2412/dma.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm/mach-s3c2412/dma.c b/arch/arm/mach-s3c2412/dma.c index e880524904e..7abecfca0b7 100644 --- a/arch/arm/mach-s3c2412/dma.c +++ b/arch/arm/mach-s3c2412/dma.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -119,13 +118,11 @@ static struct s3c24xx_dma_map __initdata s3c2412_dma_mappings[] = { .name = "i2s-sdi", .channels = MAP(S3C2412_DMAREQSEL_I2SRX), .channels_rx = MAP(S3C2412_DMAREQSEL_I2SRX), - .hw_addr.from = S3C2410_PA_IIS + S3C2412_IISRXD, }, [DMACH_I2S_OUT] = { .name = "i2s-sdo", .channels = MAP(S3C2412_DMAREQSEL_I2STX), .channels_rx = MAP(S3C2412_DMAREQSEL_I2STX), - .hw_addr.to = S3C2410_PA_IIS + S3C2412_IISTXD, }, [DMACH_USB_EP1] = { .name = "usb-ep1", -- cgit v1.2.3-18-g5258 From 637d3847ba0ca2c7780e6521cbe75568d19ff0db Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 7 May 2010 14:05:49 +0300 Subject: ASoC: core: Support for limiting the volume Add support for the core to limit the maximum volume on an existing control. The function will modify the soc_mixer_control.max value of the given control. The new value must be lower than the original one (chip maximum) If there is a need for limiting a gain on a given control, than machine drivers can do the following in their snd_soc_dai_link.init function: snd_soc_limit_volume(codec, "TPA6140A2 Headphone Playback Volume", 21); This will modify the original 31 (chip maximum) to 21, so user space will not be able to set the gain higher than this. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 2 ++ sound/soc/soc-core.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 01e9c66ddc6..9f306f0710d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -326,6 +326,8 @@ int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int snd_soc_limit_volume(struct snd_soc_codec *codec, + const char *name, int max); /** * struct snd_soc_jack_pin - Describes a pin to update based on jack detection diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d59076e2ca8..4079223203e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2237,6 +2237,45 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); +/** + * snd_soc_limit_volume - Set new limit to an existing volume control. + * + * @codec: where to look for the control + * @name: Name of the control + * @max: new maximum limit + * + * Return 0 for success, else error. + */ +int snd_soc_limit_volume(struct snd_soc_codec *codec, + const char *name, int max) +{ + struct snd_card *card = codec->card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + int found = 0; + int ret = -EINVAL; + + /* Sanity check for name and max */ + if (unlikely(!name || max <= 0)) + return -EINVAL; + + list_for_each_entry(kctl, &card->controls, list) { + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) { + found = 1; + break; + } + } + if (found) { + mc = (struct soc_mixer_control *)kctl->private_value; + if (max <= mc->max) { + mc->max = max; + ret = 0; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_limit_volume); + /** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI -- cgit v1.2.3-18-g5258 From 826e962c4689172e25acde72eafebc063253b69d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 7 May 2010 14:24:10 +0300 Subject: Revert "ASoC: tpa6130a2: Support for limiting gain" This reverts commit 6f3991152f20933b77eff30413e893bf1a15e578. Since core has now support for limiting the volume on controls this patch is not needed. Furthermore, this patch actually prevents the core to set new volume on the TPA. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/tpa6130a2-plat.h | 1 - sound/soc/codecs/tpa6130a2.c | 76 +++++------------------------------------- 2 files changed, 8 insertions(+), 69 deletions(-) diff --git a/include/sound/tpa6130a2-plat.h b/include/sound/tpa6130a2-plat.h index 426f62767da..e29fde6b5cb 100644 --- a/include/sound/tpa6130a2-plat.h +++ b/include/sound/tpa6130a2-plat.h @@ -31,7 +31,6 @@ enum tpa_model { struct tpa6130a2_platform_data { enum tpa_model id; int power_gpio; - int limit_gain; }; #endif diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 31f67b527ca..0cf3e3862e7 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -46,9 +46,6 @@ static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { "AVdd", }; -#define TPA6130A2_GAIN_MAX 0x3f -#define TPA6140A2_GAIN_MAX 0x1f - /* This struct is used to save the context */ struct tpa6130a2_data { struct mutex mutex; @@ -57,7 +54,6 @@ struct tpa6130a2_data { int power_gpio; unsigned char power_state; enum tpa_model id; - int gain_limit; }; static int tpa6130a2_i2c_read(int reg) @@ -180,40 +176,6 @@ exit: return ret; } -static int tpa6130a2_info_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct tpa6130a2_data *data; - - BUG_ON(tpa6130a2_client == NULL); - data = i2c_get_clientdata(tpa6130a2_client); - - mutex_lock(&data->mutex); - switch (mc->reg) { - case TPA6130A2_REG_VOL_MUTE: - if (data->gain_limit != mc->max) - mc->max = data->gain_limit; - break; - default: - dev_err(&tpa6130a2_client->dev, - "Invalid register: 0x02%x\n", mc->reg); - goto out; - } - if (unlikely(mc->max == 1)) - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - else - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = mc->max; -out: - mutex_unlock(&data->mutex); - return 0; -} - static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -277,15 +239,6 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, return 1; } -#define SOC_SINGLE_EXT_TLV_TPA(xname, xreg, xshift, xmax, xinvert, tlv_array) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ - SNDRV_CTL_ELEM_ACCESS_READWRITE,\ - .tlv.p = (tlv_array), \ - .info = tpa6130a2_info_volsw, \ - .get = tpa6130a2_get_reg, .put = tpa6130a2_set_reg, \ - .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } - /* * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going * down in gain. @@ -305,9 +258,10 @@ static const unsigned int tpa6130_tlv[] = { }; static const struct snd_kcontrol_new tpa6130a2_controls[] = { - SOC_SINGLE_EXT_TLV_TPA("TPA6130A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 0, TPA6130A2_GAIN_MAX, 0, - tpa6130_tlv), + SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, + tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6130_tlv), }; static const unsigned int tpa6140_tlv[] = { @@ -318,9 +272,10 @@ static const unsigned int tpa6140_tlv[] = { }; static const struct snd_kcontrol_new tpa6140a2_controls[] = { - SOC_SINGLE_EXT_TLV_TPA("TPA6140A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 1, TPA6140A2_GAIN_MAX, 0, - tpa6140_tlv), + SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, + tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6140_tlv), }; /* @@ -499,31 +454,16 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, case TPA6130A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; - if (pdata->limit_gain > 0 && - pdata->limit_gain < TPA6130A2_GAIN_MAX) - data->gain_limit = pdata->limit_gain; - else - data->gain_limit = TPA6130A2_GAIN_MAX; break; case TPA6140A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6140a2_supply_names[i];; - if (pdata->limit_gain > 0 && - pdata->limit_gain < TPA6140A2_GAIN_MAX) - data->gain_limit = pdata->limit_gain; - else - data->gain_limit = TPA6140A2_GAIN_MAX; break; default: dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", pdata->id); for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; - if (pdata->limit_gain > 0 && - pdata->limit_gain < TPA6130A2_GAIN_MAX) - data->gain_limit = pdata->limit_gain; - else - data->gain_limit = TPA6130A2_GAIN_MAX; } ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), -- cgit v1.2.3-18-g5258 From bd843edf81dc1200f71b61d4804af0e2918e8ae3 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 7 May 2010 14:24:11 +0300 Subject: ASoC: tpa6130a2: Fix for the custom kcontrol functions Since the functions arre only used for volume register, change their name, and also fix them to properly handle the cases, when via soc core the volume is limited. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/tpa6130a2.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 0cf3e3862e7..fa4fa33a51a 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -176,7 +176,7 @@ exit: return ret; } -static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, +static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = @@ -184,7 +184,8 @@ static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, struct tpa6130a2_data *data; unsigned int reg = mc->reg; unsigned int shift = mc->shift; - unsigned int mask = mc->max; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; BUG_ON(tpa6130a2_client == NULL); @@ -197,13 +198,13 @@ static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, if (invert) ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; mutex_unlock(&data->mutex); return 0; } -static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, +static int tpa6130a2_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = @@ -211,7 +212,8 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, struct tpa6130a2_data *data; unsigned int reg = mc->reg; unsigned int shift = mc->shift; - unsigned int mask = mc->max; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; unsigned int val = (ucontrol->value.integer.value[0] & mask); unsigned int val_reg; @@ -220,7 +222,7 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, data = i2c_get_clientdata(tpa6130a2_client); if (invert) - val = mask - val; + val = max - val; mutex_lock(&data->mutex); @@ -260,7 +262,7 @@ static const unsigned int tpa6130_tlv[] = { static const struct snd_kcontrol_new tpa6130a2_controls[] = { SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6130_tlv), }; @@ -274,7 +276,7 @@ static const unsigned int tpa6140_tlv[] = { static const struct snd_kcontrol_new tpa6140a2_controls[] = { SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6140_tlv), }; -- cgit v1.2.3-18-g5258 From af56b1c27b909e06318d26416d7b21fe6483224a Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:57:11 +0900 Subject: ASoC: S3C64XX: IISv4: Add CPU driver Add the CPU driver for the IISv4 block found on S3C6410. For now, the driver is almost a copy of s3c64xx-i2s.c but it should diverge as more IISv4 specific stuff is added. Signed-off-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/Kconfig | 5 + sound/soc/s3c24xx/Makefile | 2 + sound/soc/s3c24xx/s3c64xx-i2s-v4.c | 209 +++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c64xx-i2s.h | 1 + 4 files changed, 217 insertions(+) create mode 100644 sound/soc/s3c24xx/s3c64xx-i2s-v4.c diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 15fe57e5a23..c0c7edf792f 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -24,6 +24,11 @@ config SND_S3C64XX_SOC_I2S select SND_S3C_I2SV2_SOC select S3C64XX_DMA +config SND_S3C64XX_SOC_I2S_V4 + tristate + select SND_S3C_I2SV2_SOC + select S3C64XX_DMA + config SND_S3C_SOC_PCM tristate diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index df071a376fa..81d8dc503f8 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,6 +4,7 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c-ac97-objs := s3c-ac97.o +snd-soc-s3c64xx-i2s-v4-objs := s3c64xx-i2s-v4.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o @@ -12,6 +13,7 @@ obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o +obj-$(CONFIG_SND_S3C64XX_SOC_I2S_V4) += snd-soc-s3c64xx-i2s-v4.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o obj-$(CONFIG_SND_S3C_SOC_PCM) += snd-soc-s3c-pcm.o diff --git a/sound/soc/s3c24xx/s3c64xx-i2s-v4.c b/sound/soc/s3c24xx/s3c64xx-i2s-v4.c new file mode 100644 index 00000000000..06db130030a --- /dev/null +++ b/sound/soc/s3c24xx/s3c64xx-i2s-v4.c @@ -0,0 +1,209 @@ +/* sound/soc/s3c24xx/s3c64xx-i2s-v4.c + * + * ALSA SoC Audio Layer - S3C64XX I2Sv4 driver + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "s3c-dma.h" +#include "regs-i2s-v2.h" +#include "s3c64xx-i2s.h" + +static struct s3c2410_dma_client s3c64xx_dma_client_out = { + .name = "I2Sv4 PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c64xx_dma_client_in = { + .name = "I2Sv4 PCM Stereo in" +}; + +static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_out; +static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_in; +static struct s3c_i2sv2_info s3c64xx_i2sv4; + +struct snd_soc_dai s3c64xx_i2s_v4_dai; +EXPORT_SYMBOL_GPL(s3c64xx_i2s_v4_dai); + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + +static int s3c64xx_i2sv4_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + /* configure GPIO for i2s port */ + s3c_gpio_cfgpin(S3C64XX_GPC(4), S3C64XX_GPC4_I2S_V40_DO0); + s3c_gpio_cfgpin(S3C64XX_GPC(5), S3C64XX_GPC5_I2S_V40_DO1); + s3c_gpio_cfgpin(S3C64XX_GPC(7), S3C64XX_GPC7_I2S_V40_DO2); + s3c_gpio_cfgpin(S3C64XX_GPH(6), S3C64XX_GPH6_I2S_V40_BCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(7), S3C64XX_GPH7_I2S_V40_CDCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(8), S3C64XX_GPH8_I2S_V40_LRCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(9), S3C64XX_GPH9_I2S_V40_DI); + + return 0; +} + +static int s3c_i2sv4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + struct s3c_dma_params *dma_data; + u32 iismod; + + dev_dbg(cpu_dai->dev, "Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + dev_dbg(cpu_dai->dev, "%s: r: IISMOD: %x\n", __func__, iismod); + + iismod &= ~S3C64XX_IISMOD_BLC_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C64XX_IISMOD_BLC_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S24_LE: + iismod |= S3C64XX_IISMOD_BLC_24BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + dev_dbg(cpu_dai->dev, "%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static struct snd_soc_dai_ops s3c64xx_i2sv4_dai_ops = { + .hw_params = s3c_i2sv4_hw_params, +}; + +static __devinit int s3c64xx_i2sv4_dev_probe(struct platform_device *pdev) +{ + struct s3c_i2sv2_info *i2s; + struct snd_soc_dai *dai; + int ret; + + i2s = &s3c64xx_i2sv4; + dai = &s3c64xx_i2s_v4_dai; + + if (dai->dev) { + dev_dbg(dai->dev, "%s: \ + I2Sv4 instance already registered!\n", __func__); + return -EBUSY; + } + + dai->dev = &pdev->dev; + dai->name = "s3c64xx-i2s-v4"; + dai->id = 0; + dai->symmetric_rates = 1; + dai->playback.channels_min = 2; + dai->playback.channels_max = 2; + dai->playback.rates = S3C64XX_I2S_RATES; + dai->playback.formats = S3C64XX_I2S_FMTS; + dai->capture.channels_min = 2; + dai->capture.channels_max = 2; + dai->capture.rates = S3C64XX_I2S_RATES; + dai->capture.formats = S3C64XX_I2S_FMTS; + dai->probe = s3c64xx_i2sv4_probe; + dai->ops = &s3c64xx_i2sv4_dai_ops; + + i2s->feature |= S3C_FEATURE_CDCLKCON; + + i2s->dma_capture = &s3c64xx_i2sv4_pcm_stereo_in; + i2s->dma_playback = &s3c64xx_i2sv4_pcm_stereo_out; + + i2s->dma_capture->channel = DMACH_HSI_I2SV40_RX; + i2s->dma_capture->dma_addr = S3C64XX_PA_IISV4 + S3C2412_IISRXD; + i2s->dma_playback->channel = DMACH_HSI_I2SV40_TX; + i2s->dma_playback->dma_addr = S3C64XX_PA_IISV4 + S3C2412_IISTXD; + + i2s->dma_capture->client = &s3c64xx_dma_client_in; + i2s->dma_capture->dma_size = 4; + i2s->dma_playback->client = &s3c64xx_dma_client_out; + i2s->dma_playback->dma_size = 4; + + i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus\n"); + ret = PTR_ERR(i2s->iis_cclk); + goto err; + } + + clk_enable(i2s->iis_cclk); + + ret = s3c_i2sv2_probe(pdev, dai, i2s, 0); + if (ret) + goto err_clk; + + ret = s3c_i2sv2_register_dai(dai); + if (ret != 0) + goto err_i2sv2; + + return 0; + +err_i2sv2: + /* Not implemented for I2Sv2 core yet */ +err_clk: + clk_put(i2s->iis_cclk); +err: + return ret; +} + +static __devexit int s3c64xx_i2sv4_dev_remove(struct platform_device *pdev) +{ + dev_err(&pdev->dev, "Device removal not yet supported\n"); + return 0; +} + +static struct platform_driver s3c64xx_i2sv4_driver = { + .probe = s3c64xx_i2sv4_dev_probe, + .remove = s3c64xx_i2sv4_dev_remove, + .driver = { + .name = "s3c64xx-iis-v4", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c64xx_i2sv4_init(void) +{ + return platform_driver_register(&s3c64xx_i2sv4_driver); +} +module_init(s3c64xx_i2sv4_init); + +static void __exit s3c64xx_i2sv4_exit(void) +{ + platform_driver_unregister(&s3c64xx_i2sv4_driver); +} +module_exit(s3c64xx_i2sv4_exit); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, "); +MODULE_DESCRIPTION("S3C64XX I2Sv4 SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index f27ed50e4d8..7a40f43d1d5 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -37,5 +37,6 @@ struct clk; SNDRV_PCM_FMTBIT_S24_LE) extern struct snd_soc_dai s3c64xx_i2s_dai[]; +extern struct snd_soc_dai s3c64xx_i2s_v4_dai; #endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ -- cgit v1.2.3-18-g5258 From d0bbc24d2ab78d9efc61bc80cfc85f59626ba5af Mon Sep 17 00:00:00 2001 From: Jassi Brar Date: Tue, 27 Apr 2010 15:57:17 +0900 Subject: ASoC: SMDK64XX: Switch to IISv4 CPU driver Switch the MACHINE driver to use IISv4 CPU dai. Remove BROKEN dependency now that we have proper CPU driver available. Also, disable build for SMDK6400, since the S3C6400 doesn't have IISv4 controller. Signed-off-by: Jassi Brar Signed-off-by: Mark Brown --- sound/soc/s3c24xx/Kconfig | 7 +++---- sound/soc/s3c24xx/smdk64xx_wm8580.c | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index c0c7edf792f..2a7cc222d09 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -64,12 +64,11 @@ config SND_S3C24XX_SOC_JIVE_WM8750 config SND_S3C64XX_SOC_WM8580 tristate "SoC I2S Audio support for WM8580 on SMDK64XX" - depends on SND_S3C24XX_SOC && (MACH_SMDK6400 || MACH_SMDK6410) - depends on BROKEN + depends on SND_S3C24XX_SOC && MACH_SMDK6410 select SND_SOC_WM8580 - select SND_S3C64XX_SOC_I2S + select SND_S3C64XX_SOC_I2S_V4 help - Sat Y if you want to add support for SoC audio on the SMDK64XX. + Say Y if you want to add support for SoC audio on the SMDK6410. config SND_S3C24XX_SOC_SMDK2443_WM9710 tristate "SoC AC97 Audio support for SMDK2443 - WM9710" diff --git a/sound/soc/s3c24xx/smdk64xx_wm8580.c b/sound/soc/s3c24xx/smdk64xx_wm8580.c index efe4901213a..07e8e51d10d 100644 --- a/sound/soc/s3c24xx/smdk64xx_wm8580.c +++ b/sound/soc/s3c24xx/smdk64xx_wm8580.c @@ -22,8 +22,6 @@ #include "s3c-dma.h" #include "s3c64xx-i2s.h" -#define S3C64XX_I2S_V4 2 - /* SMDK64XX has a 12MHZ crystal attached to WM8580 */ #define SMDK64XX_WM8580_FREQ 12000000 @@ -215,7 +213,7 @@ static struct snd_soc_dai_link smdk64xx_dai[] = { { /* Primary Playback i/f */ .name = "WM8580 PAIF RX", .stream_name = "Playback", - .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .cpu_dai = &s3c64xx_i2s_v4_dai, .codec_dai = &wm8580_dai[WM8580_DAI_PAIFRX], .init = smdk64xx_wm8580_init_paifrx, .ops = &smdk64xx_ops, @@ -223,7 +221,7 @@ static struct snd_soc_dai_link smdk64xx_dai[] = { { /* Primary Capture i/f */ .name = "WM8580 PAIF TX", .stream_name = "Capture", - .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .cpu_dai = &s3c64xx_i2s_v4_dai, .codec_dai = &wm8580_dai[WM8580_DAI_PAIFTX], .init = smdk64xx_wm8580_init_paiftx, .ops = &smdk64xx_ops, -- cgit v1.2.3-18-g5258 From 5e688883563ef62bd3725922f1a795a8253df499 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Sat, 8 May 2010 11:24:56 +0200 Subject: ALSA: sound/usb: fix UAC1 regression Commit 23caaf19b ("ALSA: usb-mixer: Add support for Audio Class v2.0") broke support for Class1 devices due to two faulty changes. This patch fixes it. Signed-off-by: Daniel Mack Reported-and-Tested-by: The Source Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio.h | 2 +- sound/usb/mixer.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index 905a87caf3f..57f20551939 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -244,7 +244,7 @@ struct uac_selector_unit_descriptor { static inline __u8 uac_selector_unit_iSelector(struct uac_selector_unit_descriptor *desc) { __u8 *raw = (__u8 *) desc; - return raw[desc->bLength - 1]; + return raw[9 + desc->bLength - 1]; } /* 4.3.2.5 Feature Unit Descriptor */ diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 1deef623c08..e350f053440 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -656,7 +656,7 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_ case UAC_FEATURE_UNIT: { /* the header is the same for v1 and v2 */ struct uac_feature_unit_descriptor *d = p1; - id = d->bUnitID; + id = d->bSourceID; break; /* continue to parse */ } case UAC_MIXER_UNIT: { -- cgit v1.2.3-18-g5258 From 6895b5262ea80c99e526d40d39e0ca48014bdb95 Mon Sep 17 00:00:00 2001 From: Ville Syrjälä Date: Fri, 7 May 2010 00:12:12 +0300 Subject: ALSA: es1968: Clear interrupts before enabling them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid spurious interrupts when initializing the device. Signed-off-by: Ville Syrjälä Signed-off-by: Takashi Iwai --- sound/pci/es1968.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index aa973cee815..23a58f0d6cb 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -2401,6 +2401,7 @@ static void snd_es1968_start_irq(struct es1968 *chip) w = ESM_HIRQ_DSIE | ESM_HIRQ_HW_VOLUME; if (chip->rmidi) w |= ESM_HIRQ_MPU401; + outb(w, chip->io_port + 0x1A); outw(w, chip->io_port + ESM_PORT_HOST_IRQ); } -- cgit v1.2.3-18-g5258 From 1bde78bc25a8e244006f63adb851bfb34407d48c Mon Sep 17 00:00:00 2001 From: Ville Syrjälä Date: Fri, 7 May 2010 00:12:13 +0300 Subject: ALSA: maestro3: Clear interrupts before enabling them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid spurious interrupts when initializing the device. Signed-off-by: Ville Syrjälä Signed-off-by: Takashi Iwai --- sound/pci/maestro3.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index 217a4dcb259..3c40d726b46 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -2443,6 +2443,7 @@ snd_m3_enable_ints(struct snd_m3 *chip) val = ASSP_INT_ENABLE /*| MPU401_INT_ENABLE*/; if (chip->hv_config & HV_CTRL_ENABLE) val |= HV_INT_ENABLE; + outb(val, chip->iobase + HOST_INT_STATUS); outw(val, io + HOST_INT_CTRL); outb(inb(io + ASSP_CONTROL_C) | ASSP_HOST_INT_ENABLE, io + ASSP_CONTROL_C); -- cgit v1.2.3-18-g5258 From 396fa8272601c3d488cb8391c3962a7ee552afd0 Mon Sep 17 00:00:00 2001 From: Krzysztof Helt Date: Sun, 9 May 2010 20:35:44 +0200 Subject: ALSA: es1688: allocate snd_es1688 structure as a part of snd_card structure Allocate the snd_es1688 during the snd_card allocation. This allows to remove the card pointer from the snd_es1688 structure. Signed-off-by: Krzysztof Helt Signed-off-by: Takashi Iwai --- include/sound/es1688.h | 10 +++++----- sound/isa/es1688/es1688.c | 23 ++++++++++++---------- sound/isa/es1688/es1688_lib.c | 44 ++++++++++++++----------------------------- sound/isa/gus/gusextreme.c | 26 +++++++++++++------------ 4 files changed, 46 insertions(+), 57 deletions(-) diff --git a/include/sound/es1688.h b/include/sound/es1688.h index 10fcf146581..4c29572effb 100644 --- a/include/sound/es1688.h +++ b/include/sound/es1688.h @@ -44,7 +44,6 @@ struct snd_es1688 { unsigned char pad; unsigned int dma_size; - struct snd_card *card; struct snd_pcm *pcm; struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *capture_substream; @@ -108,14 +107,15 @@ struct snd_es1688 { void snd_es1688_mixer_write(struct snd_es1688 *chip, unsigned char reg, unsigned char data); int snd_es1688_create(struct snd_card *card, + struct snd_es1688 *chip, unsigned long port, unsigned long mpu_port, int irq, int mpu_irq, int dma8, - unsigned short hardware, - struct snd_es1688 ** rchip); -int snd_es1688_pcm(struct snd_es1688 *chip, int device, struct snd_pcm ** rpcm); -int snd_es1688_mixer(struct snd_es1688 *chip); + unsigned short hardware); +int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device, + struct snd_pcm **rpcm); +int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip); #endif /* __SOUND_ES1688_H */ diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index 07df201ed8f..281679493fb 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -79,8 +79,8 @@ static int __devinit snd_es1688_match(struct device *dev, unsigned int n) return enable[n]; } -static int __devinit snd_es1688_legacy_create(struct snd_card *card, - struct device *dev, unsigned int n, struct snd_es1688 **rchip) +static int __devinit snd_es1688_legacy_create(struct snd_card *card, + struct snd_es1688 *chip, struct device *dev, unsigned int n) { static long possible_ports[] = {0x220, 0x240, 0x260}; static int possible_irqs[] = {5, 9, 10, 7, -1}; @@ -104,14 +104,14 @@ static int __devinit snd_es1688_legacy_create(struct snd_card *card, } if (port[n] != SNDRV_AUTO_PORT) - return snd_es1688_create(card, port[n], mpu_port[n], irq[n], - mpu_irq[n], dma8[n], ES1688_HW_AUTO, rchip); + return snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO); i = 0; do { port[n] = possible_ports[i]; - error = snd_es1688_create(card, port[n], mpu_port[n], irq[n], - mpu_irq[n], dma8[n], ES1688_HW_AUTO, rchip); + error = snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO); } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); return error; @@ -125,19 +125,22 @@ static int __devinit snd_es1688_probe(struct device *dev, unsigned int n) struct snd_pcm *pcm; int error; - error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card); + error = snd_card_create(index[n], id[n], THIS_MODULE, + sizeof(struct snd_es1688), &card); if (error < 0) return error; - error = snd_es1688_legacy_create(card, dev, n, &chip); + chip = card->private_data; + + error = snd_es1688_legacy_create(card, chip, dev, n); if (error < 0) goto out; - error = snd_es1688_pcm(chip, 0, &pcm); + error = snd_es1688_pcm(card, chip, 0, &pcm); if (error < 0) goto out; - error = snd_es1688_mixer(chip); + error = snd_es1688_mixer(card, chip); if (error < 0) goto out; diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c index c76bb00c9d1..fdd440417bf 100644 --- a/sound/isa/es1688/es1688_lib.c +++ b/sound/isa/es1688/es1688_lib.c @@ -620,7 +620,6 @@ static int snd_es1688_free(struct snd_es1688 *chip) disable_dma(chip->dma8); free_dma(chip->dma8); } - kfree(chip); return 0; } @@ -638,23 +637,20 @@ static const char *snd_es1688_chip_id(struct snd_es1688 *chip) } int snd_es1688_create(struct snd_card *card, + struct snd_es1688 *chip, unsigned long port, unsigned long mpu_port, int irq, int mpu_irq, int dma8, - unsigned short hardware, - struct snd_es1688 **rchip) + unsigned short hardware) { static struct snd_device_ops ops = { .dev_free = snd_es1688_dev_free, }; - struct snd_es1688 *chip; int err; - *rchip = NULL; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); if (chip == NULL) return -ENOMEM; chip->irq = -1; @@ -662,25 +658,21 @@ int snd_es1688_create(struct snd_card *card, if ((chip->res_port = request_region(port + 4, 12, "ES1688")) == NULL) { snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4); - snd_es1688_free(chip); return -EBUSY; } if (request_irq(irq, snd_es1688_interrupt, IRQF_DISABLED, "ES1688", (void *) chip)) { snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq); - snd_es1688_free(chip); return -EBUSY; } chip->irq = irq; if (request_dma(dma8, "ES1688")) { snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8); - snd_es1688_free(chip); return -EBUSY; } chip->dma8 = dma8; spin_lock_init(&chip->reg_lock); spin_lock_init(&chip->mixer_lock); - chip->card = card; chip->port = port; mpu_port &= ~0x000f; if (mpu_port < 0x300 || mpu_port > 0x330) @@ -689,23 +681,16 @@ int snd_es1688_create(struct snd_card *card, chip->mpu_irq = mpu_irq; chip->hardware = hardware; - if ((err = snd_es1688_probe(chip)) < 0) { - snd_es1688_free(chip); + err = snd_es1688_probe(chip); + if (err < 0) return err; - } - if ((err = snd_es1688_init(chip, 1)) < 0) { - snd_es1688_free(chip); - return err; - } - /* Register device */ - if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { - snd_es1688_free(chip); + err = snd_es1688_init(chip, 1); + if (err < 0) return err; - } - *rchip = chip; - return 0; + /* Register device */ + return snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); } static struct snd_pcm_ops snd_es1688_playback_ops = { @@ -730,12 +715,14 @@ static struct snd_pcm_ops snd_es1688_capture_ops = { .pointer = snd_es1688_capture_pointer, }; -int snd_es1688_pcm(struct snd_es1688 * chip, int device, struct snd_pcm ** rpcm) +int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, + int device, struct snd_pcm **rpcm) { struct snd_pcm *pcm; int err; - if ((err = snd_pcm_new(chip->card, "ESx688", device, 1, 1, &pcm)) < 0) + err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm); + if (err < 0) return err; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops); @@ -1009,18 +996,15 @@ static unsigned char snd_es1688_init_table[][2] = { { ES1688_REC_DEV, 0x17 } }; -int snd_es1688_mixer(struct snd_es1688 *chip) +int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip) { - struct snd_card *card; unsigned int idx; int err; unsigned char reg, val; - if (snd_BUG_ON(!chip || !chip->card)) + if (snd_BUG_ON(!chip || !card)) return -EINVAL; - card = chip->card; - strcpy(card->mixername, snd_es1688_chip_id(chip)); for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) { diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c index 65e4b18581a..008e8e5bfa3 100644 --- a/sound/isa/gus/gusextreme.c +++ b/sound/isa/gus/gusextreme.c @@ -95,7 +95,7 @@ static int __devinit snd_gusextreme_match(struct device *dev, unsigned int n) } static int __devinit snd_gusextreme_es1688_create(struct snd_card *card, - struct device *dev, unsigned int n, struct snd_es1688 **rchip) + struct snd_es1688 *chip, struct device *dev, unsigned int n) { static long possible_ports[] = {0x220, 0x240, 0x260}; static int possible_irqs[] = {5, 9, 10, 7, -1}; @@ -119,14 +119,14 @@ static int __devinit snd_gusextreme_es1688_create(struct snd_card *card, } if (port[n] != SNDRV_AUTO_PORT) - return snd_es1688_create(card, port[n], mpu_port[n], irq[n], - mpu_irq[n], dma8[n], ES1688_HW_1688, rchip); + return snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); i = 0; do { port[n] = possible_ports[i]; - error = snd_es1688_create(card, port[n], mpu_port[n], irq[n], - mpu_irq[n], dma8[n], ES1688_HW_1688, rchip); + error = snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); return error; @@ -206,9 +206,8 @@ static int __devinit snd_gusextreme_detect(struct snd_gus_card *gus, return 0; } -static int __devinit snd_gusextreme_mixer(struct snd_es1688 *chip) +static int __devinit snd_gusextreme_mixer(struct snd_card *card) { - struct snd_card *card = chip->card; struct snd_ctl_elem_id id1, id2; int error; @@ -241,17 +240,20 @@ static int __devinit snd_gusextreme_probe(struct device *dev, unsigned int n) struct snd_opl3 *opl3; int error; - error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card); + error = snd_card_create(index[n], id[n], THIS_MODULE, + sizeof(struct snd_es1688), &card); if (error < 0) return error; + es1688 = card->private_data; + if (mpu_port[n] == SNDRV_AUTO_PORT) mpu_port[n] = 0; if (mpu_irq[n] > 15) mpu_irq[n] = -1; - error = snd_gusextreme_es1688_create(card, dev, n, &es1688); + error = snd_gusextreme_es1688_create(card, es1688, dev, n); if (error < 0) goto out; @@ -280,11 +282,11 @@ static int __devinit snd_gusextreme_probe(struct device *dev, unsigned int n) } gus->codec_flag = 1; - error = snd_es1688_pcm(es1688, 0, NULL); + error = snd_es1688_pcm(card, es1688, 0, NULL); if (error < 0) goto out; - error = snd_es1688_mixer(es1688); + error = snd_es1688_mixer(card, es1688); if (error < 0) goto out; @@ -300,7 +302,7 @@ static int __devinit snd_gusextreme_probe(struct device *dev, unsigned int n) if (error < 0) goto out; - error = snd_gusextreme_mixer(es1688); + error = snd_gusextreme_mixer(card); if (error < 0) goto out; -- cgit v1.2.3-18-g5258 From a20971b201ac1fcd236400942c98b0106c42c70a Mon Sep 17 00:00:00 2001 From: Krzysztof Helt Date: Mon, 10 May 2010 09:47:32 +0200 Subject: ALSA: Merge es1688 and es968 drivers The ESS ES968 chip is nothing more then a PnP companion for a non-PnP audio chip. It was paired with non-PnP ESS' chips: ES688 and ES1688. The ESS' audio chips are handled by the es1688 driver in native mode. The PnP cards are handled by the ES968 driver in SB compatible mode. Move the ES968 chip handling to the es1688 driver so the driver can handle both PnP and non-PnP cards. The es968 is removed. Also, a new PnP id is added for the card I acquired (the change was tested on this card). Signed-off-by: Krzysztof Helt Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 21 +- include/sound/es1688.h | 1 + sound/isa/Kconfig | 16 +- sound/isa/es1688/es1688.c | 205 +++++++++++++++++--- sound/isa/es1688/es1688_lib.c | 3 +- sound/isa/sb/Makefile | 2 - sound/isa/sb/es968.c | 248 ------------------------ 7 files changed, 190 insertions(+), 306 deletions(-) delete mode 100644 sound/isa/sb/es968.c diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index dc681badca6..2075bbb8b3e 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -632,28 +632,23 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. - Module snd-es968 - ---------------- - - Module for sound cards based on ESS ES968 chip (PnP only). - - This module supports multiple cards, PnP and autoprobe. - - The power-management is supported. - Module snd-es1688 ----------------- Module for ESS AudioDrive ES-1688 and ES-688 sound cards. - port - port # for ES-1688 chip (0x220,0x240,0x260) - fm_port - port # for OPL3 (option; share the same port as default) + isapnp - ISA PnP detection - 0 = disable, 1 = enable (default) mpu_port - port # for MPU-401 port (0x300,0x310,0x320,0x330), -1 = disable (default) - irq - IRQ # for ES-1688 chip (5,7,9,10) mpu_irq - IRQ # for MPU-401 port (5,7,9,10) + fm_port - port # for OPL3 (option; share the same port as default) + + with isapnp=0, the following additional options are available: + port - port # for ES-1688 chip (0x220,0x240,0x260) + irq - IRQ # for ES-1688 chip (5,7,9,10) dma8 - DMA # for ES-1688 chip (0,1,3) - This module supports multiple cards and autoprobe (without MPU-401 port). + This module supports multiple cards and autoprobe (without MPU-401 port) + and PnP with the ES968 chip. Module snd-es18xx ----------------- diff --git a/include/sound/es1688.h b/include/sound/es1688.h index 4c29572effb..3ec7ecbe250 100644 --- a/include/sound/es1688.h +++ b/include/sound/es1688.h @@ -117,5 +117,6 @@ int snd_es1688_create(struct snd_card *card, int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device, struct snd_pcm **rpcm); int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip); +int snd_es1688_reset(struct snd_es1688 *chip); #endif /* __SOUND_ES1688_H */ diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 755a0a5f0e3..c6990c68079 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -128,26 +128,14 @@ config SND_CS4236 To compile this driver as a module, choose M here: the module will be called snd-cs4236. -config SND_ES968 - tristate "Generic ESS ES968 driver" - depends on PNP - select ISAPNP - select SND_MPU401_UART - select SND_SB8_DSP - help - Say Y here to include support for ESS AudioDrive ES968 chips. - - To compile this driver as a module, choose M here: the module - will be called snd-es968. - config SND_ES1688 - tristate "Generic ESS ES688/ES1688 driver" + tristate "Generic ESS ES688/ES1688 and ES968 PnP driver" select SND_OPL3_LIB select SND_MPU401_UART select SND_PCM help Say Y here to include support for ESS AudioDrive ES688 or - ES1688 chips. + ES1688 chips. Also, this module support cards with ES968 PnP chip. To compile this driver as a module, choose M here: the module will be called snd-es1688. diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index 281679493fb..fdcce311f80 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -45,8 +46,13 @@ MODULE_SUPPORTED_DEVICE("{{ESS,ES688 PnP AudioDrive,pnp:ESS0100}," "{ESS,ES688 AudioDrive,pnp:ESS6881}," "{ESS,ES1688 AudioDrive,pnp:ESS1681}}"); +MODULE_ALIAS("snd_es968"); + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +#ifdef CONFIG_PNP +static int isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; +#endif static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* Usually 0x388 */ @@ -60,6 +66,10 @@ MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); module_param_array(enable, bool, NULL, 0444); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); module_param_array(port, long, NULL, 0444); MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); @@ -74,14 +84,21 @@ MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); module_param_array(dma8, int, NULL, 0444); MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver."); +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + static int __devinit snd_es1688_match(struct device *dev, unsigned int n) { - return enable[n]; + return enable[n] && !is_isapnp_selected(n); } static int __devinit snd_es1688_legacy_create(struct snd_card *card, - struct snd_es1688 *chip, struct device *dev, unsigned int n) + struct device *dev, unsigned int n) { + struct snd_es1688 *chip = card->private_data; static long possible_ports[] = {0x220, 0x240, 0x260}; static int possible_irqs[] = {5, 9, 10, 7, -1}; static int possible_dmas[] = {1, 3, 0, -1}; @@ -117,32 +134,20 @@ static int __devinit snd_es1688_legacy_create(struct snd_card *card, return error; } -static int __devinit snd_es1688_probe(struct device *dev, unsigned int n) +static int __devinit snd_es1688_probe(struct snd_card *card, unsigned int n) { - struct snd_card *card; - struct snd_es1688 *chip; + struct snd_es1688 *chip = card->private_data; struct snd_opl3 *opl3; struct snd_pcm *pcm; int error; - error = snd_card_create(index[n], id[n], THIS_MODULE, - sizeof(struct snd_es1688), &card); - if (error < 0) - return error; - - chip = card->private_data; - - error = snd_es1688_legacy_create(card, chip, dev, n); - if (error < 0) - goto out; - error = snd_es1688_pcm(card, chip, 0, &pcm); if (error < 0) - goto out; + return error; error = snd_es1688_mixer(card, chip); if (error < 0) - goto out; + return error; strcpy(card->driver, "ES1688"); strcpy(card->shortname, pcm->name); @@ -155,12 +160,12 @@ static int __devinit snd_es1688_probe(struct device *dev, unsigned int n) if (fm_port[n] > 0) { if (snd_opl3_create(card, fm_port[n], fm_port[n] + 2, OPL3_HW_OPL3, 0, &opl3) < 0) - dev_warn(dev, + dev_warn(card->dev, "opl3 not detected at 0x%lx\n", fm_port[n]); else { error = snd_opl3_hwdep_new(opl3, 0, 1, NULL); if (error < 0) - goto out; + return error; } } @@ -170,23 +175,41 @@ static int __devinit snd_es1688_probe(struct device *dev, unsigned int n) chip->mpu_port, 0, mpu_irq[n], IRQF_DISABLED, NULL); if (error < 0) - goto out; + return error; } + return snd_card_register(card); +} + +static int __devinit snd_es1688_isa_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + int error; + + error = snd_card_create(index[n], id[n], THIS_MODULE, + sizeof(struct snd_es1688), &card); + if (error < 0) + return error; + + error = snd_es1688_legacy_create(card, dev, n); + if (error < 0) + goto out; + snd_card_set_dev(card, dev); - error = snd_card_register(card); + error = snd_es1688_probe(card, n); if (error < 0) goto out; dev_set_drvdata(dev, card); - return 0; -out: snd_card_free(card); + return 0; +out: + snd_card_free(card); return error; } -static int __devexit snd_es1688_remove(struct device *dev, unsigned int n) +static int __devexit snd_es1688_isa_remove(struct device *dev, unsigned int n) { snd_card_free(dev_get_drvdata(dev)); dev_set_drvdata(dev, NULL); @@ -195,8 +218,8 @@ static int __devexit snd_es1688_remove(struct device *dev, unsigned int n) static struct isa_driver snd_es1688_driver = { .match = snd_es1688_match, - .probe = snd_es1688_probe, - .remove = __devexit_p(snd_es1688_remove), + .probe = snd_es1688_isa_probe, + .remove = __devexit_p(snd_es1688_isa_remove), #if 0 /* FIXME */ .suspend = snd_es1688_suspend, .resume = snd_es1688_resume, @@ -206,14 +229,140 @@ static struct isa_driver snd_es1688_driver = { } }; +static int snd_es968_pnp_is_probed; + +#ifdef CONFIG_PNP +static int __devinit snd_card_es968_pnp(struct snd_card *card, unsigned int n, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_es1688 *chip = card->private_data; + struct pnp_dev *pdev; + int error; + + pdev = pnp_request_card_device(pcard, pid->devs[0].id, NULL); + if (pdev == NULL) + return -ENODEV; + + error = pnp_activate_dev(pdev); + if (error < 0) { + snd_printk(KERN_ERR "ES968 pnp configure failure\n"); + return error; + } + port[n] = pnp_port_start(pdev, 0); + dma8[n] = pnp_dma(pdev, 0); + irq[n] = pnp_irq(pdev, 0); + + return snd_es1688_create(card, chip, port[n], mpu_port[n], irq[n], + mpu_irq[n], dma8[n], ES1688_HW_AUTO); +} + +static int __devinit snd_es968_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_card *card; + static unsigned int dev; + int error; + struct snd_es1688 *chip; + + if (snd_es968_pnp_is_probed) + return -EBUSY; + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + + error = snd_card_create(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_es1688), &card); + if (error < 0) + return error; + chip = card->private_data; + + error = snd_card_es968_pnp(card, dev, pcard, pid); + if (error < 0) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, &pcard->card->dev); + error = snd_es1688_probe(card, dev); + if (error < 0) + return error; + pnp_set_card_drvdata(pcard, card); + snd_es968_pnp_is_probed = 1; + return 0; +} + +static void __devexit snd_es968_pnp_remove(struct pnp_card_link * pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); + snd_es968_pnp_is_probed = 0; +} + +#ifdef CONFIG_PM +static int snd_es968_pnp_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_es1688 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + return 0; +} + +static int snd_es968_pnp_resume(struct pnp_card_link *pcard) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_es1688 *chip = card->private_data; + + snd_es1688_reset(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct pnp_card_device_id snd_es968_pnpids[] = { + { .id = "ESS0968", .devs = { { "@@@0968" }, } }, + { .id = "ESS0968", .devs = { { "ESS0968" }, } }, + { .id = "", } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids); + +static struct pnp_card_driver es968_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = DEV_NAME " PnP", + .id_table = snd_es968_pnpids, + .probe = snd_es968_pnp_detect, + .remove = __devexit_p(snd_es968_pnp_remove), +#ifdef CONFIG_PM + .suspend = snd_es968_pnp_suspend, + .resume = snd_es968_pnp_resume, +#endif +}; +#endif + static int __init alsa_card_es1688_init(void) { +#ifdef CONFIG_PNP + pnp_register_card_driver(&es968_pnpc_driver); + if (snd_es968_pnp_is_probed) + return 0; + pnp_unregister_card_driver(&es968_pnpc_driver); +#endif return isa_register_driver(&snd_es1688_driver, SNDRV_CARDS); } static void __exit alsa_card_es1688_exit(void) { - isa_unregister_driver(&snd_es1688_driver); + if (!snd_es968_pnp_is_probed) { + isa_unregister_driver(&snd_es1688_driver); + return; + } +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&es968_pnpc_driver); +#endif } module_init(alsa_card_es1688_init); diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c index fdd440417bf..07676200496 100644 --- a/sound/isa/es1688/es1688_lib.c +++ b/sound/isa/es1688/es1688_lib.c @@ -99,7 +99,7 @@ static unsigned char snd_es1688_mixer_read(struct snd_es1688 *chip, unsigned cha return result; } -static int snd_es1688_reset(struct snd_es1688 *chip) +int snd_es1688_reset(struct snd_es1688 *chip) { int i; @@ -115,6 +115,7 @@ static int snd_es1688_reset(struct snd_es1688 *chip) snd_es1688_dsp_command(chip, 0xc6); /* enable extended mode */ return 0; } +EXPORT_SYMBOL(snd_es1688_reset); static int snd_es1688_probe(struct snd_es1688 *chip) { diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile index af366968178..08b9fb97465 100644 --- a/sound/isa/sb/Makefile +++ b/sound/isa/sb/Makefile @@ -11,7 +11,6 @@ snd-sb8-objs := sb8.o snd-sb16-objs := sb16.o snd-sbawe-objs := sbawe.o emu8000.o snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o -snd-es968-objs := es968.o snd-jazz16-objs := jazz16.o # Toplevel Module Dependency @@ -21,7 +20,6 @@ obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o obj-$(CONFIG_SND_SB8) += snd-sb8.o obj-$(CONFIG_SND_SB16) += snd-sb16.o obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o -obj-$(CONFIG_SND_ES968) += snd-es968.o obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.o ifeq ($(CONFIG_SND_SB16_CSP),y) obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o diff --git a/sound/isa/sb/es968.c b/sound/isa/sb/es968.c deleted file mode 100644 index ff18286fef9..00000000000 --- a/sound/isa/sb/es968.c +++ /dev/null @@ -1,248 +0,0 @@ - -/* - card-es968.c - driver for ESS AudioDrive ES968 based soundcards. - Copyright (C) 1999 by Massimo Piccioni - - Thanks to Pierfrancesco 'qM2' Passerini. - - 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. - - 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 -*/ - -#include -#include -#include -#include -#include -#include -#include - -#define PFX "es968: " - -MODULE_AUTHOR("Massimo Piccioni "); -MODULE_DESCRIPTION("ESS AudioDrive ES968"); -MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{ESS,AudioDrive ES968}}"); - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ -static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ -static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ -static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ - -module_param_array(index, int, NULL, 0444); -MODULE_PARM_DESC(index, "Index value for es968 based soundcard."); -module_param_array(id, charp, NULL, 0444); -MODULE_PARM_DESC(id, "ID string for es968 based soundcard."); -module_param_array(enable, bool, NULL, 0444); -MODULE_PARM_DESC(enable, "Enable es968 based soundcard."); - -struct snd_card_es968 { - struct pnp_dev *dev; - struct snd_sb *chip; -}; - -static struct pnp_card_device_id snd_es968_pnpids[] = { - { .id = "ESS0968", .devs = { { "@@@0968" }, } }, - { .id = "", } /* end */ -}; - -MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids); - -#define DRIVER_NAME "snd-card-es968" - -static irqreturn_t snd_card_es968_interrupt(int irq, void *dev_id) -{ - struct snd_sb *chip = dev_id; - - if (chip->open & SB_OPEN_PCM) { - return snd_sb8dsp_interrupt(chip); - } else { - return snd_sb8dsp_midi_interrupt(chip); - } -} - -static int __devinit snd_card_es968_pnp(int dev, struct snd_card_es968 *acard, - struct pnp_card_link *card, - const struct pnp_card_device_id *id) -{ - struct pnp_dev *pdev; - int err; - - acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); - if (acard->dev == NULL) - return -ENODEV; - - pdev = acard->dev; - - err = pnp_activate_dev(pdev); - if (err < 0) { - snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); - return err; - } - port[dev] = pnp_port_start(pdev, 0); - dma8[dev] = pnp_dma(pdev, 0); - irq[dev] = pnp_irq(pdev, 0); - - return 0; -} - -static int __devinit snd_card_es968_probe(int dev, - struct pnp_card_link *pcard, - const struct pnp_card_device_id *pid) -{ - int error; - struct snd_sb *chip; - struct snd_card *card; - struct snd_card_es968 *acard; - - error = snd_card_create(index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_card_es968), &card); - if (error < 0) - return error; - acard = card->private_data; - if ((error = snd_card_es968_pnp(dev, acard, pcard, pid))) { - snd_card_free(card); - return error; - } - snd_card_set_dev(card, &pcard->card->dev); - - if ((error = snd_sbdsp_create(card, port[dev], - irq[dev], - snd_card_es968_interrupt, - dma8[dev], - -1, - SB_HW_AUTO, &chip)) < 0) { - snd_card_free(card); - return error; - } - acard->chip = chip; - - if ((error = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) { - snd_card_free(card); - return error; - } - - if ((error = snd_sbmixer_new(chip)) < 0) { - snd_card_free(card); - return error; - } - - if ((error = snd_sb8dsp_midi(chip, 0, NULL)) < 0) { - snd_card_free(card); - return error; - } - - strcpy(card->driver, "ES968"); - strcpy(card->shortname, "ESS ES968"); - sprintf(card->longname, "%s soundcard, %s at 0x%lx, irq %d, dma %d", - card->shortname, chip->name, chip->port, irq[dev], dma8[dev]); - - if ((error = snd_card_register(card)) < 0) { - snd_card_free(card); - return error; - } - pnp_set_card_drvdata(pcard, card); - return 0; -} - -static unsigned int __devinitdata es968_devices; - -static int __devinit snd_es968_pnp_detect(struct pnp_card_link *card, - const struct pnp_card_device_id *id) -{ - static int dev; - int res; - - for ( ; dev < SNDRV_CARDS; dev++) { - if (!enable[dev]) - continue; - res = snd_card_es968_probe(dev, card, id); - if (res < 0) - return res; - dev++; - es968_devices++; - return 0; - } - return -ENODEV; -} - -static void __devexit snd_es968_pnp_remove(struct pnp_card_link * pcard) -{ - snd_card_free(pnp_get_card_drvdata(pcard)); - pnp_set_card_drvdata(pcard, NULL); -} - -#ifdef CONFIG_PM -static int snd_es968_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) -{ - struct snd_card *card = pnp_get_card_drvdata(pcard); - struct snd_card_es968 *acard = card->private_data; - struct snd_sb *chip = acard->chip; - - snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); - snd_pcm_suspend_all(chip->pcm); - snd_sbmixer_suspend(chip); - return 0; -} - -static int snd_es968_pnp_resume(struct pnp_card_link *pcard) -{ - struct snd_card *card = pnp_get_card_drvdata(pcard); - struct snd_card_es968 *acard = card->private_data; - struct snd_sb *chip = acard->chip; - - snd_sbdsp_reset(chip); - snd_sbmixer_resume(chip); - snd_power_change_state(card, SNDRV_CTL_POWER_D0); - return 0; -} -#endif - -static struct pnp_card_driver es968_pnpc_driver = { - .flags = PNP_DRIVER_RES_DISABLE, - .name = "es968", - .id_table = snd_es968_pnpids, - .probe = snd_es968_pnp_detect, - .remove = __devexit_p(snd_es968_pnp_remove), -#ifdef CONFIG_PM - .suspend = snd_es968_pnp_suspend, - .resume = snd_es968_pnp_resume, -#endif -}; - -static int __init alsa_card_es968_init(void) -{ - int err = pnp_register_card_driver(&es968_pnpc_driver); - if (err) - return err; - - if (!es968_devices) { - pnp_unregister_card_driver(&es968_pnpc_driver); -#ifdef MODULE - snd_printk(KERN_ERR "no ES968 based soundcards found\n"); -#endif - return -ENODEV; - } - return 0; -} - -static void __exit alsa_card_es968_exit(void) -{ - pnp_unregister_card_driver(&es968_pnpc_driver); -} - -module_init(alsa_card_es968_init) -module_exit(alsa_card_es968_exit) -- cgit v1.2.3-18-g5258 From 670ff6abd6caff406b217f8a828d6c03656535d8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 10 May 2010 10:21:32 +0200 Subject: ALSA: opl4 - Fix a wrong argument in proc write callback The commit 24e4a1211f691fc671de44685430dbad757d8487 ALSA: info - Use standard types for info callbacks introduced a wrong type to snd_opl4_mem_proc_write() for pos argument. Fixed now. Signed-off-by: Takashi Iwai --- sound/drivers/opl4/opl4_proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c index c5c13c4c260..df850b8830a 100644 --- a/sound/drivers/opl4/opl4_proc.c +++ b/sound/drivers/opl4/opl4_proc.c @@ -73,7 +73,7 @@ static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, void *file_private_data, struct file *file, const char __user *_buf, - size_t count, size_t pos) + size_t count, loff_t pos) { struct snd_opl4 *opl4 = entry->private_data; char *buf; -- cgit v1.2.3-18-g5258 From 29e189c29d33d273424327ca4c1a74d8cdc04f48 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 20:30:00 +0100 Subject: ASoC: Remove unneeded suspend bias managment from CODEC drivers The core will ensure that the device is in either STANDBY or OFF bias before suspending, restoring the bias in the driver is unneeded. Some drivers doing slightly more roundabout things have been left alone for now. Tested-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ak4535.c | 1 - sound/soc/codecs/cq93vc.c | 2 +- sound/soc/codecs/ssm2602.c | 1 - sound/soc/codecs/stac9766.c | 3 --- sound/soc/codecs/tlv320aic23.c | 1 - sound/soc/codecs/tlv320aic3x.c | 2 +- sound/soc/codecs/tlv320dac33.c | 3 --- sound/soc/codecs/twl4030.c | 1 - sound/soc/codecs/twl6040.c | 1 - sound/soc/codecs/uda1380.c | 1 - sound/soc/codecs/wm8350.c | 3 --- sound/soc/codecs/wm8510.c | 2 +- sound/soc/codecs/wm8711.c | 2 +- sound/soc/codecs/wm8728.c | 2 +- sound/soc/codecs/wm8731.c | 1 - sound/soc/codecs/wm8903.c | 1 - sound/soc/codecs/wm8940.c | 1 - sound/soc/codecs/wm8960.c | 2 +- sound/soc/codecs/wm8974.c | 2 +- sound/soc/codecs/wm9712.c | 3 --- sound/soc/codecs/wm9713.c | 3 --- 21 files changed, 7 insertions(+), 31 deletions(-) diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 2f6221b2ebb..b5917df0d2a 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -445,7 +445,6 @@ static int ak4535_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; ak4535_sync(codec); ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ak4535_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c index 8f19b931064..a320fb5a0e2 100644 --- a/sound/soc/codecs/cq93vc.c +++ b/sound/soc/codecs/cq93vc.c @@ -153,7 +153,7 @@ static int cq93vc_resume(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - cq93vc_set_bias_level(codec, codec->suspend_bias_level); + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 99ddec7df61..f18c8b2d96d 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -559,7 +559,6 @@ static int ssm2602_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ssm2602_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index a1c7ca869d0..e90fe295c65 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -288,9 +288,6 @@ reset: } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - stac9766_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; } diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index da589d8664d..40bcbbad07f 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -633,7 +633,6 @@ static int tlv320aic23_resume(struct platform_device *pdev) } tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - tlv320aic23_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index d57372be7a9..eafa164a8f8 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1143,7 +1143,7 @@ static int aic3x_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - aic3x_set_bias_level(codec, codec->suspend_bias_level); + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index ad5e2636c94..007fe830be4 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1386,9 +1386,6 @@ static int dac33_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - dac33_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - dac33_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b717a03dfac..0fe74d1e2c5 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2181,7 +2181,6 @@ static int twl4030_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl4030_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index ead6d4b43b2..2ae442edeb9 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -996,7 +996,6 @@ static int twl6040_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl6040_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index ecba686563a..2f925a27dcd 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -670,7 +670,6 @@ static int uda1380_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - uda1380_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 145467d3705..e5a48da65f8 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1336,9 +1336,6 @@ static int wm8350_resume(struct platform_device *pdev) wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm8350_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; } diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index af8cb6995a1..d3527001b17 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -556,7 +556,7 @@ static int wm8510_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; } diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index f350c5ecd09..5acd35bdfd5 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -375,7 +375,7 @@ static int wm8711_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8711_set_bias_level(codec, codec->suspend_bias_level); + return 0; } diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 3fb653ba363..bf56a58d091 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -237,7 +237,7 @@ static int wm8728_resume(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - wm8728_set_bias_level(codec, codec->suspend_bias_level); + wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 63f90c4ad93..c4b63538bb7 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -460,7 +460,6 @@ static int wm8731_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 5a2510c39c1..05b28cf0f96 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1628,7 +1628,6 @@ static int wm8903_resume(struct platform_device *pdev) /* Bring the codec back up to standby first to minimise pop/clicks */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8903_set_bias_level(codec, codec->suspend_bias_level); /* Sync back everything else */ if (tmp_cache) { diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index b2f12828630..e313ab5de85 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -691,7 +691,6 @@ static int wm8940_resume(struct platform_device *pdev) ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret) goto error_ret; - ret = wm8940_set_bias_level(codec, codec->suspend_bias_level); error_ret: return ret; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 50b2376680c..518c84e623a 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -823,7 +823,7 @@ static int wm8960_resume(struct platform_device *pdev) } codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); - codec->set_bias_level(codec, codec->suspend_bias_level); + return 0; } diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 270eb956294..44841fec554 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -608,7 +608,7 @@ static int wm8974_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8974_set_bias_level(codec, codec->suspend_bias_level); + return 0; } diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index e237bf61512..dd4b6ce01ce 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -631,9 +631,6 @@ static int wm9712_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; } diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index d2244c93f58..6a30b844a67 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1200,9 +1200,6 @@ static int wm9713_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; } -- cgit v1.2.3-18-g5258 From 50ae8384cde9a67714ff03010493c5052690624e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 18:40:54 +0100 Subject: ASoC: Remove unused DAPM suspend flag We now manage suspend within the main power analysis rather than by flipping the state of widgets. Tested-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 1 - sound/soc/soc-dapm.c | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index d5d6ba862df..55c6d3d1894 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -427,7 +427,6 @@ struct snd_soc_dapm_widget { unsigned char connected:1; /* connected codec pin */ unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ - unsigned char suspend:1; /* was active before suspend */ unsigned char force:1; /* force state */ int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 476dbe6daba..b30b0a255cd 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2012,18 +2012,8 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, w->active = 0; break; case SND_SOC_DAPM_STREAM_SUSPEND: - if (w->active) - w->suspend = 1; - w->active = 0; - break; case SND_SOC_DAPM_STREAM_RESUME: - if (w->suspend) { - w->active = 1; - w->suspend = 0; - } - break; case SND_SOC_DAPM_STREAM_PAUSE_PUSH: - break; case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: break; } -- cgit v1.2.3-18-g5258 From 9949788b793826f2d19e929ac0219ad0e0891e2d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 20:24:05 +0100 Subject: ASoC: Refactor DAPM suspend handling Instead of using stream events to handle power down during suspend integrate the handling with the normal widget path checking by replacing all cases where we report a connected endpoint in a path with a function snd_soc_dapm_suspend_check() which looks at the ALSA power state for the card and reports false if we are in a D3 state. Since the core moves us into D3 prior to initating the suspend all power checks during suspend will cause the widgets to be powered down. In order to ensure that widgets are powered up on resume set the card to D2 at the start of resume handling (ALSA API calls require D0 so we are still protected against userspace access). Tested-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 3 +++ sound/soc/soc-dapm.c | 53 +++++++++++++++++++++++++++++----------------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4079223203e..95739767ec4 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -963,6 +963,9 @@ static void soc_resume_deferred(struct work_struct *work) dev_dbg(socdev->dev, "starting resume work\n"); + /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(codec->card, SNDRV_CTL_POWER_D2); + if (card->resume_pre) card->resume_pre(pdev); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b30b0a255cd..8c8b291320a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -430,6 +430,23 @@ static inline void dapm_clear_walk(struct snd_soc_codec *codec) p->walked = 0; } +/* We implement power down on suspend by checking the power state of + * the ALSA card - when we are suspending the ALSA state for the card + * is set to D3. + */ +static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_codec *codec = widget->codec; + + switch (snd_power_get_state(codec->card)) { + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + return 0; + default: + return 1; + } +} + /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. @@ -446,7 +463,7 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -454,12 +471,12 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_output && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected jack or spk ? */ if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources))) - return 1; + return snd_soc_dapm_suspend_check(widget); } list_for_each_entry(path, &widget->sinks, list_source) { @@ -492,7 +509,7 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -500,16 +517,16 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_input && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected VMID/Bias for lower pops */ if (widget->id == snd_soc_dapm_vmid) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected jack ? */ if (widget->id == snd_soc_dapm_mic || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks))) - return 1; + return snd_soc_dapm_suspend_check(widget); } list_for_each_entry(path, &widget->sources, list_sink) { @@ -897,22 +914,12 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (!w->power_check) continue; - /* If we're suspending then pull down all the - * power. */ - switch (event) { - case SND_SOC_DAPM_STREAM_SUSPEND: - power = 0; - break; - - default: - if (!w->force) - power = w->power_check(w); - else - power = 1; - if (power) - sys_power = 1; - break; - } + if (!w->force) + power = w->power_check(w); + else + power = 1; + if (power) + sys_power = 1; if (w->power == power) continue; -- cgit v1.2.3-18-g5258 From 1547aba993c990e5a316751431328145b38e1fea Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 21:11:40 +0100 Subject: ASoC: Support leaving paths enabled over system suspend Some devices can usefully run audio while the Linux system is suspended. One of the most common examples is smartphone systems, which are normally designed to allow audio to be run between the baseband and the CODEC without passing through the CPU and so can suspend the CPU when on a voice call for additional power savings. Support such systems by providing an API snd_soc_dapm_ignore_suspend(). This can be used to mark DAPM endpoints as not being sensitive to system suspend. When the system is being suspended paths between endpoints which are marked as ignoring suspend will be kept active. Both source and sink must be marked, and there must already be an active path between the two endpoints prior to suspend. When paths are active over suspend the bias management will hold the device bias in the ON state. This is used to avoid suspending the CODEC while it is still in use. Tested-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 2 ++ sound/soc/soc-core.c | 32 ++++++++++++++++++++++++++++---- sound/soc/soc-dapm.c | 31 ++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 55c6d3d1894..66ff4c124db 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -341,6 +341,7 @@ int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_sync(struct snd_soc_codec *codec); int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, const char *pin); +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin); /* dapm widget types */ enum snd_soc_dapm_type { @@ -428,6 +429,7 @@ struct snd_soc_dapm_widget { unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ unsigned char force:1; /* force state */ + unsigned char ignore_suspend:1; /* kept enabled over suspend */ int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 95739767ec4..8661e5b4adb 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -927,8 +927,19 @@ static int soc_suspend(struct device *dev) SND_SOC_DAPM_STREAM_SUSPEND); } - if (codec_dev->suspend) - codec_dev->suspend(pdev, PMSG_SUSPEND); + /* If there are paths active then the CODEC will be held with + * bias _ON and should not be suspended. */ + if (codec_dev->suspend) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->suspend(pdev, PMSG_SUSPEND); + break; + default: + dev_dbg(socdev->dev, "CODEC is on over suspend\n"); + break; + } + } for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; @@ -975,8 +986,21 @@ static void soc_resume_deferred(struct work_struct *work) cpu_dai->resume(cpu_dai); } - if (codec_dev->resume) - codec_dev->resume(pdev); + /* If the CODEC was idle over suspend then it will have been + * left with bias OFF or STANDBY and suspended so we must now + * resume. Otherwise the suspend was suppressed. + */ + if (codec_dev->resume) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->resume(pdev); + break; + default: + dev_dbg(socdev->dev, "CODEC was on over suspend\n"); + break; + } + } for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8c8b291320a..fefb6c44fc8 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -441,7 +441,9 @@ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) switch (snd_power_get_state(codec->card)) { case SNDRV_CTL_POWER_D3hot: case SNDRV_CTL_POWER_D3cold: - return 0; + if (widget->ignore_suspend) + pr_debug("%s ignoring suspend\n", widget->name); + return widget->ignore_suspend; default: return 1; } @@ -2136,6 +2138,33 @@ int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin) } EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status); +/** + * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint + * @codec: audio codec + * @pin: audio signal pin endpoint (or start point) + * + * Mark the given endpoint or pin as ignoring suspend. When the + * system is disabled a path between two endpoints flagged as ignoring + * suspend will not be disabled. The path must already be enabled via + * normal means at suspend time, it will not be turned on if it was not + * already enabled. + */ +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + w->ignore_suspend = 1; + return 0; + } + } + + pr_err("Unknown DAPM pin: %s\n", pin); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); + /** * snd_soc_dapm_free - free dapm resources * @socdev: SoC device -- cgit v1.2.3-18-g5258 From 452a5fd679f8a6f215df71c41487ad4b2c7774db Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 21:18:53 +0100 Subject: ASoC: Allow active paths from the GSM modem while the GTA02 is suspended Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index dea83d30a5c..209c25994c7 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -362,6 +362,14 @@ static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) snd_soc_dapm_disable_pin(codec, "Handset Mic"); snd_soc_dapm_disable_pin(codec, "Handset Spk"); + /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(codec, "Stereo Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line In"); + snd_soc_dapm_ignore_suspend(codec, "Headset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Spk"); + snd_soc_dapm_sync(codec); return 0; -- cgit v1.2.3-18-g5258 From 3efab7dcc0f4d0087f73ff975eaa2fddc02ffc69 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 May 2010 13:25:43 +0100 Subject: ASoC: Allow DAI links to be kept active over suspend As well as allowing DAPM pins to be marked as ignoring suspend allow DAI links to be similarly marked. This is primarily intended for digital links between CODECs and non-CPU devices such as basebands in mobile phones and will suppress all suspend calls for the DAI link. It is likely that this will need to be revisited if used with devices which are part of the SoC CPU. Tested-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 9f306f0710d..8326fc3db1c 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -505,6 +505,9 @@ struct snd_soc_dai_link { /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec); + /* Keep DAI active over suspend */ + unsigned int ignore_suspend:1; + /* Symmetry requirements */ unsigned int symmetric_rates:1; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 8661e5b4adb..34f71bf6014 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -893,19 +893,31 @@ static int soc_suspend(struct device *dev) /* mute any active DAC's */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 1); } /* suspend all pcms */ - for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { + if (card->dai_link[i].ignore_suspend) + continue; + snd_pcm_suspend_all(card->dai_link[i].pcm); + } if (card->suspend_pre) card->suspend_pre(pdev, PMSG_SUSPEND); for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && !cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); if (platform->suspend) @@ -918,6 +930,10 @@ static int soc_suspend(struct device *dev) for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_SUSPEND); @@ -943,6 +959,10 @@ static int soc_suspend(struct device *dev) for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); } @@ -982,6 +1002,10 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); } @@ -1004,6 +1028,10 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_RESUME); @@ -1016,12 +1044,20 @@ static void soc_resume_deferred(struct work_struct *work) /* unmute any active DACs */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 0); } for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && !cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); if (platform->resume) -- cgit v1.2.3-18-g5258 From 06ae99888e599ea2e82386d618405c473c379185 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 19:14:45 +0100 Subject: ASoC: Refactor WM8731 regulator management into bias management This allows more flexible integration with subsystem features. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8731.c | 51 ++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index c4b63538bb7..2650fac30ee 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -368,6 +368,10 @@ static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, static int wm8731_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); + int i, ret; + u8 data[2]; + u16 *cache = codec->reg_cache; u16 reg; switch (level) { @@ -376,6 +380,24 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + if (ret != 0) + return ret; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) { + if (cache[i] == wm8731_reg[i]) + continue; + + data[0] = (i << 1) | ((cache[i] >> 8) + & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + } + /* Clear PWROFF, gate CLKOUT, everything else as-is */ reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f; snd_soc_write(codec, WM8731_PWR, reg | 0x0040); @@ -383,6 +405,8 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_OFF: snd_soc_write(codec, WM8731_ACTIVE, 0x0); snd_soc_write(codec, WM8731_PWR, 0xffff); + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); break; } codec->bias_level = level; @@ -427,12 +451,9 @@ static int wm8731_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; - struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); - snd_soc_write(codec, WM8731_ACTIVE, 0x0); wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); - regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), - wm8731->supplies); + return 0; } @@ -440,25 +461,7 @@ static int wm8731_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); - int i, ret; - u8 data[2]; - u16 *cache = codec->reg_cache; - - ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), - wm8731->supplies); - if (ret != 0) - return ret; - - /* Sync reg_cache with the hardware */ - for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) { - if (cache[i] == wm8731_reg[i]) - continue; - data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); - data[1] = cache[i] & 0x00ff; - codec->hw_write(codec->control_data, data, 2); - } wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; @@ -603,6 +606,9 @@ static int wm8731_register(struct wm8731_priv *wm8731, goto err_codec; } + /* Regulators will have been enabled by bias management */ + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); + return 0; err_codec: @@ -621,7 +627,6 @@ static void wm8731_unregister(struct wm8731_priv *wm8731) wm8731_set_bias_level(&wm8731->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dai(&wm8731_dai); snd_soc_unregister_codec(&wm8731->codec); - regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); kfree(wm8731); wm8731_codec = NULL; -- cgit v1.2.3-18-g5258 From 896060c76bdfd8a45eb33b3dd1a8307fe37f6c04 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 7 May 2010 18:39:25 +0100 Subject: ASoC: Use more idiomatic driver name for WM8731 Make dev_() prints much prettier. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- drivers/mfd/wm831x-irq.c | 30 ++++++++++++++++++++++++++++++ sound/soc/codecs/wm8731.c | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 30132769711..4c1122ceb44 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -388,12 +389,41 @@ static void wm831x_irq_mask(unsigned int irq) wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; } +static int wm831x_irq_set_type(unsigned int irq, unsigned int type) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + int val; + + irq = irq - wm831x->irq_base; + + if (irq < WM831X_IRQ_GPIO_1 || irq > WM831X_IRQ_GPIO_11) + return -EINVAL; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + val = WM831X_GPN_INT_MODE; + break; + case IRQ_TYPE_EDGE_RISING: + val = WM831X_GPN_POL; + break; + case IRQ_TYPE_EDGE_FALLING: + val = 0; + break; + default: + return -EINVAL; + } + + return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + irq, + WM831X_GPN_INT_MODE | WM831X_GPN_POL, val); +} + static struct irq_chip wm831x_irq_chip = { .name = "wm831x", .bus_lock = wm831x_irq_lock, .bus_sync_unlock = wm831x_irq_sync_unlock, .mask = wm831x_irq_mask, .unmask = wm831x_irq_unmask, + .set_type = wm831x_irq_set_type, }; /* The processing of the primary interrupt occurs in a thread so that diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 2650fac30ee..e17c714196f 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -707,7 +707,7 @@ MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); static struct i2c_driver wm8731_i2c_driver = { .driver = { - .name = "WM8731 I2C Codec", + .name = "wm8731", .owner = THIS_MODULE, }, .probe = wm8731_i2c_probe, -- cgit v1.2.3-18-g5258 From 1965c441ecf1f9cd723dd590cbb6c191cfc13fb2 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Thu, 6 May 2010 16:37:03 -0500 Subject: ALSA: hda: enable SPDIF output for Conexant 5051/Lenovo docking stations This patch enables the SPDIF output pin by default. It also enables it for quirks related to Levono docking stations (x200 and 25041, identified with the same 17aa:20f2 ID). Even though not all Lenovo docking stations have SPDIF connectors, enabling the pin by default shouldn't be a problem for anyone. Other quirks remain unmodified. Signed-off-by: Pierre-Louis Bossart Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_conexant.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 114af8e19d8..0aa122e26dc 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -1784,6 +1784,7 @@ static struct hda_verb cxt5051_init_verbs[] = { {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44}, {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44}, /* SPDIF route: PCM */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0}, /* EAPD */ {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ @@ -1840,6 +1841,7 @@ static struct hda_verb cxt5051_lenovo_x200_init_verbs[] = { {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44}, {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44}, /* SPDIF route: PCM */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, /* needed for W500 Advanced Mini Dock 250410 */ {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0}, /* EAPD */ {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ @@ -1911,7 +1913,7 @@ enum { CXT5051_LAPTOP, /* Laptops w/ EAPD support */ CXT5051_HP, /* no docking */ CXT5051_HP_DV6736, /* HP without mic switch */ - CXT5051_LENOVO_X200, /* Lenovo X200 laptop */ + CXT5051_LENOVO_X200, /* Lenovo X200 laptop, also used for Advanced Mini Dock 250410 */ CXT5051_F700, /* HP Compaq Presario F700 */ CXT5051_TOSHIBA, /* Toshiba M300 & co */ CXT5051_MODELS -- cgit v1.2.3-18-g5258 From beaffc399397eb9427225f37c1e56c099571b2df Mon Sep 17 00:00:00 2001 From: Shahin Ghazinouri Date: Tue, 11 May 2010 08:19:55 +0200 Subject: ALSA: hda - Fixes distorted recording on US15W chipset The HDA controller in US15W (Poulsbo) reports inaccurate position values for capture streams when using the LPIB read method, resulting in distorted recordings. However, using the position buffer is broken for playback streams, resulting in a fallback to the LPIB method with the current driver. This patch works around the issue by independently detecting the read position method for capture and playback streams. The patch will not have any effect if the position fix method is explicitly set. [Code simplified by tiwai] Signed-off-by: Shahin Ghazinouri Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 236b4caa142..cad9b70c27a 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -425,7 +425,7 @@ struct azx { struct snd_dma_buffer posbuf; /* flags */ - int position_fix; + int position_fix[2]; /* for both playback/capture streams */ int poll_count; unsigned int running :1; unsigned int initialized :1; @@ -1306,8 +1306,10 @@ static int azx_setup_controller(struct azx *chip, struct azx_dev *azx_dev) azx_sd_writel(azx_dev, SD_BDLPU, upper_32_bits(azx_dev->bdl.addr)); /* enable the position buffer */ - if (chip->position_fix == POS_FIX_POSBUF || - chip->position_fix == POS_FIX_AUTO || + if (chip->position_fix[0] == POS_FIX_POSBUF || + chip->position_fix[0] == POS_FIX_AUTO || + chip->position_fix[1] == POS_FIX_POSBUF || + chip->position_fix[1] == POS_FIX_AUTO || chip->via_dmapos_patch) { if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE)) azx_writel(chip, DPLBASE, @@ -1847,13 +1849,16 @@ static unsigned int azx_get_position(struct azx *chip, if (chip->via_dmapos_patch) pos = azx_via_get_position(chip, azx_dev); - else if (chip->position_fix == POS_FIX_POSBUF || - chip->position_fix == POS_FIX_AUTO) { - /* use the position buffer */ - pos = le32_to_cpu(*azx_dev->posbuf); - } else { - /* read LPIB */ - pos = azx_sd_readl(azx_dev, SD_LPIB); + else { + int stream = azx_dev->substream->stream; + if (chip->position_fix[stream] == POS_FIX_POSBUF || + chip->position_fix[stream] == POS_FIX_AUTO) { + /* use the position buffer */ + pos = le32_to_cpu(*azx_dev->posbuf); + } else { + /* read LPIB */ + pos = azx_sd_readl(azx_dev, SD_LPIB); + } } if (pos >= azx_dev->bufsize) pos = 0; @@ -1881,22 +1886,24 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) { unsigned int pos; + int stream; if (azx_dev->start_flag && time_before_eq(jiffies, azx_dev->start_jiffies)) return -1; /* bogus (too early) interrupt */ azx_dev->start_flag = 0; + stream = azx_dev->substream->stream; pos = azx_get_position(chip, azx_dev); - if (chip->position_fix == POS_FIX_AUTO) { + if (chip->position_fix[stream] == POS_FIX_AUTO) { if (!pos) { printk(KERN_WARNING "hda-intel: Invalid position buffer, " "using LPIB read method instead.\n"); - chip->position_fix = POS_FIX_LPIB; + chip->position_fix[stream] = POS_FIX_LPIB; pos = azx_get_position(chip, azx_dev); } else - chip->position_fix = POS_FIX_POSBUF; + chip->position_fix[stream] = POS_FIX_POSBUF; } if (!bdl_pos_adj[chip->dev_index]) @@ -2435,7 +2442,8 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, chip->dev_index = dev; INIT_WORK(&chip->irq_pending_work, azx_irq_pending_work); - chip->position_fix = check_position_fix(chip, position_fix[dev]); + chip->position_fix[0] = chip->position_fix[1] = + check_position_fix(chip, position_fix[dev]); check_probe_mask(chip, dev); chip->single_cmd = single_cmd; -- cgit v1.2.3-18-g5258 From e54637205b00837bf00de916b0ae361c6aa0b139 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 11 May 2010 10:21:46 +0200 Subject: [ALSA] snd-hda-intel: use WALLCLK register to check for early irqs Use 24Mhz WALLCLK register to ignore too early interrupts and wrong interrupt status. The bad timing confuses the higher ALSA layer and causes audio skipping. More information about behaviour and debugging can be found in kernel bz#15912. https://bugzilla.kernel.org/show_bug.cgi?id=15912 Signed-off-by: Jaroslav Kysela --- sound/pci/hda/hda_intel.c | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index cad9b70c27a..0a6c55bb7d6 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -174,7 +174,7 @@ MODULE_DESCRIPTION("Intel HDA driver"); #define ICH6_GSTS_FSTS (1 << 1) /* flush status */ #define ICH6_REG_INTCTL 0x20 #define ICH6_REG_INTSTS 0x24 -#define ICH6_REG_WALCLK 0x30 +#define ICH6_REG_WALLCLK 0x30 /* 24Mhz source */ #define ICH6_REG_SYNC 0x34 #define ICH6_REG_CORBLBASE 0x40 #define ICH6_REG_CORBUBASE 0x44 @@ -340,8 +340,8 @@ struct azx_dev { unsigned int period_bytes; /* size of the period in bytes */ unsigned int frags; /* number for period in the play buffer */ unsigned int fifo_size; /* FIFO size */ - unsigned long start_jiffies; /* start + minimum jiffies */ - unsigned long min_jiffies; /* minimum jiffies before position is valid */ + unsigned long start_wallclk; /* start + minimum wallclk */ + unsigned long period_wallclk; /* wallclk for period */ void __iomem *sd_addr; /* stream descriptor pointer */ @@ -361,7 +361,6 @@ struct azx_dev { unsigned int opened :1; unsigned int running :1; unsigned int irq_pending :1; - unsigned int start_flag: 1; /* stream full start flag */ /* * For VIA: * A flag to ensure DMA position is 0 @@ -1676,8 +1675,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) return err; } - azx_dev->min_jiffies = (runtime->period_size * HZ) / - (runtime->rate * 2); + /* wallclk has 24Mhz clock source */ + azx_dev->period_wallclk = (((runtime->period_size * 24000) / + runtime->rate) * 1000); azx_setup_controller(chip, azx_dev); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1; @@ -1731,14 +1731,15 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) if (s->pcm->card != substream->pcm->card) continue; azx_dev = get_azx_dev(s); - if (rstart) { - azx_dev->start_flag = 1; - azx_dev->start_jiffies = jiffies + azx_dev->min_jiffies; - } - if (start) + if (start) { + azx_dev->start_wallclk = azx_readl(chip, WALLCLK); + if (!rstart) + azx_dev->start_wallclk -= + azx_dev->period_wallclk; azx_stream_start(chip, azx_dev); - else + } else { azx_stream_stop(chip, azx_dev); + } azx_dev->running = start; } spin_unlock(&chip->reg_lock); @@ -1885,13 +1886,14 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) */ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) { + u32 wallclk; unsigned int pos; int stream; - if (azx_dev->start_flag && - time_before_eq(jiffies, azx_dev->start_jiffies)) + wallclk = azx_readl(chip, WALLCLK); + if ((wallclk - azx_dev->start_wallclk) < + (azx_dev->period_wallclk * 2) / 3) return -1; /* bogus (too early) interrupt */ - azx_dev->start_flag = 0; stream = azx_dev->substream->stream; pos = azx_get_position(chip, azx_dev); @@ -1906,13 +1908,12 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) chip->position_fix[stream] = POS_FIX_POSBUF; } - if (!bdl_pos_adj[chip->dev_index]) - return 1; /* no delayed ack */ if (WARN_ONCE(!azx_dev->period_bytes, "hda-intel: zero azx_dev->period_bytes")) return 0; /* this shouldn't happen! */ if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) return 0; /* NG - it's below the period boundary */ + azx_dev->start_wallclk = wallclk; return 1; /* OK, it's fine */ } @@ -1922,7 +1923,7 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) static void azx_irq_pending_work(struct work_struct *work) { struct azx *chip = container_of(work, struct azx, irq_pending_work); - int i, pending; + int i, pending, ok; if (!chip->irq_pending_warned) { printk(KERN_WARNING @@ -1941,11 +1942,14 @@ static void azx_irq_pending_work(struct work_struct *work) !azx_dev->substream || !azx_dev->running) continue; - if (azx_position_ok(chip, azx_dev)) { + ok = azx_position_ok(chip, azx_dev); + if (ok > 0) { azx_dev->irq_pending = 0; spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(azx_dev->substream); spin_lock(&chip->reg_lock); + } else if (ok < 0) { + pending = 0; /* too early */ } else pending++; } -- cgit v1.2.3-18-g5258 From d11bb4a925613fa814ed4ae350440eb24ebff336 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 10 May 2010 14:39:24 +0300 Subject: ASoC: core: Fix for the volume limiting when invert is in use If the register for the volume needs invert, than the inversion need to be done from the chip maximum, and not from the platform dependent limit. Introduce soc_mixer_control.platform_max value, which initially equals to chip maximum. The snd_soc_limit_volume function only modify the platform_max, all volsw_info call returns this as well. The .max value holds the chip default (maximum), and it is used for the inversion, if it is needed. Additional check in the volsw_info call has been added to check the validity of the platform_max in case, when custom macros used by codec drivers are not initializing it correctly. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 23 ++++++++++++----------- sound/soc/soc-core.c | 30 +++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index 8326fc3db1c..697e7ffe39d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -30,10 +30,10 @@ #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \ ((unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .rshift = xshift, .max = xmax, \ - .invert = xinvert}) + .platform_max = xmax, .invert = xinvert}) #define SOC_SINGLE_VALUE_EXT(xreg, xmax, xinvert) \ ((unsigned long)&(struct soc_mixer_control) \ - {.reg = xreg, .max = xmax, .invert = xinvert}) + {.reg = xreg, .max = xmax, .platform_max = xmax, .invert = xinvert}) #define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ @@ -53,14 +53,14 @@ .put = snd_soc_put_volsw, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ @@ -70,7 +70,7 @@ .put = snd_soc_put_volsw, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right,\ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ @@ -80,7 +80,7 @@ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ @@ -89,7 +89,8 @@ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ .put = snd_soc_put_volsw_s8, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ - {.reg = xreg, .min = xmin, .max = xmax} } + {.reg = xreg, .min = xmin, .max = xmax, \ + .platform_max = xmax} } #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .max = xmax, .texts = xtexts } @@ -126,7 +127,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -146,7 +147,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R_EXT_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ @@ -157,7 +158,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_bool_ext, \ @@ -572,7 +573,7 @@ struct snd_soc_pcm_runtime { /* mixer control */ struct soc_mixer_control { - int min, max; + int min, max, platform_max; unsigned int reg, rreg, shift, rshift, invert; }; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 34f71bf6014..e1043f64473 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2017,18 +2017,22 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; - if (max == 1 && !strstr(kcontrol->id.name, " Volume")) + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max; + uinfo->value.integer.max = platform_max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw); @@ -2126,16 +2130,20 @@ int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; - if (max == 1 && !strstr(kcontrol->id.name, " Volume")) + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max; + uinfo->value.integer.max = platform_max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); @@ -2236,13 +2244,17 @@ int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; int min = mc->min; + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max-min; + uinfo->value.integer.max = platform_max - min; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); @@ -2331,7 +2343,7 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec, if (found) { mc = (struct soc_mixer_control *)kctl->private_value; if (max <= mc->max) { - mc->max = max; + mc->platform_max = max; ret = 0; } } -- cgit v1.2.3-18-g5258 From f48f606d9f85a8b25c716082c73c36e9ff6f0cde Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 11 May 2010 12:10:47 +0200 Subject: [ALSA] snd-hda-intel: Improve azx_position_ok() Add back the zero return value (activate workqueue) when bdl_pos_adj is nonzero for position check. Do the position related check only for first next period using wallclk counter. Return -1 value (ignore interrupt) when period_bytes variable is zero. Signed-off-by: Jaroslav Kysela --- sound/pci/hda/hda_intel.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 0a6c55bb7d6..170610e1d7d 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1890,9 +1890,8 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) unsigned int pos; int stream; - wallclk = azx_readl(chip, WALLCLK); - if ((wallclk - azx_dev->start_wallclk) < - (azx_dev->period_wallclk * 2) / 3) + wallclk = azx_readl(chip, WALLCLK) - azx_dev->start_wallclk; + if (wallclk < (azx_dev->period_wallclk * 2) / 3) return -1; /* bogus (too early) interrupt */ stream = azx_dev->substream->stream; @@ -1910,9 +1909,11 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) if (WARN_ONCE(!azx_dev->period_bytes, "hda-intel: zero azx_dev->period_bytes")) - return 0; /* this shouldn't happen! */ - if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) - return 0; /* NG - it's below the period boundary */ + return -1; /* this shouldn't happen! */ + if (wallclk <= azx_dev->period_wallclk && + pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) + /* NG - it's below the first next period boundary */ + return bdl_pos_adj[chip->dev_index] ? 0 : -1; azx_dev->start_wallclk = wallclk; return 1; /* OK, it's fine */ } -- cgit v1.2.3-18-g5258 From fd5722e5cdb233bbc47e782e10b3b5e3488a22fa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 May 2010 14:55:04 +0100 Subject: ASoC: Add register write logging for WM8994 Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8994.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index f04a116e678..c6b3f1ebd62 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1710,6 +1710,8 @@ static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, if (!wm8994_volatile(reg)) wm8994->reg_cache[reg] = value; + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + return wm8994_reg_write(codec->control_data, reg, value); } -- cgit v1.2.3-18-g5258 From 3254d2850075c88083d870b3ee5e1ce0922cd3d9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 May 2010 14:56:03 +0100 Subject: ASoC: Add additional WM hubs DC servo trace Log the values we're getting back from the DC servo and the values we write to it. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm_hubs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index defdbd08e13..8ad43bb32eb 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -127,6 +127,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) break; } + dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r); + /* HPOUT1L */ if (reg_l + hubs->dcs_codes > 0 && reg_l + hubs->dcs_codes < 0xff) @@ -139,6 +141,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) reg_r += hubs->dcs_codes; dcs_cfg |= reg_r; + dev_dbg(codec->dev, "DCS result: %x\n", dcs_cfg); + /* Do it */ snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg); wait_for_dc_servo(codec, -- cgit v1.2.3-18-g5258 From 6adb26bd03f6056357298bc29d5c6acdea2b216a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 May 2010 16:13:11 +0100 Subject: ASoC: Reorder power down sequence for WM hubs devices Disable the output stage prior to the delay stage rather than the other way around. Fixes merge issue with previous headphone output path corrections. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm_hubs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 8ad43bb32eb..16f1a57da08 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -401,14 +401,14 @@ static int hp_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, - WM8993_HPOUT1L_DLY | - WM8993_HPOUT1R_DLY | + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_OUTP | WM8993_HPOUT1L_RMV_SHORT | WM8993_HPOUT1R_RMV_SHORT, 0); snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, - WM8993_HPOUT1L_OUTP | - WM8993_HPOUT1R_OUTP, 0); + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY, 0); snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, -- cgit v1.2.3-18-g5258 From 6a2f1ee1f9bb5346644105c9355e9e06f6a847d3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 May 2010 18:36:37 +0100 Subject: ASoC: Don't restart unconfigured WM8994 FLLs If the FLL is not configured attempting to resume it will produce a warning message so skip the resume. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8994.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index c6b3f1ebd62..dda356591fd 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3548,6 +3548,9 @@ static int wm8994_resume(struct platform_device *pdev) wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY); for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + if (!wm8994->fll_suspend[i].out) + continue; + ret = wm8994_set_fll(&codec->dai[0], i + 1, wm8994->fll_suspend[i].src, wm8994->fll_suspend[i].in, -- cgit v1.2.3-18-g5258 From 89485d4931769d40353ea49bff1596accff8f06e Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Tue, 11 May 2010 18:57:37 +0200 Subject: ALSA: include/sound/asound.h whitespace fixups This fixes some whitespace/indentation flaws I stumbled over. Signed-off-by: Daniel Mack Signed-off-by: Takashi Iwai --- include/sound/asound.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/sound/asound.h b/include/sound/asound.h index 09859550063..9f1eecf99e6 100644 --- a/include/sound/asound.h +++ b/include/sound/asound.h @@ -574,7 +574,7 @@ enum { #define SNDRV_TIMER_FLG_SLAVE (1<<0) /* cannot be controlled */ struct snd_timer_id { - int dev_class; + int dev_class; int dev_sclass; int card; int device; @@ -762,7 +762,7 @@ struct snd_ctl_elem_id { snd_ctl_elem_iface_t iface; /* interface identifier */ unsigned int device; /* device/client number */ unsigned int subdevice; /* subdevice (substream) number */ - unsigned char name[44]; /* ASCII name of item */ + unsigned char name[44]; /* ASCII name of item */ unsigned int index; /* index of item */ }; @@ -809,7 +809,7 @@ struct snd_ctl_elem_info { struct snd_ctl_elem_value { struct snd_ctl_elem_id id; /* W: element ID */ unsigned int indirect: 1; /* W: indirect access - obsoleted */ - union { + union { union { long value[128]; long *value_ptr; /* obsoleted */ @@ -827,15 +827,15 @@ struct snd_ctl_elem_value { unsigned char *data_ptr; /* obsoleted */ } bytes; struct snd_aes_iec958 iec958; - } value; /* RO */ + } value; /* RO */ struct timespec tstamp; - unsigned char reserved[128-sizeof(struct timespec)]; + unsigned char reserved[128-sizeof(struct timespec)]; }; struct snd_ctl_tlv { - unsigned int numid; /* control element numeric identification */ - unsigned int length; /* in bytes aligned to 4 */ - unsigned int tlv[0]; /* first TLV */ + unsigned int numid; /* control element numeric identification */ + unsigned int length; /* in bytes aligned to 4 */ + unsigned int tlv[0]; /* first TLV */ }; #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) @@ -886,8 +886,8 @@ struct snd_ctl_event { unsigned int mask; struct snd_ctl_elem_id id; } elem; - unsigned char data8[60]; - } data; + unsigned char data8[60]; + } data; }; /* -- cgit v1.2.3-18-g5258 From 0350b6a0cbeaf46e0883d8c79ede2efd49965472 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Tue, 11 May 2010 18:13:49 +0200 Subject: include/linux/usb/audio.h: add __attribute__((packed)) This was missing on the definition of struct uac_iso_endpoint_descriptor Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index 57f20551939..9fae6bdab33 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -456,7 +456,7 @@ struct uac_iso_endpoint_descriptor { __u8 bmAttributes; __u8 bLockDelayUnits; __le16 wLockDelay; -}; +} __attribute__((packed)); #define UAC_ISO_ENDPOINT_DESC_SIZE 7 #define UAC_EP_CS_ATTR_SAMPLE_RATE 0x01 -- cgit v1.2.3-18-g5258 From e213e9cf707c51808e372dabd1070a61af17e77b Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Tue, 11 May 2010 18:13:50 +0200 Subject: ALSA: sound/usb: add preliminary support for UAC2 interrupts For both UAC1 and UAC2, interrupt endpoint messages are now parsed with structs rather that with anonymous buffer array accesses. For UAC2, only CUR interrupt notifications are supported for now. snd_usb_mixer_status_complete() was renamed to snd_usb_mixer_interrupt(). Fixed one indentation flaw on the way. Signed-off-by: Daniel Mack Cc: Clemens Ladisch Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v2.h | 12 ++++++ include/linux/usb/audio.h | 15 +++++++ sound/usb/mixer.c | 98 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 112 insertions(+), 13 deletions(-) diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index 0952231e6c3..2389f93a28b 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -105,6 +105,17 @@ struct uac_as_header_descriptor_v2 { __u8 iChannelNames; } __attribute__((packed)); +/* 6.1 Interrupt Data Message */ + +#define UAC2_INTERRUPT_DATA_MSG_VENDOR (1 << 0) +#define UAC2_INTERRUPT_DATA_MSG_EP (1 << 1) + +struct uac2_interrupt_data_msg { + __u8 bInfo; + __u8 bAttribute; + __le16 wValue; + __le16 wIndex; +} __attribute__((packed)); /* A.7 Audio Function Category Codes */ #define UAC2_FUNCTION_SUBCLASS_UNDEFINED 0x00 @@ -153,6 +164,7 @@ struct uac_as_header_descriptor_v2 { /* A.14 Audio Class-Specific Request Codes */ #define UAC2_CS_CUR 0x01 #define UAC2_CS_RANGE 0x02 +#define UAC2_CS_MEM 0x03 /* A.15 Encoder Type Codes */ #define UAC2_ENCODER_UNDEFINED 0x00 diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index 9fae6bdab33..c0ef18dc2da 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -488,6 +488,21 @@ struct uac_iso_endpoint_descriptor { #define UAC_FU_BASS_BOOST (1 << (UAC_BASS_BOOST_CONTROL - 1)) #define UAC_FU_LOUDNESS (1 << (UAC_LOUDNESS_CONTROL - 1)) +/* status word format (3.7.1.1) */ + +#define UAC1_STATUS_TYPE_ORIG_MASK 0x0f +#define UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF 0x0 +#define UAC1_STATUS_TYPE_ORIG_AUDIO_STREAM_IF 0x1 +#define UAC1_STATUS_TYPE_ORIG_AUDIO_STREAM_EP 0x2 + +#define UAC1_STATUS_TYPE_IRQ_PENDING (1 << 7) +#define UAC1_STATUS_TYPE_MEM_CHANGED (1 << 6) + +struct uac1_status_word { + __u8 bStatusType; + __u8 bOriginator; +} __attribute__((packed)); + #ifdef __KERNEL__ struct usb_audio_control { diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e350f053440..820dfe08ac2 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1443,8 +1443,8 @@ static struct procunit_info procunits[] = { * predefined data for extension units */ static struct procunit_value_info clock_rate_xu_info[] = { - { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, - { 0 } + { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, + { 0 } }; static struct procunit_value_info clock_source_xu_info[] = { { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN }, @@ -1967,26 +1967,98 @@ static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, } } -static void snd_usb_mixer_status_complete(struct urb *urb) +static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, + int attribute, int value, int index) +{ + struct usb_mixer_elem_info *info; + __u8 unitid = (index >> 8) & 0xff; + __u8 control = (value >> 8) & 0xff; + __u8 channel = value & 0xff; + + if (channel >= MAX_CHANNELS) { + snd_printk(KERN_DEBUG "%s(): bogus channel number %d\n", + __func__, channel); + return; + } + + for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) { + if (info->control != control) + continue; + + switch (attribute) { + case UAC2_CS_CUR: + /* invalidate cache, so the value is read from the device */ + if (channel) + info->cached &= ~(1 << channel); + else /* master channel */ + info->cached = 0; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + info->elem_id); + break; + + case UAC2_CS_RANGE: + /* TODO */ + break; + + case UAC2_CS_MEM: + /* TODO */ + break; + + default: + snd_printk(KERN_DEBUG "unknown attribute %d in interrupt\n", + attribute); + break; + } /* switch */ + } +} + +static void snd_usb_mixer_interrupt(struct urb *urb) { struct usb_mixer_interface *mixer = urb->context; + int len = urb->actual_length; + + if (urb->status != 0) + goto requeue; - if (urb->status == 0) { - u8 *buf = urb->transfer_buffer; - int i; + if (mixer->protocol == UAC_VERSION_1) { + struct uac1_status_word *status; - for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) { + for (status = urb->transfer_buffer; + len >= sizeof(*status); + len -= sizeof(*status), status++) { snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n", - buf[0], buf[1]); + status->bStatusType, + status->bOriginator); + /* ignore any notifications not from the control interface */ - if ((buf[0] & 0x0f) != 0) + if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) != + UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF) continue; - if (!(buf[0] & 0x40)) - snd_usb_mixer_notify_id(mixer, buf[1]); + + if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED) + snd_usb_mixer_rc_memory_change(mixer, status->bOriginator); else - snd_usb_mixer_rc_memory_change(mixer, buf[1]); + snd_usb_mixer_notify_id(mixer, status->bOriginator); + } + } else { /* UAC_VERSION_2 */ + struct uac2_interrupt_data_msg *msg; + + for (msg = urb->transfer_buffer; + len >= sizeof(*msg); + len -= sizeof(*msg), msg++) { + /* drop vendor specific and endpoint requests */ + if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) || + (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP)) + continue; + + snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute, + le16_to_cpu(msg->wValue), + le16_to_cpu(msg->wIndex)); } } + +requeue: if (urb->status != -ENOENT && urb->status != -ECONNRESET) { urb->dev = mixer->chip->dev; usb_submit_urb(urb, GFP_ATOMIC); @@ -2023,7 +2095,7 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) usb_fill_int_urb(mixer->urb, mixer->chip->dev, usb_rcvintpipe(mixer->chip->dev, epnum), transfer_buffer, buffer_length, - snd_usb_mixer_status_complete, mixer, ep->bInterval); + snd_usb_mixer_interrupt, mixer, ep->bInterval); usb_submit_urb(mixer->urb, GFP_KERNEL); return 0; } -- cgit v1.2.3-18-g5258 From 36aeff6146925025033e2bcd45fa1e9725bc4599 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 12 May 2010 10:35:36 +0300 Subject: ASoC: TWL4030: Add control for digimic Left Right swap The codec has support for swapping the left and right channels in the digimic interface. New kcontrol to handle this bit. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 0fe74d1e2c5..6a34f562b56 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1102,6 +1102,16 @@ static const struct soc_enum twl4030_vibradir_enum = ARRAY_SIZE(twl4030_vibradir_texts), twl4030_vibradir_texts); +/* Digimic Left and right swapping */ +static const char *twl4030_digimicswap_texts[] = { + "Not swapped", "Swapped", +}; + +static const struct soc_enum twl4030_digimicswap_enum = + SOC_ENUM_SINGLE(TWL4030_REG_MISC_SET_1, 0, + ARRAY_SIZE(twl4030_digimicswap_texts), + twl4030_digimicswap_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Codec operation mode control */ SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum, @@ -1178,6 +1188,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum), + + SOC_ENUM("Digimic LR Swap", twl4030_digimicswap_enum), }; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { -- cgit v1.2.3-18-g5258 From d98508a121e8f4b1ccf876fea463fa0afddc4e19 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Thu, 13 May 2010 19:48:16 +0400 Subject: OMAP: McBSP: Add 32-bit mode support This patchs should allow to use 32-bit samples on e.g. TLV320AIC3x codec, or others. Signed-off-by: Sergey Lapin Acked-by: Mark Brown Acked-by: Peter Ujfalusi Signed-off-by: Liam Girdwood --- sound/soc/omap/omap-mcbsp.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 2d33a89f147..6f44cb4d30b 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -320,8 +320,18 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode; - omap_mcbsp_dai_dma_params[id][substream->stream].data_type = - OMAP_DMA_DATA_TYPE_S16; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + omap_mcbsp_dai_dma_params[id][substream->stream].data_type = + OMAP_DMA_DATA_TYPE_S16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + omap_mcbsp_dai_dma_params[id][substream->stream].data_type = + OMAP_DMA_DATA_TYPE_S32; + break; + default: + return -EINVAL; + } snd_soc_dai_set_dma_data(cpu_dai, substream, &omap_mcbsp_dai_dma_params[id][substream->stream]); @@ -356,6 +366,14 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16); regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16); break; + case SNDRV_PCM_FORMAT_S32_LE: + /* Set word lengths */ + wlen = 32; + regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32); + regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32); + regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32); + regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32); + break; default: /* Unsupported PCM format */ return -EINVAL; @@ -659,13 +677,15 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .channels_min = 1, \ .channels_max = 16, \ .rates = OMAP_MCBSP_RATES, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .capture = { \ .channels_min = 1, \ .channels_max = 16, \ .rates = OMAP_MCBSP_RATES, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .ops = &omap_mcbsp_dai_ops, \ .private_data = &mcbsp_data[(link_id)].bus_id, \ -- cgit v1.2.3-18-g5258 From 9319b9da35179e11aa9c15d80062d63b36a0517f Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Wed, 12 May 2010 12:18:39 -0500 Subject: ARM: McBSP: Fix request for irq in OMAP4 In OMAP4, there is only one irq line for TX and RX paths. Use the correct irq line to avoid errors at runtime. Also, request irq line only once (instead of requesting for TX and RX). Signed-off-by: Jorge Eduardo Candelaria Acked-by: Jarkko Nikula Acked-by: Mark Brown Acked-by: Tony Lindgren Signed-off-by: Liam Girdwood --- arch/arm/mach-omap2/mcbsp.c | 12 ++++-------- arch/arm/plat-omap/mcbsp.c | 20 ++++++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/arch/arm/mach-omap2/mcbsp.c b/arch/arm/mach-omap2/mcbsp.c index be8fce395a5..016fe60c71a 100644 --- a/arch/arm/mach-omap2/mcbsp.c +++ b/arch/arm/mach-omap2/mcbsp.c @@ -186,32 +186,28 @@ static struct omap_mcbsp_platform_data omap44xx_mcbsp_pdata[] = { .phys_base = OMAP44XX_MCBSP1_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP1_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP1_TX, - .rx_irq = INT_24XX_MCBSP1_IRQ_RX, - .tx_irq = INT_24XX_MCBSP1_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP1, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP2_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP2_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP2_TX, - .rx_irq = INT_24XX_MCBSP2_IRQ_RX, - .tx_irq = INT_24XX_MCBSP2_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP2, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP3_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP3_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP3_TX, - .rx_irq = INT_24XX_MCBSP3_IRQ_RX, - .tx_irq = INT_24XX_MCBSP3_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP3, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP4_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP4_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP4_TX, - .rx_irq = INT_24XX_MCBSP4_IRQ_RX, - .tx_irq = INT_24XX_MCBSP4_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP4, .ops = &omap2_mcbsp_ops, }, }; diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index 4ff7a11b183..8bbbb93e212 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -778,14 +778,17 @@ int omap_mcbsp_request(unsigned int id) goto err_clk_disable; } - init_completion(&mcbsp->rx_irq_completion); - err = request_irq(mcbsp->rx_irq, omap_mcbsp_rx_irq_handler, + if (mcbsp->rx_irq) { + init_completion(&mcbsp->rx_irq_completion); + err = request_irq(mcbsp->rx_irq, + omap_mcbsp_rx_irq_handler, 0, "McBSP", (void *)mcbsp); - if (err != 0) { - dev_err(mcbsp->dev, "Unable to request RX IRQ %d " - "for McBSP%d\n", mcbsp->rx_irq, - mcbsp->id); - goto err_free_irq; + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request RX IRQ %d " + "for McBSP%d\n", mcbsp->rx_irq, + mcbsp->id); + goto err_free_irq; + } } } @@ -835,7 +838,8 @@ void omap_mcbsp_free(unsigned int id) if (mcbsp->io_type == OMAP_MCBSP_IRQ_IO) { /* Free IRQs */ - free_irq(mcbsp->rx_irq, (void *)mcbsp); + if (mcbsp->rx_irq) + free_irq(mcbsp->rx_irq, (void *)mcbsp); free_irq(mcbsp->tx_irq, (void *)mcbsp); } -- cgit v1.2.3-18-g5258 From 752ec2f2d97da34abffa1abff81c96183a895b28 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Wed, 12 May 2010 12:18:40 -0500 Subject: ARM: McBSP: Add support for omap4 in McBSP driver McBSP module in OMAP4 needs to be able to set its tx/rx threshold and enable the transmitter/receiver when starting an audio stream. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Acked-by: Tony Lindgren Signed-off-by: Liam Girdwood --- arch/arm/plat-omap/mcbsp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index 8bbbb93e212..4820cabadce 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -488,7 +488,7 @@ void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) { struct omap_mcbsp *mcbsp; - if (!cpu_is_omap34xx()) + if (!cpu_is_omap34xx() && !cpu_is_omap44xx()) return; if (!omap_mcbsp_check_valid_id(id)) { @@ -510,7 +510,7 @@ void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold) { struct omap_mcbsp *mcbsp; - if (!cpu_is_omap34xx()) + if (!cpu_is_omap34xx() && !cpu_is_omap44xx()) return; if (!omap_mcbsp_check_valid_id(id)) { @@ -641,7 +641,7 @@ static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) * Enable wakup behavior, smart idle and all wakeups * REVISIT: some wakeups may be unnecessary */ - if (cpu_is_omap34xx()) { + if (cpu_is_omap34xx() || cpu_is_omap44xx()) { u16 syscon; syscon = MCBSP_READ(mcbsp, SYSCON); @@ -664,7 +664,7 @@ static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) /* * Disable wakup behavior, smart idle and all wakeups */ - if (cpu_is_omap34xx()) { + if (cpu_is_omap34xx() || cpu_is_omap44xx()) { u16 syscon; syscon = MCBSP_READ(mcbsp, SYSCON); @@ -913,7 +913,7 @@ void omap_mcbsp_start(unsigned int id, int tx, int rx) MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 7)); } - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { /* Release the transmitter and receiver */ w = MCBSP_READ_CACHE(mcbsp, XCCR); w &= ~(tx ? XDISABLE : 0); @@ -943,7 +943,7 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) /* Reset transmitter */ tx &= 1; - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { w = MCBSP_READ_CACHE(mcbsp, XCCR); w |= (tx ? XDISABLE : 0); MCBSP_WRITE(mcbsp, XCCR, w); @@ -953,7 +953,7 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) /* Reset receiver */ rx &= 1; - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { w = MCBSP_READ_CACHE(mcbsp, RCCR); w |= (rx ? RDISABLE : 0); MCBSP_WRITE(mcbsp, RCCR, w); -- cgit v1.2.3-18-g5258 From b0fb75ad5c8ca205396d7a493c9be5a5da802747 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 14 May 2010 16:48:28 +0200 Subject: ALSA: es1688: add pedantic range checks Smatch complains that if (dev == SNDRV_CARDS) we're one past the end of the array. That's unlikely to happen in real life, I suppose. Also smatch complains about "strcpy(card->shortname, pcm->name);" The "pcm->name" buffer is 80 characters and "card->shortname" is 32 characters. If you follow the call paths it turns out we never actually use more than 16 characters so it's not a problem. But anyway, let's make it easy for people auditing this in the future. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/isa/es1688/es1688.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index fdcce311f80..0cde8131a57 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -149,10 +149,11 @@ static int __devinit snd_es1688_probe(struct snd_card *card, unsigned int n) if (error < 0) return error; - strcpy(card->driver, "ES1688"); - strcpy(card->shortname, pcm->name); - sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i", pcm->name, - chip->port, chip->irq, chip->dma8); + strlcpy(card->driver, "ES1688", sizeof(card->driver)); + strlcpy(card->shortname, pcm->name, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i, dma %i", pcm->name, chip->port, + chip->irq, chip->dma8); if (fm_port[n] == SNDRV_AUTO_PORT) fm_port[n] = port[n]; /* share the same port */ @@ -271,6 +272,8 @@ static int __devinit snd_es968_pnp_detect(struct pnp_card_link *pcard, if (enable[dev] && isapnp[dev]) break; } + if (dev == SNDRV_CARDS) + return -ENODEV; error = snd_card_create(index[dev], id[dev], THIS_MODULE, sizeof(struct snd_es1688), &card); -- cgit v1.2.3-18-g5258 From 2448b14715bc83d75ed779452fe89285df4d772a Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 14 May 2010 16:49:20 +0200 Subject: ALSA: asihpi: testing the wrong variable There is a typo here. We want to test "*dst" not "dst". Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/pci/asihpi/hpifunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/asihpi/hpifunc.c b/sound/pci/asihpi/hpifunc.c index 254c580db63..15f0c7ea0e5 100644 --- a/sound/pci/asihpi/hpifunc.c +++ b/sound/pci/asihpi/hpifunc.c @@ -3279,7 +3279,7 @@ static u16 hpi_entity_alloc_and_copy(struct hpi_entity *src, buf_size = hpi_entity_size(src); *dst = kmalloc(buf_size, GFP_KERNEL); - if (dst == NULL) + if (*dst == NULL) return HPI_ERROR_MEMORY_ALLOC; memcpy(*dst, src, buf_size); return 0; -- cgit v1.2.3-18-g5258 From 1be1d76b8a1f622fd3a64778dfe117053f2af1f2 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 14 May 2010 16:50:05 +0200 Subject: ALSA: asihpi: incorrect range check The entity_type_to_size[] array has LAST_ENTITY_TYPE (11) number of elements, not LAST_ENTITY_ROLE (17). This only affects the debug output. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/pci/asihpi/hpifunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/asihpi/hpifunc.c b/sound/pci/asihpi/hpifunc.c index 15f0c7ea0e5..eda26b31232 100644 --- a/sound/pci/asihpi/hpifunc.c +++ b/sound/pci/asihpi/hpifunc.c @@ -3407,7 +3407,7 @@ u16 hpi_entity_alloc_and_pack(const enum e_entity_type type, if (hE) return hE; - HPI_DEBUG_ASSERT(role > entity_role_null && type < LAST_ENTITY_ROLE); + HPI_DEBUG_ASSERT(role > entity_role_null && type < LAST_ENTITY_TYPE); bytes_to_copy = entity_type_to_size[type] * item_count; total_size = hpi_entity_header_size(*entity) + bytes_to_copy; -- cgit v1.2.3-18-g5258 From e9abf85fe1fb0cd1da19ec231f2d6873e4af9fbd Mon Sep 17 00:00:00 2001 From: Wu Fengguang Date: Fri, 14 May 2010 16:36:12 +0800 Subject: ALSA: intelhdmi - add dependency on SND_DYNAMIC_MINORS This is necessary to support >=3 HDMI playback devices starting from the CougarPoint codec. Signed-off-by: Wu Fengguang Signed-off-by: Takashi Iwai --- sound/pci/hda/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 567348b05b5..9194c3c1d04 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -145,6 +145,7 @@ config SND_HDA_CODEC_NVHDMI 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 -- cgit v1.2.3-18-g5258 From 41da2e0a01bf13986a561d8a16929d009eb9099c Mon Sep 17 00:00:00 2001 From: Wu Fengguang Date: Fri, 14 May 2010 16:36:13 +0800 Subject: ALSA: intelhdmi - user friendly codec name Use the full chipset codename as codec name. They are more user friendly than the spec abbrs. Signed-off-by: Wu Fengguang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_intelhdmi.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/pci/hda/patch_intelhdmi.c b/sound/pci/hda/patch_intelhdmi.c index 88d035104cc..433224d28b9 100644 --- a/sound/pci/hda/patch_intelhdmi.c +++ b/sound/pci/hda/patch_intelhdmi.c @@ -185,14 +185,14 @@ static int patch_intel_hdmi(struct hda_codec *codec) } static struct hda_codec_preset snd_hda_preset_intelhdmi[] = { - { .id = 0x808629fb, .name = "G45 DEVCL", .patch = patch_intel_hdmi }, - { .id = 0x80862801, .name = "G45 DEVBLC", .patch = patch_intel_hdmi }, - { .id = 0x80862802, .name = "G45 DEVCTG", .patch = patch_intel_hdmi }, - { .id = 0x80862803, .name = "G45 DEVELK", .patch = patch_intel_hdmi }, - { .id = 0x80862804, .name = "G45 DEVIBX", .patch = patch_intel_hdmi }, - { .id = 0x80860054, .name = "Q57 DEVIBX", .patch = patch_intel_hdmi }, - { .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_intel_hdmi }, - {} /* terminator */ +{ .id = 0x808629fb, .name = "Crestline HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80862801, .name = "Bearlake HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80862802, .name = "Cantiga HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80862803, .name = "Eaglelake HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80862804, .name = "IbexPeak HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80860054, .name = "IbexPeak HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_intel_hdmi }, +{} /* terminator */ }; MODULE_ALIAS("snd-hda-codec-id:808629fb"); -- cgit v1.2.3-18-g5258 From e48b00870f269541c28bec8d457904a04545d5e5 Mon Sep 17 00:00:00 2001 From: Wu Fengguang Date: Fri, 14 May 2010 16:36:14 +0800 Subject: ALSA: intelhdmi - add id for the CougarPoint chipset Add id for Intel CougarPoint HDMI audio codec. CougarPoint provides 3 Audio Converters. Increase MAX_HDMI_CVTS/MAX_HDMI_PINS accordingly. Signed-off-by: Wu Fengguang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_intelhdmi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_intelhdmi.c b/sound/pci/hda/patch_intelhdmi.c index 433224d28b9..b81d23e42ac 100644 --- a/sound/pci/hda/patch_intelhdmi.c +++ b/sound/pci/hda/patch_intelhdmi.c @@ -40,7 +40,7 @@ * * The HDA correspondence of pipes/ports are converter/pin nodes. */ -#define MAX_HDMI_CVTS 2 +#define MAX_HDMI_CVTS 3 #define MAX_HDMI_PINS 3 #include "patch_hdmi.c" @@ -48,6 +48,7 @@ static char *intel_hdmi_pcm_names[MAX_HDMI_CVTS] = { "INTEL HDMI 0", "INTEL HDMI 1", + "INTEL HDMI 2", }; /* @@ -191,6 +192,7 @@ static struct hda_codec_preset snd_hda_preset_intelhdmi[] = { { .id = 0x80862803, .name = "Eaglelake HDMI", .patch = patch_intel_hdmi }, { .id = 0x80862804, .name = "IbexPeak HDMI", .patch = patch_intel_hdmi }, { .id = 0x80860054, .name = "IbexPeak HDMI", .patch = patch_intel_hdmi }, +{ .id = 0x80862805, .name = "CougarPoint HDMI", .patch = patch_intel_hdmi }, { .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_intel_hdmi }, {} /* terminator */ }; @@ -200,6 +202,7 @@ MODULE_ALIAS("snd-hda-codec-id:80862801"); MODULE_ALIAS("snd-hda-codec-id:80862802"); MODULE_ALIAS("snd-hda-codec-id:80862803"); MODULE_ALIAS("snd-hda-codec-id:80862804"); +MODULE_ALIAS("snd-hda-codec-id:80862805"); MODULE_ALIAS("snd-hda-codec-id:80860054"); MODULE_ALIAS("snd-hda-codec-id:10951392"); -- cgit v1.2.3-18-g5258 From 3eaead579ebc09d4f4c050c9b6ce6d67337bcb20 Mon Sep 17 00:00:00 2001 From: Wu Fengguang Date: Fri, 14 May 2010 16:36:15 +0800 Subject: ALSA: hdmi - dont fail on extra nodes The number of HDMI nodes is expected to go up in future. So don't fail hard on seeing extra converter/pin nodes. We can still operate safely on the nodes within MAX_HDMI_CVTS/MAX_HDMI_PINS. Signed-off-by: Wu Fengguang Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_hdmi.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 2c2bafbf025..86067ee7863 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -766,7 +766,7 @@ static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid) if (spec->num_pins >= MAX_HDMI_PINS) { snd_printk(KERN_WARNING "HDMI: no space for pin %d\n", pin_nid); - return -EINVAL; + return -E2BIG; } hdmi_present_sense(codec, pin_nid, &spec->sink_eld[spec->num_pins]); @@ -788,7 +788,7 @@ static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t nid) if (spec->num_cvts >= MAX_HDMI_CVTS) { snd_printk(KERN_WARNING "HDMI: no space for converter %d\n", nid); - return -EINVAL; + return -E2BIG; } spec->cvt[spec->num_cvts] = nid; @@ -820,15 +820,13 @@ static int hdmi_parse_codec(struct hda_codec *codec) switch (type) { case AC_WID_AUD_OUT: - if (hdmi_add_cvt(codec, nid) < 0) - return -EINVAL; + hdmi_add_cvt(codec, nid); break; case AC_WID_PIN: caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP))) continue; - if (hdmi_add_pin(codec, nid) < 0) - return -EINVAL; + hdmi_add_pin(codec, nid); break; } } -- cgit v1.2.3-18-g5258 From 550a8b691ca67761bbf382d98fbd81d215f1d7f0 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 13 May 2010 21:58:37 +0200 Subject: ALSA: sound/pci/asihpi: Use kzalloc Use kzalloc rather than the combination of kmalloc and memset. The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @@ expression x,size,flags; statement S; @@ -x = kmalloc(size,flags); +x = kzalloc(size,flags); if (x == NULL) S -memset(x, 0, size); // Signed-off-by: Julia Lawall Signed-off-by: Takashi Iwai --- sound/pci/asihpi/hpi6000.c | 3 +-- sound/pci/asihpi/hpi6205.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c index 9c50931731a..839ecb2e4b6 100644 --- a/sound/pci/asihpi/hpi6000.c +++ b/sound/pci/asihpi/hpi6000.c @@ -448,14 +448,13 @@ static void subsys_create_adapter(struct hpi_message *phm, if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_PCI2040) return; - ao.priv = kmalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); + ao.priv = kzalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); if (!ao.priv) { HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n"); phr->error = HPI_ERROR_MEMORY_ALLOC; return; } - memset(ao.priv, 0, sizeof(struct hpi_hw_obj)); /* create the adapter object based on the resource information */ /*? memcpy(&ao.Pci,&phm->u.s.Resource.r.Pci,sizeof(ao.Pci)); */ ao.pci = *phm->u.s.resource.r.pci; diff --git a/sound/pci/asihpi/hpi6205.c b/sound/pci/asihpi/hpi6205.c index 8df2ff76d94..5e88c1fc2b9 100644 --- a/sound/pci/asihpi/hpi6205.c +++ b/sound/pci/asihpi/hpi6205.c @@ -482,13 +482,12 @@ static void subsys_create_adapter(struct hpi_message *phm, if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_DSP6205) return; - ao.priv = kmalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); + ao.priv = kzalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL); if (!ao.priv) { HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n"); phr->error = HPI_ERROR_MEMORY_ALLOC; return; } - memset(ao.priv, 0, sizeof(struct hpi_hw_obj)); ao.pci = *phm->u.s.resource.r.pci; err = create_adapter_obj(&ao, &os_error_code); -- cgit v1.2.3-18-g5258 From 7fd1d74bfc0ecf3dfa139b47daa7941841724886 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 17 May 2010 14:21:45 +0300 Subject: ASoC: tlv320dac33: Use dev_dbg in dac33_hard_power function Since the cases when the same power state would be set again handled gracefully, we do not need to use dev_warn. Signed-off-by: Felipe Balbi Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 007fe830be4..ad1795a83ac 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -352,7 +352,7 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) /* Safety check */ if (unlikely(power == dac33->chip_power)) { - dev_warn(codec->dev, "Trying to set the same power state: %s\n", + dev_dbg(codec->dev, "Trying to set the same power state: %s\n", power ? "ON" : "OFF"); goto exit; } -- cgit v1.2.3-18-g5258 From 2d4cdd6fc974716555fcbaf7ec1d4dda22784c1b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 17 May 2010 14:21:46 +0300 Subject: ASoC: tlv320dac33: Avoid powering off while in BIAS_OFF Avoid calling the dac33_hard_power when the codec was already in BIAS_OFF state. This could happen in device suspend and module removal time. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index ad1795a83ac..bcf6d934499 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -589,6 +589,9 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec, } break; case SND_SOC_BIAS_OFF: + /* Do not power off, when the codec is already off */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + return 0; ret = dac33_hard_power(codec, 0); if (ret != 0) return ret; -- cgit v1.2.3-18-g5258 From 5e64d6aadd2b8d5d480c3ff402f5dbbd009f58c5 Mon Sep 17 00:00:00 2001 From: Misael Lopez Cruz Date: Mon, 17 May 2010 19:53:10 -0500 Subject: ASoC: SDP4430: Add sdp4430 machine driver Add ASoC support for TI SDP4430. Signed-off-by: Misael Lopez Cruz Signed-off-by: Margarita Olaya Cabrera Signed-off-by: Jorge Eduardo Candelaria Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/Kconfig | 9 ++ sound/soc/omap/Makefile | 2 + sound/soc/omap/sdp4430.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 sound/soc/omap/sdp4430.c diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 83be4a76d2b..d542ea2ff6b 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -98,6 +98,15 @@ config SND_OMAP_SOC_SDP3430 Say Y if you want to add support for SoC audio on Texas Instruments SDP3430. +config SND_OMAP_SOC_SDP4430 + tristate "SoC Audio support for Texas Instruments SDP4430" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_4430SDP + select SND_OMAP_SOC_MCPDM + select SND_SOC_TWL6040 + help + Say Y if you want to add support for SoC audio on Texas Instruments + SDP4430. + config SND_OMAP_SOC_OMAP3_PANDORA tristate "SoC Audio support for OMAP3 Pandora" depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 3a75755f25e..ba9fc650db2 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -17,6 +17,7 @@ snd-soc-omap2evm-objs := omap2evm.o snd-soc-omap3evm-objs := omap3evm.o snd-soc-am3517evm-objs := am3517evm.o snd-soc-sdp3430-objs := sdp3430.o +snd-soc-sdp4430-objs := sdp4430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o snd-soc-zoom2-objs := zoom2.o @@ -31,6 +32,7 @@ obj-$(CONFIG_SND_OMAP_SOC_OMAP2EVM) += snd-soc-omap2evm.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o +obj-$(CONFIG_SND_OMAP_SOC_SDP4430) += snd-soc-sdp4430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c new file mode 100644 index 00000000000..83831ee35d0 --- /dev/null +++ b/sound/soc/omap/sdp4430.c @@ -0,0 +1,228 @@ +/* + * sdp4430.c -- SoC audio for TI OMAP4430 SDP + * + * Author: Misael Lopez Cruz + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mcpdm.h" +#include "omap-mcpdm.h" +#include "omap-pcm.h" +#include "../codecs/twl6040.h" + +static int twl6040_power_mode; + +static int sdp4430_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 *codec_dai = rtd->dai->codec_dai; + int clk_id, freq; + int ret; + + if (twl6040_power_mode) { + clk_id = TWL6040_SYSCLK_SEL_HPPLL; + freq = 38400000; + } else { + clk_id = TWL6040_SYSCLK_SEL_LPPLL; + freq = 32768; + } + + /* set the codec mclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq, + SND_SOC_CLOCK_IN); + if (ret) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } +} + +static struct snd_soc_ops sdp4430_ops = { + .hw_params = sdp4430_hw_params, +}; + +static int sdp4430_get_power_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = twl6040_power_mode; + return 0; +} + +static int sdp4430_set_power_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (twl6040_power_mode == ucontrol->value.integer.value[0]) + return 0; + + twl6040_power_mode = ucontrol->value.integer.value[0]; + + return 1; +} + +static const char *power_texts[] = {"Low-Power", "High-Performance"}; + +static const struct soc_enum sdp4430_enum[] = { + SOC_ENUM_SINGLE_EXT(2, power_texts), +}; + +static const struct snd_kcontrol_new sdp4430_controls[] = { + SOC_ENUM_EXT("TWL6040 Power Mode", sdp4430_enum[0], + sdp4430_get_power_mode, sdp4430_set_power_mode), +}; + +/* SDP4430 machine DAPM */ +static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Main Mic Bias"}, + {"SUBMIC", NULL, "Main Mic Bias"}, + {"Main Mic Bias", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Mic"}, + + /* Headset Stereophone (Headphone): HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, +}; + +static int sdp4430_twl6040_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add SDP4430 specific controls */ + ret = snd_soc_add_controls(codec, sdp4430_controls, + ARRAY_SIZE(sdp4430_controls)); + if (ret) + return ret; + + /* Add SDP4430 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, sdp4430_twl6040_dapm_widgets, + ARRAY_SIZE(sdp4430_twl6040_dapm_widgets)); + if (ret) + return ret; + + /* Set up SDP4430 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* SDP4430 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Stereophone"); + + /* TWL6040 not connected pins */ + snd_soc_dapm_nc_pin(codec, "AFML"); + snd_soc_dapm_nc_pin(codec, "AFMR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link sdp4430_dai = { + .name = "TWL6040", + .stream_name = "TWL6040", + .cpu_dai = &omap_mcpdm_dai, + .codec_dai = &twl6040_dai, + .init = sdp4430_twl6040_init, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_sdp4430 = { + .name = "SDP4430", + .platform = &omap_soc_platform, + .dai_link = &sdp4430_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device sdp4430_snd_devdata = { + .card = &snd_soc_sdp4430, + .codec_dev = &soc_codec_dev_twl6040, +}; + +static struct platform_device *sdp4430_snd_device; + +static int __init sdp4430_soc_init(void) +{ + int ret; + + if (!machine_is_omap_4430sdp()) { + pr_debug("Not SDP4430!\n"); + return -ENODEV; + } + printk(KERN_INFO "SDP4430 SoC init\n"); + + sdp4430_snd_device = platform_device_alloc("soc-audio", -1); + if (!sdp4430_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(sdp4430_snd_device, &sdp4430_snd_devdata); + sdp4430_snd_devdata.dev = &sdp4430_snd_device->dev; + + ret = platform_device_add(sdp4430_snd_device); + if (ret) + goto err; + + /* Codec starts in HP mode */ + twl6040_power_mode = 1; + + return 0; + +err: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(sdp4430_snd_device); + return ret; +} +module_init(sdp4430_soc_init); + +static void __exit sdp4430_soc_exit(void) +{ + platform_device_unregister(sdp4430_snd_device); +} +module_exit(sdp4430_soc_exit); + +MODULE_AUTHOR("Misael Lopez Cruz "); +MODULE_DESCRIPTION("ALSA SoC SDP4430"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3-18-g5258 From 7254e2bddc96c47cb5edd30cfd3e1f12a2df9149 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Tue, 18 May 2010 12:44:17 -0500 Subject: ASoC: SDP4430: Add support for Earphone speaker Enable earphone speaker in sdp4430 machine driver. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/sdp4430.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index 83831ee35d0..3a0c1986612 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -101,6 +101,7 @@ static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { SND_SOC_DAPM_SPK("Ext Spk", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_SPK("Earphone Spk", NULL), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -120,6 +121,9 @@ static const struct snd_soc_dapm_route audio_map[] = { /* Headset Stereophone (Headphone): HSOL, HSOR */ {"Headset Stereophone", NULL, "HSOL"}, {"Headset Stereophone", NULL, "HSOR"}, + + /* Earphone speaker */ + {"Earphone Spk", NULL, "EP"}, }; static int sdp4430_twl6040_init(struct snd_soc_codec *codec) -- cgit v1.2.3-18-g5258 From 871a05a78b4879d768bc2fde4b75439f517e2839 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Tue, 18 May 2010 12:44:18 -0500 Subject: ASoC: TWL6040: Enable earphone path in codec Add control to enable earphone driver in TWL6040 codec. This driver is connected to HSDAC Left. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 2ae442edeb9..af36346ff33 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -432,6 +432,12 @@ static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0); */ static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0); +/* + * EPGAIN volume control: + * from -24 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0); + /* Left analog microphone selection */ static const char *twl6040_amicl_texts[] = {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"}; @@ -479,6 +485,9 @@ static const struct snd_kcontrol_new hfl_driver_switch_controls = static const struct snd_kcontrol_new hfr_driver_switch_controls = SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 4, 1, 0); +static const struct snd_kcontrol_new ep_driver_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0); + static const struct snd_kcontrol_new twl6040_snd_controls[] = { /* Capture gains */ SOC_DOUBLE_TLV("Capture Preamplifier Volume", @@ -491,7 +500,8 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), SOC_DOUBLE_R_TLV("Handsfree Playback Volume", TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), - + SOC_SINGLE_TLV("Earphone Playback Volume", + TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv), }; static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { @@ -507,6 +517,7 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HSOR"), SND_SOC_DAPM_OUTPUT("HFL"), SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("EP"), /* Analog input muxes for the capture amplifiers */ SND_SOC_DAPM_MUX("Analog Left Capture Route", @@ -572,6 +583,10 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_NOPM, 0, 0, &hfr_driver_switch_controls, twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH_E("Earphone Driver", + SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /* Analog playback PGAs */ SND_SOC_DAPM_PGA("HFDAC Left PGA", @@ -607,6 +622,10 @@ static const struct snd_soc_dapm_route intercon[] = { {"HSOL", NULL, "Headset Left Driver"}, {"HSOR", NULL, "Headset Right Driver"}, + /* Earphone playback path */ + {"Earphone Driver", "Switch", "HSDAC Left"}, + {"EP", NULL, "Earphone Driver"}, + /* Handsfree playback path */ {"HFDAC Left Playback", "Switch", "HFDAC Left"}, {"HFDAC Right Playback", "Switch", "HFDAC Right"}, -- cgit v1.2.3-18-g5258 From d8b55d2cd00df4d599985440fd75b38d153bffcb Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 19 May 2010 14:14:51 +0100 Subject: ASoC: sdp4430 - add sdp4430 pcm ops to DAI. Fix build warning about unused ops and add ops to the sdp4430 DAI link. Signed-off-by: Liam Girdwood --- sound/soc/omap/sdp4430.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index 3a0c1986612..4ebbde6b565 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -167,6 +167,7 @@ static struct snd_soc_dai_link sdp4430_dai = { .cpu_dai = &omap_mcpdm_dai, .codec_dai = &twl6040_dai, .init = sdp4430_twl6040_init, + .ops = &sdp4430_ops, }; /* Audio machine driver */ -- cgit v1.2.3-18-g5258 From 266d38c8e3d7f62152b1448fd9a7265f32f32d87 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 19 May 2010 13:55:26 +0300 Subject: ASoC: tpa6130a2: Define output pins with SND_SOC_DAPM_OUTPUT Codec output pin should be defined with SND_SOC_DAPM_OUTPUT as otherwise external widgets doesn't alter the output state. Signed-off-by: Jarkko Nikula Cc: Peter Ujfalusi Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index fa4fa33a51a..89788921280 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -371,8 +371,8 @@ static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { 0, 0, tpa6130a2_supply_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* Outputs */ - SND_SOC_DAPM_HP("TPA6130A2 Headphone Left", NULL), - SND_SOC_DAPM_HP("TPA6130A2 Headphone Right", NULL), + SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Left"), + SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Right"), }; static const struct snd_soc_dapm_route audio_map[] = { -- cgit v1.2.3-18-g5258 From ad8332c1302bcb4f80d593fd3eb477be9d7f5604 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 19 May 2010 10:52:28 +0300 Subject: ASoC: tpa6130a2: Remove CPVSS and HPVdd supplies These pins are for decoupling capacitors for the internal charge pumps in TPA6130A2 and TPA6140A2 and not for connecting external supply. Thanks to Eduardo Valentin for pointing out the issue with TPA6130A2 and Ilkka Koskinen with TPA6140A2. Signed-off-by: Jarkko Nikula Acked-by: Peter Ujfalusi Reviewed-by: Ilkka Koskinen Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 52 +++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 89788921280..20ac6770039 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -35,22 +35,11 @@ static struct i2c_client *tpa6130a2_client; -#define TPA6130A2_NUM_SUPPLIES 2 -static const char *tpa6130a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { - "CPVSS", - "Vdd", -}; - -static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { - "HPVdd", - "AVdd", -}; - /* This struct is used to save the context */ struct tpa6130a2_data { struct mutex mutex; unsigned char regs[TPA6130A2_CACHEREGNUM]; - struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES]; + struct regulator *supply; int power_gpio; unsigned char power_state; enum tpa_model id; @@ -135,11 +124,10 @@ static int tpa6130a2_power(int power) if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 1); - ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), - data->supplies); + ret = regulator_enable(data->supply); if (ret != 0) { dev_err(&tpa6130a2_client->dev, - "Failed to enable supplies: %d\n", ret); + "Failed to enable supply: %d\n", ret); goto exit; } @@ -160,11 +148,10 @@ static int tpa6130a2_power(int power) if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 0); - ret = regulator_bulk_disable(ARRAY_SIZE(data->supplies), - data->supplies); + ret = regulator_disable(data->supply); if (ret != 0) { dev_err(&tpa6130a2_client->dev, - "Failed to disable supplies: %d\n", ret); + "Failed to disable supply: %d\n", ret); goto exit; } @@ -411,7 +398,8 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, struct device *dev; struct tpa6130a2_data *data; struct tpa6130a2_platform_data *pdata; - int i, ret; + const char *regulator; + int ret; dev = &client->dev; @@ -453,25 +441,21 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, } switch (data->id) { + default: + dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", + pdata->id); case TPA6130A2: - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6130a2_supply_names[i]; + regulator = "Vdd"; break; case TPA6140A2: - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6140a2_supply_names[i];; + regulator = "AVdd"; break; - default: - dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", - pdata->id); - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6130a2_supply_names[i]; } - ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), - data->supplies); - if (ret != 0) { - dev_err(dev, "Failed to request supplies: %d\n", ret); + data->supply = regulator_get(dev, regulator); + if (IS_ERR(data->supply)) { + ret = PTR_ERR(data->supply); + dev_err(dev, "Failed to request supply: %d\n", ret); goto err_regulator; } @@ -494,7 +478,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, return 0; err_power: - regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies); + regulator_put(data->supply); err_regulator: if (data->power_gpio >= 0) gpio_free(data->power_gpio); @@ -515,7 +499,7 @@ static int __devexit tpa6130a2_remove(struct i2c_client *client) if (data->power_gpio >= 0) gpio_free(data->power_gpio); - regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies); + regulator_put(data->supply); kfree(data); tpa6130a2_client = NULL; -- cgit v1.2.3-18-g5258 From fbc256692eac29e04cf87e45736d7ff149180a52 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 20 May 2010 10:40:55 +0200 Subject: ALSA: hda: Storage class should be before const qualifier The C99 specification states in section 6.11.5: The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature. Signed-off-by: Tobias Klauser Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 80a183f99eb..53538b0f999 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -14207,7 +14207,7 @@ enum { ALC269_FIXUP_SONY_VAIO, }; -const static struct hda_verb alc269_sony_vaio_fixup_verbs[] = { +static const struct hda_verb alc269_sony_vaio_fixup_verbs[] = { {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREFGRD}, {} }; -- cgit v1.2.3-18-g5258