From 5ce568329e4fcf9e9050bff878f8157ca43bc882 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 12 Dec 2012 23:28:04 -0200 Subject: ASoC: wm8962: Add device tree support Add device tree support. Signed-off-by: Fabio Estevam Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/wm8962.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/wm8962.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/wm8962.txt b/Documentation/devicetree/bindings/sound/wm8962.txt new file mode 100644 index 000000000000..dceb3b1c2bb7 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8962.txt @@ -0,0 +1,16 @@ +WM8962 audio CODEC + +This device supports I2C only. + +Required properties: + + - compatible : "wlf,wm8962" + + - reg : the I2C address of the device. + +Example: + +codec: wm8962@1a { + compatible = "wlf,wm8962"; + reg = <0x1a>; +}; -- cgit v1.2.3 From fd23fb9f6bfd43a6e62b2646d18d5ca3edc3ebe3 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Mon, 10 Dec 2012 10:30:04 +0100 Subject: ALSA: ASoC: cs4271: add optional soft reset workaround The CS4271 requires its LRCLK and MCLK to be stable before its RESET line is de-asserted. That also means that clocks cannot be changed without putting the chip back into hardware reset, which also requires a complete re-initialization of all registers. One (undocumented) workaround is to assert and de-assert the PDN bit in the MODE2 register. This patch adds a new flag to both the DT bindings as well as to the platform data to enable that workaround. Signed-off-by: Daniel Mack Acked-by: Alexander Sverdlin Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/cs4271.txt | 12 ++++++++ include/sound/cs4271.h | 15 ++++++++++ sound/soc/codecs/cs4271.c | 34 ++++++++++++++++++++++ 3 files changed, 61 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/cs4271.txt b/Documentation/devicetree/bindings/sound/cs4271.txt index a850fb9c88ea..e2cd1d7539e5 100644 --- a/Documentation/devicetree/bindings/sound/cs4271.txt +++ b/Documentation/devicetree/bindings/sound/cs4271.txt @@ -20,6 +20,18 @@ Optional properties: !RESET pin - cirrus,amuteb-eq-bmutec: When given, the Codec's AMUTEB=BMUTEC flag is enabled. + - cirrus,enable-soft-reset: + The CS4271 requires its LRCLK and MCLK to be stable before its RESET + line is de-asserted. That also means that clocks cannot be changed + without putting the chip back into hardware reset, which also requires + a complete re-initialization of all registers. + + One (undocumented) workaround is to assert and de-assert the PDN bit + in the MODE2 register. This workaround can be enabled with this DT + property. + + Note that this is not needed in case the clocks are stable + throughout the entire runtime of the codec. Examples: diff --git a/include/sound/cs4271.h b/include/sound/cs4271.h index dd8c48d14ed9..70f45355acaa 100644 --- a/include/sound/cs4271.h +++ b/include/sound/cs4271.h @@ -20,6 +20,21 @@ struct cs4271_platform_data { int gpio_nreset; /* GPIO driving Reset pin, if any */ bool amutec_eq_bmutec; /* flag to enable AMUTEC=BMUTEC */ + + /* + * The CS4271 requires its LRCLK and MCLK to be stable before its RESET + * line is de-asserted. That also means that clocks cannot be changed + * without putting the chip back into hardware reset, which also requires + * a complete re-initialization of all registers. + * + * One (undocumented) workaround is to assert and de-assert the PDN bit + * in the MODE2 register. This workaround can be enabled with the + * following flag. + * + * Note that this is not needed in case the clocks are stable + * throughout the entire runtime of the codec. + */ + bool enable_soft_reset; }; #endif /* __CS4271_H */ diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index ac8742a1f25a..2415a4118dbd 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -167,6 +167,8 @@ struct cs4271_private { int gpio_nreset; /* GPIO that disable serial bus, if any */ int gpio_disable; + /* enable soft reset workaround */ + bool enable_soft_reset; }; /* @@ -325,6 +327,33 @@ static int cs4271_hw_params(struct snd_pcm_substream *substream, int i, ret; unsigned int ratio, val; + if (cs4271->enable_soft_reset) { + /* + * Put the codec in soft reset and back again in case it's not + * currently streaming data. This way of bringing the codec in + * sync to the current clocks is not explicitly documented in + * the data sheet, but it seems to work fine, and in contrast + * to a read hardware reset, we don't have to sync back all + * registers every time. + */ + + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !dai->capture_active) || + (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + !dai->playback_active)) { + ret = snd_soc_update_bits(codec, CS4271_MODE2, + CS4271_MODE2_PDN, + CS4271_MODE2_PDN); + if (ret < 0) + return ret; + + ret = snd_soc_update_bits(codec, CS4271_MODE2, + CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + } + } + cs4271->rate = params_rate(params); /* Configure DAC */ @@ -484,6 +513,10 @@ static int cs4271_probe(struct snd_soc_codec *codec) if (of_get_property(codec->dev->of_node, "cirrus,amutec-eq-bmutec", NULL)) amutec_eq_bmutec = true; + + if (of_get_property(codec->dev->of_node, + "cirrus,enable-soft-reset", NULL)) + cs4271->enable_soft_reset = true; } #endif @@ -492,6 +525,7 @@ static int cs4271_probe(struct snd_soc_codec *codec) gpio_nreset = cs4271plat->gpio_nreset; amutec_eq_bmutec = cs4271plat->amutec_eq_bmutec; + cs4271->enable_soft_reset = cs4271plat->enable_soft_reset; } if (gpio_nreset >= 0) -- cgit v1.2.3 From bd0b286e838ef1ca19bbe1cb55f0ec7e0135de1f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 31 Dec 2012 11:51:48 +0100 Subject: ASoC: omap-twl4030: Add support for routing, voice port and jack detect Update the common machine driver to support more boards including Zoom2 and SDP3430. - Support for voice port of twl4030 - HS jack plug detection support - The audio routing can be fine tuned via pdata or via provided routing table from DT. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/omap-twl4030.txt | 46 +++++ sound/soc/omap/Kconfig | 2 + sound/soc/omap/omap-twl4030.c | 204 ++++++++++++++++++++- 3 files changed, 250 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/omap-twl4030.txt b/Documentation/devicetree/bindings/sound/omap-twl4030.txt index 6fae51c7f766..1ab6bc8404d5 100644 --- a/Documentation/devicetree/bindings/sound/omap-twl4030.txt +++ b/Documentation/devicetree/bindings/sound/omap-twl4030.txt @@ -6,6 +6,52 @@ Required properties: - ti,mcbsp: phandle for the McBSP node - ti,codec: phandle for the twl4030 audio node +Optional properties: +- ti,mcbsp-voice: phandle for the McBSP node connected to the voice port of twl +- ti, jack-det-gpio: Jack detect GPIO +- ti,audio-routing: List of connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. + If the routing is not provided all possible connection will be available + +Available audio endpoints for the audio-routing table: + +Board connectors: + * Headset Stereophone + * Earpiece Spk + * Handsfree Spk + * Ext Spk + * Main Mic + * Sub Mic + * Headset Mic + * Carkit Mic + * Digital0 Mic + * Digital1 Mic + * Line In + +twl4030 pins: + * HSOL + * HSOR + * EARPIECE + * HFL + * HFR + * PREDRIVEL + * PREDRIVER + * CARKITL + * CARKITR + * MAINMIC + * SUBMIC + * HSMIC + * DIGIMIC0 + * DIGIMIC1 + * CARKITMIC + * AUXL + * AUXR + + * Headset Mic Bias + * Mic Bias 1 /* Used for Main Mic or Digimic0 */ + * Mic Bias 2 /* Used for Sub Mic or Digimic1 */ + Example: sound { diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 7048137f9a33..e8d2a2f983b5 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -91,6 +91,8 @@ config SND_OMAP_SOC_OMAP_TWL4030 - Gumstix Overo or CompuLab CM-T35/CM-T3730 - IGEP v2 - OMAP3EVM + - SDP3430 + - Zoom2 config SND_OMAP_SOC_OMAP_ABE_TWL6040 tristate "SoC Audio support for OMAP boards using ABE and twl6040 codec" diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c index 4541d28b5314..fd98509d0f49 100644 --- a/sound/soc/omap/omap-twl4030.c +++ b/sound/soc/omap/omap-twl4030.c @@ -11,6 +11,8 @@ * omap3evm (Author: Anuj Aggarwal ) * overo (Author: Steve Sakoman ) * igep0020 (Author: Enric Balletbo i Serra ) + * zoom2 (Author: Misael Lopez Cruz ) + * sdp3430 (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 @@ -32,14 +34,22 @@ #include #include #include +#include +#include #include #include #include +#include #include "omap-mcbsp.h" #include "omap-pcm.h" +struct omap_twl4030 { + int jack_detect; /* board can detect jack events */ + struct snd_soc_jack hs_jack; +}; + static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -87,17 +97,164 @@ static struct snd_soc_ops omap_twl4030_ops = { .hw_params = omap_twl4030_hw_params, }; +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_SPK("Earpiece Spk", NULL), + SND_SOC_DAPM_SPK("Handsfree Spk", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SPK("Carkit Spk", NULL), + + SND_SOC_DAPM_MIC("Main Mic", NULL), + SND_SOC_DAPM_MIC("Sub Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Carkit Mic", NULL), + SND_SOC_DAPM_MIC("Digital0 Mic", NULL), + SND_SOC_DAPM_MIC("Digital1 Mic", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Headset Stereophone: HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + /* External Speakers: HFL, HFR */ + {"Handsfree Spk", NULL, "HFL"}, + {"Handsfree Spk", NULL, "HFR"}, + /* External Speakers: PredrivL, PredrivR */ + {"Ext Spk", NULL, "PREDRIVEL"}, + {"Ext Spk", NULL, "PREDRIVER"}, + /* Carkit speakers: CARKITL, CARKITR */ + {"Carkit Spk", NULL, "CARKITL"}, + {"Carkit Spk", NULL, "CARKITR"}, + /* Earpiece */ + {"Earpiece Spk", NULL, "EARPIECE"}, + + /* External Mics: MAINMIC, SUBMIC with bias */ + {"MAINMIC", NULL, "Main Mic"}, + {"Main Mic", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Sub Mic"}, + {"Sub Mic", NULL, "Mic Bias 2"}, + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "Headset Mic Bias"}, + /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */ + {"DIGIMIC0", NULL, "Digital0 Mic"}, + {"Digital0 Mic", NULL, "Mic Bias 1"}, + {"DIGIMIC1", NULL, "Digital1 Mic"}, + {"Digital1 Mic", NULL, "Mic Bias 2"}, + /* Carkit In: CARKITMIC */ + {"CARKITMIC", NULL, "Carkit Mic"}, + /* Aux In: AUXL, AUXR */ + {"AUXL", NULL, "Line In"}, + {"AUXR", NULL, "Line In"}, +}; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; + +static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, + int connected, char *pin) +{ + if (!connected) + snd_soc_dapm_disable_pin(dapm, pin); +} + +static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); + int ret = 0; + + /* Headset jack detection only if it is supported */ + if (priv->jack_detect > 0) { + hs_jack_gpios[0].gpio = priv->jack_detect; + + ret = snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET, + &priv->hs_jack); + if (ret) + return ret; + + ret = snd_soc_jack_add_pins(&priv->hs_jack, + ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&priv->hs_jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + return ret; + } + + /* + * NULL pdata means we booted with DT. In this case the routing is + * provided and the card is fully routed, no need to mark pins. + */ + if (!pdata || !pdata->custom_routing) + return ret; + + /* Disable not connected paths if not used */ + twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); + twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); + twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); + twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); + twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); + + twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); + twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); + twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); + twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); + twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); + twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); + twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); + + return ret; +} + /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link omap_twl4030_dai_links[] = { { - .name = "TWL4030", - .stream_name = "TWL4030", + .name = "TWL4030 HiFi", + .stream_name = "TWL4030 HiFi", .cpu_dai_name = "omap-mcbsp.2", .codec_dai_name = "twl4030-hifi", .platform_name = "omap-pcm-audio", .codec_name = "twl4030-codec", + .init = omap_twl4030_init, .ops = &omap_twl4030_ops, }, + { + .name = "TWL4030 Voice", + .stream_name = "TWL4030 Voice", + .cpu_dai_name = "omap-mcbsp.3", + .codec_dai_name = "twl4030-voice", + .platform_name = "omap-pcm-audio", + .codec_name = "twl4030-codec", + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM, + }, }; /* Audio machine driver */ @@ -105,6 +262,11 @@ static struct snd_soc_card omap_twl4030_card = { .owner = THIS_MODULE, .dai_link = omap_twl4030_dai_links, .num_links = ARRAY_SIZE(omap_twl4030_dai_links), + + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), }; static int omap_twl4030_probe(struct platform_device *pdev) @@ -112,12 +274,18 @@ static int omap_twl4030_probe(struct platform_device *pdev) struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); struct device_node *node = pdev->dev.of_node; struct snd_soc_card *card = &omap_twl4030_card; + struct omap_twl4030 *priv; int ret = 0; card->dev = &pdev->dev; + priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + if (node) { struct device_node *dai_node; + struct property *prop; if (snd_soc_of_parse_card_name(card, "ti,model")) { dev_err(&pdev->dev, "Card name is not provided\n"); @@ -132,6 +300,27 @@ static int omap_twl4030_probe(struct platform_device *pdev) omap_twl4030_dai_links[0].cpu_dai_name = NULL; omap_twl4030_dai_links[0].cpu_of_node = dai_node; + dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); + if (!dai_node) { + card->num_links = 1; + } else { + omap_twl4030_dai_links[1].cpu_dai_name = NULL; + omap_twl4030_dai_links[1].cpu_of_node = dai_node; + } + + priv->jack_detect = of_get_named_gpio(node, + "ti,jack-det-gpio", 0); + + /* Optional: audio routing can be provided */ + prop = of_find_property(node, "ti,audio-routing", NULL); + if (prop) { + ret = snd_soc_of_parse_audio_routing(card, + "ti,audio-routing"); + if (ret) + return ret; + + card->fully_routed = 1; + } } else if (pdata) { if (pdata->card_name) { card->name = pdata->card_name; @@ -139,11 +328,17 @@ static int omap_twl4030_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Card name is not provided\n"); return -ENODEV; } + + if (!pdata->voice_connected) + card->num_links = 1; + + priv->jack_detect = pdata->jack_detect; } else { dev_err(&pdev->dev, "Missing pdata\n"); return -ENODEV; } + snd_soc_card_set_drvdata(card, priv); ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", @@ -157,7 +352,12 @@ static int omap_twl4030_probe(struct platform_device *pdev) static int omap_twl4030_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); + if (priv->jack_detect > 0) + snd_soc_jack_free_gpios(&priv->hs_jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); snd_soc_unregister_card(card); return 0; -- cgit v1.2.3 From 6ab317419c62850a71e2adfd1573e5ee87d8774f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 9 Jan 2013 11:15:13 +0100 Subject: ALSA: hda - Allow power_save_controller option override DCAPS Change the power_save_controller option to bint from bool so that user can override the runtime PM capability bit and force to enable or disable the runtime PM. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 5 +++-- sound/pci/hda/hda_intel.c | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index b9cfd339a6fa..ce6581c8ca26 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -890,8 +890,9 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. enable_msi - Enable Message Signaled Interrupt (MSI) (default = off) power_save - Automatic power-saving timeout (in second, 0 = disable) - power_save_controller - Reset HD-audio controller in power-saving mode - (default = on) + power_save_controller - Support runtime D3 of HD-audio controller + (-1 = on for supported chip (default), false = off, + true = force to on even for unsupported hardware) align_buffer_size - Force rounding of buffer/period sizes to multiples of 128 bytes. This is more efficient in terms of memory access but isn't required by the HDA spec and prevents diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index cca87277baf0..988323577834 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -134,8 +134,8 @@ MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " * this may give more power-saving, but will take longer time to * wake up. */ -static bool power_save_controller = 1; -module_param(power_save_controller, bool, 0644); +static int power_save_controller = -1; +module_param(power_save_controller, bint, 0644); MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode."); #endif /* CONFIG_PM */ @@ -2711,6 +2711,8 @@ static int azx_runtime_idle(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data; + if (power_save_controller > 0) + return 0; if (!power_save_controller || !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) return -EBUSY; -- cgit v1.2.3 From bbf1453e28e4e3ee2cf5a0c34a20469b4d465f0f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 10 Jan 2013 00:29:11 -0800 Subject: ASoC: ak4642: add Device Tree support Support for loading the ak4642 codec module via devicetree. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ak4642.txt | 17 +++++++++++ sound/soc/codecs/ak4642.c | 33 ++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/ak4642.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/ak4642.txt b/Documentation/devicetree/bindings/sound/ak4642.txt new file mode 100644 index 000000000000..623d4e70ae11 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ak4642.txt @@ -0,0 +1,17 @@ +AK4642 I2C transmitter + +This device supports I2C mode only. + +Required properties: + + - compatible : "asahi-kasei,ak4642" or "asahi-kasei,ak4643" or "asahi-kasei,ak4648" + - reg : The chip select number on the I2C bus + +Example: + +&i2c { + ak4648: ak4648@0x12 { + compatible = "asahi-kasei,ak4642"; + reg = <0x12>; + }; +}; diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 1f0cdab03294..c78794dc4b69 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -513,12 +514,31 @@ static struct snd_soc_codec_driver soc_codec_dev_ak4648 = { }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static struct of_device_id ak4642_of_match[]; static int ak4642_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct device_node *np = i2c->dev.of_node; + const struct snd_soc_codec_driver *driver; + + driver = NULL; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(ak4642_of_match, &i2c->dev); + if (of_id) + driver = of_id->data; + } else { + driver = (struct snd_soc_codec_driver *)id->driver_data; + } + + if (!driver) { + dev_err(&i2c->dev, "no driver\n"); + return -EINVAL; + } + return snd_soc_register_codec(&i2c->dev, - (struct snd_soc_codec_driver *)id->driver_data, - &ak4642_dai, 1); + driver, &ak4642_dai, 1); } static int ak4642_i2c_remove(struct i2c_client *client) @@ -527,6 +547,14 @@ static int ak4642_i2c_remove(struct i2c_client *client) return 0; } +static struct of_device_id ak4642_of_match[] __devinitconst = { + { .compatible = "asahi-kasei,ak4642", .data = &soc_codec_dev_ak4642}, + { .compatible = "asahi-kasei,ak4643", .data = &soc_codec_dev_ak4642}, + { .compatible = "asahi-kasei,ak4648", .data = &soc_codec_dev_ak4648}, + {}, +}; +MODULE_DEVICE_TABLE(of, ak4642_of_match); + static const struct i2c_device_id ak4642_i2c_id[] = { { "ak4642", (kernel_ulong_t)&soc_codec_dev_ak4642 }, { "ak4643", (kernel_ulong_t)&soc_codec_dev_ak4642 }, @@ -539,6 +567,7 @@ static struct i2c_driver ak4642_i2c_driver = { .driver = { .name = "ak4642-codec", .owner = THIS_MODULE, + .of_match_table = ak4642_of_match, }, .probe = ak4642_i2c_probe, .remove = ak4642_i2c_remove, -- cgit v1.2.3 From 609dad9bdf970da0952cea29a4442318cd4a090e Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Sat, 5 Jan 2013 02:18:43 +0100 Subject: ASoC: tegra: add ac97 host driver This adds the driver for the Tegra 2x AC97 host controller. Signed-off-by: Lucas Stach Reviewed-by: Stephen Warren Signed-off-by: Mark Brown --- .../bindings/sound/nvidia,tegra20-ac97.txt | 22 + sound/soc/tegra/Kconfig | 10 + sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra20_ac97.c | 480 +++++++++++++++++++++ sound/soc/tegra/tegra20_ac97.h | 95 ++++ 5 files changed, 609 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra20-ac97.txt create mode 100644 sound/soc/tegra/tegra20_ac97.c create mode 100644 sound/soc/tegra/tegra20_ac97.h (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra20-ac97.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra20-ac97.txt new file mode 100644 index 000000000000..c1454979c1ef --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra20-ac97.txt @@ -0,0 +1,22 @@ +NVIDIA Tegra 20 AC97 controller + +Required properties: +- compatible : "nvidia,tegra20-ac97" +- reg : Should contain AC97 controller registers location and length +- interrupts : Should contain AC97 interrupt +- nvidia,dma-request-selector : The Tegra DMA controller's phandle and + request selector for the AC97 controller +- nvidia,codec-reset-gpio : The Tegra GPIO controller's phandle and the number + of the GPIO used to reset the external AC97 codec +- nvidia,codec-sync-gpio : The Tegra GPIO controller's phandle and the number + of the GPIO corresponding with the AC97 DAP _FS line +Example: + +ac97@70002000 { + compatible = "nvidia,tegra20-ac97"; + reg = <0x70002000 0x200>; + interrupts = <0 81 0x04>; + nvidia,dma-request-selector = <&apbdma 12>; + nvidia,codec-reset-gpio = <&gpio 170 0>; + nvidia,codec-sync-gpio = <&gpio 120 0>; +}; diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 19e5fe7cc403..4b3a2b8cb788 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -6,6 +6,16 @@ config SND_SOC_TEGRA help Say Y or M here if you want support for SoC audio on Tegra. +config SND_SOC_TEGRA20_AC97 + tristate + depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC + select SND_SOC_AC97_BUS + select SND_SOC_TEGRA20_DAS + help + Say Y or M if you want to add support for codecs attached to the + Tegra20 AC97 interface. You will also need to select the individual + machine drivers to support below. + config SND_SOC_TEGRA20_DAS tristate depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 391e78a34c06..02513d9edf22 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -1,6 +1,7 @@ # Tegra platform Support snd-soc-tegra-pcm-objs := tegra_pcm.o snd-soc-tegra-utils-objs += tegra_asoc_utils.o +snd-soc-tegra20-ac97-objs := tegra20_ac97.o snd-soc-tegra20-das-objs := tegra20_das.o snd-soc-tegra20-i2s-objs := tegra20_i2s.o snd-soc-tegra20-spdif-objs := tegra20_spdif.o @@ -9,6 +10,7 @@ snd-soc-tegra30-i2s-objs := tegra30_i2s.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o +obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c new file mode 100644 index 000000000000..1bae73bf1c8a --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.c @@ -0,0 +1,480 @@ +/* + * tegra20_ac97.c - Tegra20 AC97 platform driver + * + * Copyright (c) 2012 Lucas Stach + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tegra_asoc_utils.h" +#include "tegra20_ac97.h" + +#define DRV_NAME "tegra20-ac97" + +static struct tegra20_ac97 *workdata; + +static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* reset line is not driven by DAC pad group, have to toggle GPIO */ + gpio_set_value(workdata->reset_gpio, 0); + udelay(2); + + gpio_set_value(workdata->reset_gpio, 1); + udelay(2); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* + * although sync line is driven by the DAC pad group warm reset using + * the controller cmd is not working, have to toggle sync line + * manually. + */ + gpio_request(workdata->sync_gpio, "codec-sync"); + + gpio_direction_output(workdata->sync_gpio, 1); + + udelay(2); + gpio_set_value(workdata->sync_gpio, 0); + udelay(2); + gpio_free(workdata->sync_gpio); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd, + unsigned short reg) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_STA_VALID1) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); + + return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >> + TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT); +} + +static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd, + unsigned short reg, unsigned short val) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) & + TEGRA20_AC97_CMD_CMD_DATA_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback); + if (!(readback & TEGRA20_AC97_CMD_BUSY)) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = tegra20_ac97_codec_read, + .write = tegra20_ac97_codec_write, + .reset = tegra20_ac97_codec_reset, + .warm_reset = tegra20_ac97_codec_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN); +} + +static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN, 0); +} + +static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN); +} + +static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0); +} + +static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_start_playback(ac97); + else + tegra20_ac97_start_capture(ac97); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_stop_playback(ac97); + else + tegra20_ac97_stop_capture(ac97); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = { + .trigger = tegra20_ac97_trigger, +}; + +static int tegra20_ac97_probe(struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &ac97->capture_dma_data; + dai->playback_dma_data = &ac97->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_driver tegra20_ac97_dai = { + .name = "tegra-ac97-pcm", + .ac97_control = 1, + .probe = tegra20_ac97_probe, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_ac97_dai_ops, +}; + +static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_CTRL: + case TEGRA20_AC97_CMD: + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static const struct regmap_config tegra20_ac97_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_AC97_FIFO_RX1, + .writeable_reg = tegra20_ac97_wr_rd_reg, + .readable_reg = tegra20_ac97_wr_rd_reg, + .volatile_reg = tegra20_ac97_volatile_reg, + .precious_reg = tegra20_ac97_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int tegra20_ac97_platform_probe(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97; + struct resource *mem, *memregion; + u32 of_dma[2]; + void __iomem *regs; + int ret = 0; + + ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97), + GFP_KERNEL); + if (!ac97) { + dev_err(&pdev->dev, "Can't allocate tegra20_ac97\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, ac97); + + ac97->clk_ac97 = clk_get(&pdev->dev, NULL); + if (IS_ERR(ac97->clk_ac97)) { + dev_err(&pdev->dev, "Can't retrieve ac97 clock\n"); + ret = PTR_ERR(ac97->clk_ac97); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_clk_put; + } + + ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_ac97_regmap_config); + if (IS_ERR(ac97->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(ac97->regmap); + goto err_clk_put; + } + + if (of_property_read_u32_array(pdev->dev.of_node, + "nvidia,dma-request-selector", + of_dma, 2) < 0) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-reset-gpio", 0); + if (gpio_is_valid(ac97->reset_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio, + GPIOF_OUT_INIT_HIGH, "codec-reset"); + if (ret) { + dev_err(&pdev->dev, "could not get codec-reset GPIO\n"); + goto err_clk_put; + } + } else { + dev_err(&pdev->dev, "no codec-reset GPIO supplied\n"); + goto err_clk_put; + } + + ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-sync-gpio", 0); + if (!gpio_is_valid(ac97->sync_gpio)) { + dev_err(&pdev->dev, "no codec-sync GPIO supplied\n"); + goto err_clk_put; + } + + ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1; + ac97->capture_dma_data.wrap = 4; + ac97->capture_dma_data.width = 32; + ac97->capture_dma_data.req_sel = of_dma[1]; + + ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1; + ac97->playback_dma_data.wrap = 4; + ac97->playback_dma_data.width = 32; + ac97->playback_dma_data.req_sel = of_dma[1]; + + ret = snd_soc_register_dais(&pdev->dev, &tegra20_ac97_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_clk_put; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dai; + } + + ret = tegra_asoc_utils_init(&ac97->util_data, &pdev->dev); + if (ret) + goto err_unregister_pcm; + + ret = tegra_asoc_utils_set_ac97_rate(&ac97->util_data); + if (ret) + goto err_asoc_utils_fini; + + ret = clk_prepare_enable(ac97->clk_ac97); + if (ret) { + dev_err(&pdev->dev, "clk_enable failed: %d\n", ret); + goto err_asoc_utils_fini; + } + + /* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */ + workdata = ac97; + + return 0; + +err_asoc_utils_fini: + tegra_asoc_utils_fini(&ac97->util_data); +err_unregister_pcm: + tegra_pcm_platform_unregister(&pdev->dev); +err_unregister_dai: + snd_soc_unregister_dai(&pdev->dev); +err_clk_put: + clk_put(ac97->clk_ac97); +err: + return ret; +} + +static int tegra20_ac97_platform_remove(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_dai(&pdev->dev); + + tegra_asoc_utils_fini(&ac97->util_data); + + clk_disable_unprepare(ac97->clk_ac97); + clk_put(ac97->clk_ac97); + + return 0; +} + +static const struct of_device_id tegra20_ac97_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra20-ac97", }, + {}, +}; + +static struct platform_driver tegra20_ac97_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra20_ac97_of_match, + }, + .probe = tegra20_ac97_platform_probe, + .remove = tegra20_ac97_platform_remove, +}; +module_platform_driver(tegra20_ac97_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h new file mode 100644 index 000000000000..dddc6828004e --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.h @@ -0,0 +1,95 @@ +/* + * tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver + * + * Copyright (c) 2012 Lucas Stach + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + * + * 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. + * + */ + +#ifndef __TEGRA20_AC97_H__ +#define __TEGRA20_AC97_H__ + +#include "tegra_pcm.h" + +#define TEGRA20_AC97_CTRL 0x00 +#define TEGRA20_AC97_CMD 0x04 +#define TEGRA20_AC97_STATUS1 0x08 +/* ... */ +#define TEGRA20_AC97_FIFO1_SCR 0x1c +/* ... */ +#define TEGRA20_AC97_FIFO_TX1 0x40 +#define TEGRA20_AC97_FIFO_RX1 0x80 + +/* TEGRA20_AC97_CTRL */ +#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16) +#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11) +#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10) +#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9) +#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8) +#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7) +#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6) +#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5) +#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4) +#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3) +#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2) +#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1) +#define TEGRA20_AC97_CTRL_STM_EN (1 << 0) + +/* TEGRA20_AC97_CMD */ +#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24 +#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) +#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8 +#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) +#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2 +#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT) +#define TEGRA20_AC97_CMD_BUSY (1 << 0) + +/* TEGRA20_AC97_STATUS1 */ +#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24 +#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8 +#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2) +#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1) +#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0) + +/* TEGRA20_AC97_FIFO1_SCR */ +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27 +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22 +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19) +#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18) +#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17) +#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16) +#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15) +#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14) +#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13) +#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12) +#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11) +#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10) +#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9) +#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8) + +struct tegra20_ac97 { + struct clk *clk_ac97; + struct tegra_pcm_dma_params capture_dma_data; + struct tegra_pcm_dma_params playback_dma_data; + struct regmap *regmap; + int reset_gpio; + int sync_gpio; + struct tegra_asoc_utils_data util_data; +}; +#endif /* __TEGRA20_AC97_H__ */ -- cgit v1.2.3 From 9e7b6d60d880a463b17e4eae0d61c9f9a12f22bb Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 10 Jan 2013 00:34:08 -0800 Subject: ASoC: fsi: add device tree support Support for loading the Renesas FSI driver via devicetree. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/renesas,fsi.txt | 26 ++++++++ sound/soc/sh/fsi.c | 71 +++++++++++++++++++--- 2 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/renesas,fsi.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/renesas,fsi.txt b/Documentation/devicetree/bindings/sound/renesas,fsi.txt new file mode 100644 index 000000000000..c5be003f413e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/renesas,fsi.txt @@ -0,0 +1,26 @@ +Renesas FSI + +Required properties: +- compatible : "renesas,sh_fsi2" or "renesas,sh_fsi" +- reg : Should contain the register physical address and length +- interrupts : Should contain FSI interrupt + +- fsia,spdif-connection : FSI is connected by S/PDFI +- fsia,stream-mode-support : FSI supports 16bit stream mode. +- fsia,use-internal-clock : FSI uses internal clock when master mode. + +- fsib,spdif-connection : same as fsia +- fsib,stream-mode-support : same as fsia +- fsib,use-internal-clock : same as fsia + +Example: + +sh_fsi2: sh_fsi2@0xec230000 { + compatible = "renesas,sh_fsi2"; + reg = <0xec230000 0x400>; + interrupts = <0 146 0x4>; + + fsia,spdif-connection; + fsia,stream-mode-support; + fsia,use-internal-clock; +}; diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index ef34ef8e92ed..91576120cd47 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -297,7 +299,7 @@ struct fsi_master { int irq; struct fsi_priv fsia; struct fsi_priv fsib; - struct fsi_core *core; + const struct fsi_core *core; spinlock_t lock; }; @@ -1887,6 +1889,33 @@ static struct snd_soc_platform_driver fsi_soc_platform = { /* * platform function */ +static void fsi_of_parse(char *name, + struct device_node *np, + struct sh_fsi_port_info *info, + struct device *dev) +{ + int i; + char prop[128]; + unsigned long flags = 0; + struct { + char *name; + unsigned int val; + } of_parse_property[] = { + { "spdif-connection", SH_FSI_FMT_SPDIF }, + { "stream-mode-support", SH_FSI_ENABLE_STREAM_MODE }, + { "use-internal-clock", SH_FSI_CLK_CPG }, + }; + + for (i = 0; i < ARRAY_SIZE(of_parse_property); i++) { + sprintf(prop, "%s,%s", name, of_parse_property[i].name); + if (of_get_property(np, prop, NULL)) + flags |= of_parse_property[i].val; + } + info->flags = flags; + + dev_dbg(dev, "%s flags : %lx\n", name, info->flags); +} + static void fsi_port_info_init(struct fsi_priv *fsi, struct sh_fsi_port_info *info) { @@ -1914,22 +1943,40 @@ static void fsi_handler_init(struct fsi_priv *fsi, } } +static struct of_device_id fsi_of_match[]; static int fsi_probe(struct platform_device *pdev) { struct fsi_master *master; - const struct platform_device_id *id_entry; + struct device_node *np = pdev->dev.of_node; struct sh_fsi_platform_info info; + const struct fsi_core *core; struct fsi_priv *fsi; struct resource *res; unsigned int irq; int ret; memset(&info, 0, sizeof(info)); - if (pdev->dev.platform_data) - memcpy(&info, pdev->dev.platform_data, sizeof(info)); - id_entry = pdev->id_entry; - if (!id_entry) { + core = NULL; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(fsi_of_match, &pdev->dev); + if (of_id) { + core = of_id->data; + fsi_of_parse("fsia", np, &info.port_a, &pdev->dev); + fsi_of_parse("fsib", np, &info.port_b, &pdev->dev); + } + } else { + const struct platform_device_id *id_entry = pdev->id_entry; + if (id_entry) + core = (struct fsi_core *)id_entry->driver_data; + + if (pdev->dev.platform_data) + memcpy(&info, pdev->dev.platform_data, sizeof(info)); + } + + if (!core) { dev_err(&pdev->dev, "unknown fsi device\n"); return -ENODEV; } @@ -1956,7 +2003,7 @@ static int fsi_probe(struct platform_device *pdev) /* master setting */ master->irq = irq; - master->core = (struct fsi_core *)id_entry->driver_data; + master->core = core; spin_lock_init(&master->lock); /* FSI A setting */ @@ -1987,7 +2034,7 @@ static int fsi_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, master); ret = devm_request_irq(&pdev->dev, irq, &fsi_interrupt, 0, - id_entry->name, master); + dev_name(&pdev->dev), master); if (ret) { dev_err(&pdev->dev, "irq request err\n"); goto exit_fsib; @@ -2113,6 +2160,13 @@ static struct fsi_core fsi2_core = { .b_mclk = B_MST_CTLR, }; +static struct of_device_id fsi_of_match[] __devinitconst = { + { .compatible = "renesas,sh_fsi", .data = &fsi1_core}, + { .compatible = "renesas,sh_fsi2", .data = &fsi2_core}, + {}, +}; +MODULE_DEVICE_TABLE(of, fsi_of_match); + static struct platform_device_id fsi_id_table[] = { { "sh_fsi", (kernel_ulong_t)&fsi1_core }, { "sh_fsi2", (kernel_ulong_t)&fsi2_core }, @@ -2124,6 +2178,7 @@ static struct platform_driver fsi_driver = { .driver = { .name = "fsi-pcm-audio", .pm = &fsi_pm_ops, + .of_match_table = fsi_of_match, }, .probe = fsi_probe, .remove = fsi_remove, -- cgit v1.2.3 From 6995b8cb9622bf574ac6f309e69288e7d0f76ece Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 16 Jan 2013 13:05:12 +0100 Subject: ASoC: tegra: add tegra machine driver using wm9712 codec This adds a very simple machine driver using the Wolfson wm9712 AC97 codec. Signed-off-by: Lucas Stach Reviewed-by: Stephen Warren Signed-off-by: Mark Brown --- .../bindings/sound/nvidia,tegra-audio-wm9712.txt | 51 ++++++ sound/soc/tegra/Kconfig | 9 ++ sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra_wm9712.c | 176 +++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.txt create mode 100644 sound/soc/tegra/tegra_wm9712.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.txt new file mode 100644 index 000000000000..be35d34e8b26 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.txt @@ -0,0 +1,51 @@ +NVIDIA Tegra audio complex + +Required properties: +- compatible : "nvidia,tegra-audio-wm9712" +- nvidia,model : The user-visible name of this sound complex. +- nvidia,audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names for sources and + sinks are the WM9712's pins, and the jacks on the board: + + WM9712 pins: + + * MONOOUT + * HPOUTL + * HPOUTR + * LOUT2 + * ROUT2 + * OUT3 + * LINEINL + * LINEINR + * PHONE + * PCBEEP + * MIC1 + * MIC2 + * Mic Bias + + Board connectors: + + * Headphone + * LineIn + * Mic + +- nvidia,ac97-controller : The phandle of the Tegra AC97 controller + + +Example: + +sound { + compatible = "nvidia,tegra-audio-wm9712-colibri_t20", + "nvidia,tegra-audio-wm9712"; + nvidia,model = "Toradex Colibri T20"; + + nvidia,audio-routing = + "Headphone", "HPOUTL", + "Headphone", "HPOUTR", + "LineIn", "LINEINL", + "LineIn", "LINEINR", + "Mic", "MIC1"; + + nvidia,ac97-controller = <&ac97>; +}; diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 4b3a2b8cb788..dbc27ce1d4de 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -80,6 +80,15 @@ config SND_SOC_TEGRA_WM8903 boards using the WM8093 codec. Currently, the supported boards are Harmony, Ventana, Seaboard, Kaen, and Aebl. +config SND_SOC_TEGRA_WM9712 + tristate "SoC Audio support for Tegra boards using a WM9712 codec" + depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC + select SND_SOC_TEGRA20_AC97 + select SND_SOC_WM9712 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM9712 (or compatible) codec. + config SND_SOC_TEGRA_TRIMSLICE tristate "SoC Audio support for TrimSlice board" depends on SND_SOC_TEGRA && I2C diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 02513d9edf22..416a14bde41b 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -20,10 +20,12 @@ obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o # Tegra machine Support snd-soc-tegra-wm8753-objs := tegra_wm8753.o snd-soc-tegra-wm8903-objs := tegra_wm8903.o +snd-soc-tegra-wm9712-objs := tegra_wm9712.o snd-soc-tegra-trimslice-objs := trimslice.o snd-soc-tegra-alc5632-objs := tegra_alc5632.o obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o +obj-$(CONFIG_SND_SOC_TEGRA_WM9712) += snd-soc-tegra-wm9712.o obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c new file mode 100644 index 000000000000..cdbd2f0a23bc --- /dev/null +++ b/sound/soc/tegra/tegra_wm9712.c @@ -0,0 +1,176 @@ +/* + * tegra20_wm9712.c - Tegra machine ASoC driver for boards using WM9712 codec. + * + * Copyright 2012 Lucas Stach + * + * Partly based on code copyright/by: + * Copyright 2011,2012 Toradex Inc. + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DRV_NAME "tegra-snd-wm9712" + +struct tegra_wm9712 { + struct platform_device *codec; +}; + +static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + + return snd_soc_dapm_sync(dapm); +} + +static struct snd_soc_dai_link tegra_wm9712_dai = { + .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "tegra-ac97-pcm", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", + .init = tegra_wm9712_init, +}; + +static struct snd_soc_card snd_soc_tegra_wm9712 = { + .name = "tegra-wm9712", + .owner = THIS_MODULE, + .dai_link = &tegra_wm9712_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm9712_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm9712_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm9712; + struct tegra_wm9712 *machine; + int ret; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712), + GFP_KERNEL); + if (!machine) { + dev_err(&pdev->dev, "Can't allocate tegra_wm9712 struct\n"); + return -ENOMEM; + } + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + machine->codec = platform_device_alloc("wm9712-codec", -1); + if (!machine->codec) { + dev_err(&pdev->dev, "Can't allocate wm9712 platform device\n"); + return -ENOMEM; + } + + ret = platform_device_add(machine->codec); + if (ret) + goto codec_put; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto codec_unregister; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto codec_unregister; + + tegra_wm9712_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,ac97-controller", 0); + if (!tegra_wm9712_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,ac97-controller' missing or invalid\n"); + ret = -EINVAL; + goto codec_unregister; + } + + tegra_wm9712_dai.platform_of_node = tegra_wm9712_dai.cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto codec_unregister; + } + + return 0; + +codec_unregister: + platform_device_del(machine->codec); +codec_put: + platform_device_put(machine->codec); + return ret; +} + +static int tegra_wm9712_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm9712 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + platform_device_unregister(machine->codec); + + return 0; +} + +static const struct of_device_id tegra_wm9712_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra-audio-wm9712", }, + {}, +}; + +static struct platform_driver tegra_wm9712_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm9712_of_match, + }, + .probe = tegra_wm9712_driver_probe, + .remove = tegra_wm9712_driver_remove, +}; +module_platform_driver(tegra_wm9712_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra+WM9712 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm9712_of_match); -- cgit v1.2.3 From 40476f61897933d524b7069a6df65629a469d922 Mon Sep 17 00:00:00 2001 From: Padmavathi Venna Date: Fri, 18 Jan 2013 17:17:01 +0530 Subject: ASoC: samsung: Add DT support for i2s Add support for device based discovery. Signed-off-by: Padmavathi Venna Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/samsung-i2s.txt | 63 +++++++ sound/soc/samsung/dma.c | 3 +- sound/soc/samsung/dma.h | 1 + sound/soc/samsung/i2s.c | 209 ++++++++++++++++----- 4 files changed, 230 insertions(+), 46 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/samsung-i2s.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/samsung-i2s.txt b/Documentation/devicetree/bindings/sound/samsung-i2s.txt new file mode 100644 index 000000000000..3070046da2e5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/samsung-i2s.txt @@ -0,0 +1,63 @@ +* Samsung I2S controller + +Required SoC Specific Properties: + +- compatible : "samsung,i2s-v5" +- reg: physical base address of the controller and length of memory mapped + region. +- dmas: list of DMA controller phandle and DMA request line ordered pairs. +- dma-names: identifier string for each DMA request line in the dmas property. + These strings correspond 1:1 with the ordered pairs in dmas. + +Optional SoC Specific Properties: + +- samsung,supports-6ch: If the I2S Primary sound source has 5.1 Channel + support, this flag is enabled. +- samsung,supports-rstclr: This flag should be set if I2S software reset bit + control is required. When this flag is set I2S software reset bit will be + enabled or disabled based on need. +- samsung,supports-secdai:If I2S block has a secondary FIFO and internal DMA, + then this flag is enabled. +- samsung,idma-addr: Internal DMA register base address of the audio + sub system(used in secondary sound source). + +Required Board Specific Properties: + +- gpios: The gpio specifier for data out,data in, LRCLK, CDCLK and SCLK + interface lines. The format of the gpio specifier depends on the gpio + controller. + The syntax of samsung gpio specifier is + <[phandle of the gpio controller node] + [pin number within the gpio controller] + [mux function] + [flags and pull up/down] + [drive strength]> + +Example: + +- SoC Specific Portion: + +i2s@03830000 { + compatible = "samsung,i2s-v5"; + reg = <0x03830000 0x100>; + dmas = <&pdma0 10 + &pdma0 9 + &pdma0 8>; + dma-names = "tx", "rx", "tx-sec"; + samsung,supports-6ch; + samsung,supports-rstclr; + samsung,supports-secdai; + samsung,idma-addr = <0x03000000>; +}; + +- Board Specific Portion: + +i2s@03830000 { + gpios = <&gpz 0 2 0 0>, /* I2S_0_SCLK */ + <&gpz 1 2 0 0>, /* I2S_0_CDCLK */ + <&gpz 2 2 0 0>, /* I2S_0_LRCK */ + <&gpz 3 2 0 0>, /* I2S_0_SDI */ + <&gpz 4 2 0 0>, /* I2S_0_SDO[1] */ + <&gpz 5 2 0 0>, /* I2S_0_SDO[2] */ + <&gpz 6 2 0 0>; /* I2S_0_SDO[3] */ +}; diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index db87628d7630..21b79262010e 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -174,7 +174,8 @@ static int dma_hw_params(struct snd_pcm_substream *substream, config.width = prtd->params->dma_size; config.fifo = prtd->params->dma_addr; prtd->params->ch = prtd->params->ops->request( - prtd->params->channel, &req); + prtd->params->channel, &req, rtd->cpu_dai->dev, + prtd->params->ch_name); prtd->params->ops->config(prtd->params->ch, &config); } diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index 73d8c7c8a1e8..189a7a6d5020 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -19,6 +19,7 @@ struct s3c_dma_params { int dma_size; /* Size of the DMA transfer */ unsigned ch; struct samsung_dma_ops *ops; + char *ch_name; }; int asoc_dma_platform_register(struct device *dev); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 808df74c3248..2fc42f9bf962 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -15,11 +15,15 @@ #include #include #include +#include +#include #include #include #include +#include + #include #include "dma.h" @@ -34,6 +38,10 @@ enum samsung_dai_type { TYPE_SEC, }; +struct samsung_i2s_dai_data { + int dai_type; +}; + struct i2s_dai { /* Platform device for this DAI */ struct platform_device *pdev; @@ -71,6 +79,7 @@ struct i2s_dai { u32 suspend_i2smod; u32 suspend_i2scon; u32 suspend_i2spsr; + unsigned long gpios[7]; /* i2s gpio line numbers */ }; /* Lock for cross i/f checks */ @@ -1000,19 +1009,76 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) return i2s; } +#ifdef CONFIG_OF +static int samsung_i2s_parse_dt_gpio(struct i2s_dai *i2s) +{ + struct device *dev = &i2s->pdev->dev; + int index, gpio, ret; + + for (index = 0; index < 7; index++) { + gpio = of_get_gpio(dev->of_node, index); + if (!gpio_is_valid(gpio)) { + dev_err(dev, "invalid gpio[%d]: %d\n", index, gpio); + goto free_gpio; + } + + ret = gpio_request(gpio, dev_name(dev)); + if (ret) { + dev_err(dev, "gpio [%d] request failed\n", gpio); + goto free_gpio; + } + i2s->gpios[index] = gpio; + } + return 0; + +free_gpio: + while (--index >= 0) + gpio_free(i2s->gpios[index]); + return -EINVAL; +} + +static void samsung_i2s_dt_gpio_free(struct i2s_dai *i2s) +{ + unsigned int index; + for (index = 0; index < 7; index++) + gpio_free(i2s->gpios[index]); +} +#else +static int samsung_i2s_parse_dt_gpio(struct i2s_dai *dai) +{ + return -EINVAL; +} + +static void samsung_i2s_dt_gpio_free(struct i2s_dai *dai) +{ +} + +#endif + +static const struct of_device_id exynos_i2s_match[]; + static inline int samsung_i2s_get_driver_data(struct platform_device *pdev) { - return platform_get_device_id(pdev)->driver_data; +#ifdef CONFIG_OF + struct samsung_i2s_dai_data *data; + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(exynos_i2s_match, pdev->dev.of_node); + data = (struct samsung_i2s_dai_data *) match->data; + return data->dai_type; + } else +#endif + return platform_get_device_id(pdev)->driver_data; } static int samsung_i2s_probe(struct platform_device *pdev) { - u32 dma_pl_chan, dma_cp_chan, dma_pl_sec_chan; struct i2s_dai *pri_dai, *sec_dai = NULL; - struct s3c_audio_pdata *i2s_pdata; - struct samsung_i2s *i2s_cfg; + struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; + struct samsung_i2s *i2s_cfg = NULL; struct resource *res; - u32 regs_base, quirks; + u32 regs_base, quirks = 0, idma_addr = 0; + struct device_node *np = pdev->dev.of_node; enum samsung_dai_type samsung_dai_type; int ret = 0; @@ -1027,31 +1093,60 @@ static int samsung_i2s_probe(struct platform_device *pdev) return 0; } - i2s_pdata = pdev->dev.platform_data; - if (i2s_pdata == NULL) { - dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); - return -EINVAL; + pri_dai = i2s_alloc_dai(pdev, false); + if (!pri_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); + return -ENOMEM; } - res = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!res) { - dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n"); - return -ENXIO; - } - dma_pl_chan = res->start; + if (!np) { + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, + "Unable to get I2S-TX dma resource\n"); + return -ENXIO; + } + pri_dai->dma_playback.channel = res->start; - res = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (!res) { - dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n"); - return -ENXIO; - } - dma_cp_chan = res->start; + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, + "Unable to get I2S-RX dma resource\n"); + return -ENXIO; + } + pri_dai->dma_capture.channel = res->start; - res = platform_get_resource(pdev, IORESOURCE_DMA, 2); - if (res) - dma_pl_sec_chan = res->start; - else - dma_pl_sec_chan = 0; + if (i2s_pdata == NULL) { + dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); + return -EINVAL; + } + + if (&i2s_pdata->type) + i2s_cfg = &i2s_pdata->type.i2s; + + if (i2s_cfg) { + quirks = i2s_cfg->quirks; + idma_addr = i2s_cfg->idma_addr; + } + } else { + if (of_find_property(np, "samsung,supports-6ch", NULL)) + quirks |= QUIRK_PRI_6CHAN; + + if (of_find_property(np, "samsung,supports-secdai", NULL)) + quirks |= QUIRK_SEC_DAI; + + if (of_find_property(np, "samsung,supports-rstclr", NULL)) + quirks |= QUIRK_NEED_RSTCLR; + + if (of_property_read_u32(np, "samsung,idma-addr", + &idma_addr)) { + if (quirks & QUIRK_SEC_DAI) { + dev_err(&pdev->dev, "idma address is not"\ + "specified"); + return -EINVAL; + } + } + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { @@ -1066,24 +1161,14 @@ static int samsung_i2s_probe(struct platform_device *pdev) } regs_base = res->start; - i2s_cfg = &i2s_pdata->type.i2s; - quirks = i2s_cfg->quirks; - - pri_dai = i2s_alloc_dai(pdev, false); - if (!pri_dai) { - dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); - ret = -ENOMEM; - goto err; - } - pri_dai->dma_playback.dma_addr = regs_base + I2STXD; pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; pri_dai->dma_playback.client = (struct s3c2410_dma_client *)&pri_dai->dma_playback; + pri_dai->dma_playback.ch_name = "tx"; pri_dai->dma_capture.client = (struct s3c2410_dma_client *)&pri_dai->dma_capture; - pri_dai->dma_playback.channel = dma_pl_chan; - pri_dai->dma_capture.channel = dma_cp_chan; + pri_dai->dma_capture.ch_name = "rx"; pri_dai->dma_playback.dma_size = 4; pri_dai->dma_capture.dma_size = 4; pri_dai->base = regs_base; @@ -1102,20 +1187,34 @@ static int samsung_i2s_probe(struct platform_device *pdev) sec_dai->dma_playback.dma_addr = regs_base + I2STXDS; sec_dai->dma_playback.client = (struct s3c2410_dma_client *)&sec_dai->dma_playback; - /* Use iDMA always if SysDMA not provided */ - sec_dai->dma_playback.channel = dma_pl_sec_chan ? : -1; + sec_dai->dma_playback.ch_name = "tx-sec"; + + if (!np) { + res = platform_get_resource(pdev, IORESOURCE_DMA, 2); + if (res) + sec_dai->dma_playback.channel = res->start; + } + sec_dai->dma_playback.dma_size = 4; sec_dai->base = regs_base; sec_dai->quirks = quirks; - sec_dai->idma_playback.dma_addr = i2s_cfg->idma_addr; + sec_dai->idma_playback.dma_addr = idma_addr; sec_dai->pri_dai = pri_dai; pri_dai->sec_dai = sec_dai; } - if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { - dev_err(&pdev->dev, "Unable to configure gpio\n"); - ret = -EINVAL; - goto err; + if (np) { + if (samsung_i2s_parse_dt_gpio(pri_dai)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err; + } + } else { + if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err; + } } snd_soc_register_dai(&pri_dai->pdev->dev, &pri_dai->i2s_dai_drv); @@ -1135,10 +1234,14 @@ static int samsung_i2s_remove(struct platform_device *pdev) { struct i2s_dai *i2s, *other; struct resource *res; + struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; i2s = dev_get_drvdata(&pdev->dev); other = i2s->pri_dai ? : i2s->sec_dai; + if (!i2s_pdata->cfg_gpio && pdev->dev.of_node) + samsung_i2s_dt_gpio_free(i2s->pri_dai); + if (other) { other->pri_dai = NULL; other->sec_dai = NULL; @@ -1170,6 +1273,21 @@ static struct platform_device_id samsung_i2s_driver_ids[] = { }; MODULE_DEVICE_TABLE(platform, samsung-i2s-driver-ids); +#ifdef CONFIG_OF +static struct samsung_i2s_dai_data samsung_i2s_dai_data_array[] = { + [TYPE_PRI] = { TYPE_PRI }, + [TYPE_SEC] = { TYPE_SEC }, +}; + +static const struct of_device_id exynos_i2s_match[] = { + { .compatible = "samsung,i2s-v5", + .data = &samsung_i2s_dai_data_array[TYPE_PRI], + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_i2s_match); +#endif + static struct platform_driver samsung_i2s_driver = { .probe = samsung_i2s_probe, .remove = samsung_i2s_remove, @@ -1177,6 +1295,7 @@ static struct platform_driver samsung_i2s_driver = { .driver = { .name = "samsung-i2s", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(exynos_i2s_match), }, }; -- cgit v1.2.3 From 28a480583361b8e67b0a7f4898180725b71cceec Mon Sep 17 00:00:00 2001 From: Padmavathi Venna Date: Fri, 18 Jan 2013 17:17:06 +0530 Subject: ASoC: SMDK: WM8994: Add device tree support for machine file Add the basic device tree based lookup. Signed-off-by: Padmavathi Venna Signed-off-by: Mark Brown --- .../bindings/sound/samsung,smdk-wm8994.txt | 14 ++++++++++++ arch/arm/boot/dts/exynos5250-smdk5250.dts | 18 ++++++++++++--- arch/arm/boot/dts/exynos5250.dtsi | 6 ++--- sound/soc/samsung/smdk_wm8994.c | 26 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/samsung,smdk-wm8994.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/samsung,smdk-wm8994.txt b/Documentation/devicetree/bindings/sound/samsung,smdk-wm8994.txt new file mode 100644 index 000000000000..4686646fb122 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/samsung,smdk-wm8994.txt @@ -0,0 +1,14 @@ +Samsung SMDK audio complex + +Required properties: +- compatible : "samsung,smdk-wm8994" +- samsung,i2s-controller: The phandle of the Samsung I2S0 controller +- samsung,audio-codec: The phandle of the WM8994 audio codec +Example: + +sound { + compatible = "samsung,smdk-wm8994"; + + samsung,i2s-controller = <&i2s0>; + samsung,audio-codec = <&wm8994>; +}; diff --git a/arch/arm/boot/dts/exynos5250-smdk5250.dts b/arch/arm/boot/dts/exynos5250-smdk5250.dts index 78fee35d09fc..127b8cd1385c 100644 --- a/arch/arm/boot/dts/exynos5250-smdk5250.dts +++ b/arch/arm/boot/dts/exynos5250-smdk5250.dts @@ -49,6 +49,11 @@ compatible = "samsung,s524ad0xd1"; reg = <0x51>; }; + + wm8994: wm8994@1a { + compatible = "wlf,wm8994"; + reg = <0x1a>; + }; }; i2c@121D0000 { @@ -205,17 +210,24 @@ samsung,mfc-l = <0x51000000 0x800000>; }; - i2s@03830000 { + i2s0: i2s@03830000 { gpios = <&gpz 0 2 0 0>, <&gpz 1 2 0 0>, <&gpz 2 2 0 0>, <&gpz 3 2 0 0>, <&gpz 4 2 0 0>, <&gpz 5 2 0 0>, <&gpz 6 2 0 0>; }; - i2s@12D60000 { + i2s1: i2s@12D60000 { status = "disabled"; }; - i2s@12D70000 { + i2s2: i2s@12D70000 { status = "disabled"; }; + + sound { + compatible = "samsung,smdk-wm8994"; + + samsung,i2s-controller = <&i2s0>; + samsung,audio-codec = <&wm8994>; + }; }; diff --git a/arch/arm/boot/dts/exynos5250.dtsi b/arch/arm/boot/dts/exynos5250.dtsi index fe05b60a3984..a320b4ac11dd 100644 --- a/arch/arm/boot/dts/exynos5250.dtsi +++ b/arch/arm/boot/dts/exynos5250.dtsi @@ -269,7 +269,7 @@ #size-cells = <0>; }; - i2s@03830000 { + i2s0: i2s@03830000 { compatible = "samsung,i2s-v5"; reg = <0x03830000 0x100>; dmas = <&pdma0 10 @@ -282,7 +282,7 @@ samsung,idma-addr = <0x03000000>; }; - i2s@12D60000 { + i2s1: i2s@12D60000 { compatible = "samsung,i2s-v5"; reg = <0x12D60000 0x100>; dmas = <&pdma1 12 @@ -290,7 +290,7 @@ dma-names = "tx", "rx"; }; - i2s@12D70000 { + i2s2: i2s@12D70000 { compatible = "samsung,i2s-v5"; reg = <0x12D70000 0x100>; dmas = <&pdma0 12 diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c index cc2f407e9f1b..581ea4a06fc6 100644 --- a/sound/soc/samsung/smdk_wm8994.c +++ b/sound/soc/samsung/smdk_wm8994.c @@ -10,6 +10,7 @@ #include "../codecs/wm8994.h" #include #include +#include /* * Default CFG switch settings to use this driver: @@ -153,9 +154,25 @@ static struct snd_soc_card smdk = { static int smdk_audio_probe(struct platform_device *pdev) { int ret; + struct device_node *np = pdev->dev.of_node; struct snd_soc_card *card = &smdk; card->dev = &pdev->dev; + + if (np) { + smdk_dai[0].cpu_dai_name = NULL; + smdk_dai[0].cpu_of_node = of_parse_phandle(np, + "samsung,i2s-controller", 0); + if (!smdk_dai[0].cpu_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + } + + smdk_dai[0].platform_name = NULL; + smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node; + } + ret = snd_soc_register_card(card); if (ret) @@ -173,10 +190,19 @@ static int smdk_audio_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id samsung_wm8994_of_match[] = { + { .compatible = "samsung,smdk-wm8994", }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match); +#endif /* CONFIG_OF */ + static struct platform_driver smdk_audio_driver = { .driver = { .name = "smdk-audio", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_wm8994_of_match), }, .probe = smdk_audio_probe, .remove = smdk_audio_remove, -- cgit v1.2.3 From 7b2ee291fbd3dbe8079c67fec6382a8ed6c275f7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Jan 2013 09:18:55 +0100 Subject: ALSA: hda - Update documentation Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio.txt | 126 +++++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 25 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index 7813c06a5c71..d4faa63ff352 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -176,14 +176,14 @@ support the automatic probing (yet as of 2.6.28). And, BIOS is often, yes, pretty often broken. It sets up wrong values and screws up the driver. -The preset model is provided basically to overcome such a situation. -When the matching preset model is found in the white-list, the driver -assumes the static configuration of that preset and builds the mixer -elements and PCM streams based on the static information. Thus, if -you have a newer machine with a slightly different PCI SSID from the -existing one, you may have a good chance to re-use the same model. -You can pass the `model` option to specify the preset model instead of -PCI SSID look-up. +The preset model (or recently called as "fix-up") is provided +basically to overcome such a situation. When the matching preset +model is found in the white-list, the driver assumes the static +configuration of that preset with the correct pin setup, etc. +Thus, if you have a newer machine with a slightly different PCI SSID +(or codec SSID) from the existing one, you may have a good chance to +re-use the same model. You can pass the `model` option to specify the +preset model instead of PCI (and codec-) SSID look-up. What `model` option values are available depends on the codec chip. Check your codec chip from the codec proc file (see "Codec Proc-File" @@ -199,17 +199,12 @@ non-working HD-audio hardware is to check HD-audio codec and several different `model` option values. If you have any luck, some of them might suit with your device well. -Some codecs such as ALC880 have a special model option `model=test`. -This configures the driver to provide as many mixer controls as -possible for every single pin feature except for the unsolicited -events (and maybe some other specials). Adjust each mixer element and -try the I/O in the way of trial-and-error until figuring out the whole -I/O pin mappings. +There are a few special model option values: +- when 'nofixup' is passed, the device-specific fixups in the codec + parser are skipped. +- when `generic` is passed, the codec-specific parser is skipped and + only the generic parser is used. -Note that `model=generic` has a special meaning. It means to use the -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. Speaker and Headphone Output ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -387,9 +382,8 @@ init_verbs:: (separated with a space). hints:: Shows / stores hint strings for codec parsers for any use. - Its format is `key = value`. For example, passing `hp_detect = yes` - to IDT/STAC codec parser will result in the disablement of the - headphone detection. + Its format is `key = value`. For example, passing `jack_detect = no` + will disable the jack detection of the machine completely. init_pin_configs:: Shows the initial pin default config values set by BIOS. driver_pin_configs:: @@ -421,6 +415,61 @@ re-configure based on that state, run like below: ------------------------------------------------------------------------ +Hint Strings +~~~~~~~~~~~~ +The codec parser have several switches and adjustment knobs for +matching better with the actual codec or device behavior. Many of +them can be adjusted dynamically via "hints" strings as mentioned in +the section above. For example, by passing `jack_detect = no` string +via sysfs or a patch file, you can disable the jack detection, thus +the codec parser will skip the features like auto-mute or mic +auto-switch. As a boolean value, either `yes`, `no`, `true`, `false`, +`1` or `0` can be passed. + +The generic parser supports the following hints: + +- jack_detect (bool): specify whether the jack detection is available + at all on this machine; default true +- inv_jack_detect (bool): indicates that the jack detection logic is + inverted +- trigger_sense (bool): indicates that the jack detection needs the + explicit call of AC_VERB_SET_PIN_SENSE verb +- inv_eapd (bool): indicates that the EAPD is implemented in the + inverted logic +- pcm_format_first (bool): sets the PCM format before the stream tag + and channel ID +- sticky_stream (bool): keep the PCM format, stream tag and ID as long + as possible; default true +- spdif_status_reset (bool): reset the SPDIF status bits at each time + the SPDIF stream is set up +- pin_amp_workaround (bool): the output pin may have multiple amp + values +- single_adc_amp (bool): ADCs can have only single input amps +- auto_mute (bool): enable/disable the headphone auto-mute feature; + default true +- auto_mic (bool): enable/disable the mic auto-switch feature; default + true +- line_in_auto_switch (bool): enable/disable the line-in auto-switch + feature; default false +- need_dac_fix (bool): limits the DACs depending on the channel count +- primary_hp (bool): probe headphone jacks as the primary outputs; + default true +- multi_cap_vol (bool): provide multiple capture volumes +- inv_dmic_split (bool): provide split internal mic volume/switch for + phase-inverted digital mics +- indep_hp (bool): provide the independent headphone PCM stream and + the corresponding mixer control, if available +- add_stereo_mix_input (bool): add the stereo mix (analog-loopback + mix) to the input mux if available +- add_out_jack_modes (bool): add "xxx Jack Mode" enum controls to each + output jack for allowing to change the headphone amp capability +- add_in_jack_modes (bool): add "xxx Jack Mode" enum controls to each + input jack for allowing to change the mic bias vref +- power_down_unused (bool): power down the unused widgets +- mixer_nid (int): specifies the widget NID of the analog-loopback + mixer + + Early Patching ~~~~~~~~~~~~~~ When CONFIG_SND_HDA_PATCH_LOADER=y is set, you can pass a "patch" as a @@ -445,7 +494,7 @@ A patch file is a plain text file which looks like below: 0x20 0x400 0xff [hint] - hp_detect = yes + jack_detect = no ------------------------------------------------------------------------ The file needs to have a line `[codec]`. The next line should contain @@ -531,6 +580,13 @@ cable is unplugged. Thus, if you hear noises, suspect first the power-saving. See /sys/module/snd_hda_intel/parameters/power_save to check the current value. If it's non-zero, the feature is turned on. +The recent kernel supports the runtime PM for the HD-audio controller +chip, too. It means that the HD-audio controller is also powered up / +down dynamically. The feature is enabled only for certain controller +chips like Intel LynxPoint. You can enable/disable this feature +forcibly by setting `power_save_controller` option, which is also +available at /sys/module/snd_hda_intel/parameters directory. + Tracepoints ~~~~~~~~~~~ @@ -587,8 +643,9 @@ The latest development codes for HD-audio are found on sound git tree: - git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git The master branch or for-next branches can be used as the main -development branches in general while the HD-audio specific patches -are committed in topic/hda branch. +development branches in general while the development for the current +and next kernels are found in for-linus and for-next branches, +respectively. If you are using the latest Linus tree, it'd be better to pull the above GIT tree onto it. If you are using the older kernels, an easy @@ -699,7 +756,11 @@ won't be always updated. For example, the volume values are usually cached in the driver, and thus changing the widget amp value directly via hda-verb won't change the mixer value. -The hda-verb program is found in the ftp directory: +The hda-verb program is included now in alsa-tools: + +- git://git.alsa-project.org/alsa-tools.git + +Also, the old stand-alone package is found in the ftp directory: - ftp://ftp.suse.com/pub/people/tiwai/misc/ @@ -777,3 +838,18 @@ A git repository is available: See README file in the tarball for more details about hda-emu program. + + +hda-jack-retask +~~~~~~~~~~~~~~~ +hda-jack-retask is a user-friendly GUI program to manipulate the +HD-audio pin control for jack retasking. If you have a problem about +the jack assignment, try this program and check whether you can get +useful results. Once when you figure out the proper pin assignment, +it can be fixed either in the driver code statically or via passing a +firmware patch file (see "Early Patching" section). + +The program is included in alsa-tools now: + +- git://git.alsa-project.org/alsa-tools.git + -- cgit v1.2.3 From 04044b819b21826f11f32e11aba54def635d8457 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Tue, 29 Jan 2013 12:56:27 +0100 Subject: ALSA: Documentation: fix some thinkos Signed-off-by: Antonio Ospite Signed-off-by: Takashi Iwai --- Documentation/DocBook/writing-an-alsa-driver.tmpl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/writing-an-alsa-driver.tmpl b/Documentation/DocBook/writing-an-alsa-driver.tmpl index fb32aead5a0b..da2f443ab8ec 100644 --- a/Documentation/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/DocBook/writing-an-alsa-driver.tmpl @@ -871,9 +871,8 @@ This function itself doesn't allocate the data space. The data must be allocated manually beforehand, and its pointer is passed - as the argument. This pointer is used as the - (chip identifier in the above example) - for the instance. + as the argument. This pointer (chip in the + above example) is used as the identifier for the instance. @@ -2304,7 +2303,7 @@ struct _snd_pcm_runtime { SNDRV_PCM_INFO_XXX. Here, at least, you have to specify whether the mmap is supported and which interleaved format is supported. - When the is supported, add the + When the hardware supports mmap, add the SNDRV_PCM_INFO_MMAP flag here. When the hardware supports the interleaved or the non-interleaved formats, SNDRV_PCM_INFO_INTERLEAVED or -- cgit v1.2.3 From d4dab5ab5bd0445420aa090185409bf3ae6ccd37 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Tue, 29 Jan 2013 12:56:28 +0100 Subject: ALSA: Documentation: fix some typos s/PAUSE_PUSE/PAUSE_PUSH/ s/happense/happens/ Signed-off-by: Antonio Ospite Signed-off-by: Takashi Iwai --- Documentation/DocBook/writing-an-alsa-driver.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/writing-an-alsa-driver.tmpl b/Documentation/DocBook/writing-an-alsa-driver.tmpl index da2f443ab8ec..c0781bb1f9b5 100644 --- a/Documentation/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/DocBook/writing-an-alsa-driver.tmpl @@ -2897,7 +2897,7 @@ struct _snd_pcm_runtime { When the pcm supports the pause operation (given in the info - field of the hardware table), the PAUSE_PUSE + field of the hardware table), the PAUSE_PUSH and PAUSE_RELEASE commands must be handled here, too. The former is the command to pause the pcm, and the latter to restart the pcm again. @@ -3084,7 +3084,7 @@ struct _snd_pcm_runtime {
High frequency timer interrupts - This happense when the hardware doesn't generate interrupts + This happens when the hardware doesn't generate interrupts at the period boundary but issues timer interrupts at a fixed timer rate (e.g. es1968 or ymfpci drivers). In this case, you need to check the current hardware -- cgit v1.2.3 From e2e8bfdf61573c98162d1112b971d8d00f00fcf8 Mon Sep 17 00:00:00 2001 From: Hebbar Gururaja Date: Thu, 31 Jan 2013 18:23:04 +0530 Subject: ASoC: tlv320aic3x: Convert mic bias to a supply widget Convert MicBias widgets to supply widget. On tlv320aic3x, Mic bias power on/off shares the same register bits with output mic bias voltage. So, when power on mic bias, we need reclaim it to voltage value. Provide a new platform data so that the micbias voltage can be sent according to board requirement. Now since tlv320aic3x codec driver is DT aware, update dt files and functions to handle this new "micbias-vg" platform data. Because of sharing of bits, when enabling the micbias, voltage also needs to be updated. So use SND_SOC_DAPM_POST_PMU & SND_SOC_DAPM_PRE_PMD macro to create an event to handle this. Since micbias is converted to supply widget, updated machine drivers as well. This change is runtime tested on da850-evm with audio loopback (arecord|aplay) for confirmation. Signed-off-by: Hebbar Gururaja Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/tlv320aic3x.txt | 6 ++ include/sound/tlv320aic3x.h | 10 +++ sound/soc/codecs/tlv320aic3x.c | 83 ++++++++++++++++++++-- sound/soc/codecs/tlv320aic3x.h | 4 ++ sound/soc/davinci/davinci-evm.c | 6 +- sound/soc/omap/n810.c | 4 +- sound/soc/omap/rx51.c | 8 +-- 7 files changed, 106 insertions(+), 15 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/sound/tlv320aic3x.txt b/Documentation/devicetree/bindings/sound/tlv320aic3x.txt index e7b98f41fa5f..f47c3f589fd0 100644 --- a/Documentation/devicetree/bindings/sound/tlv320aic3x.txt +++ b/Documentation/devicetree/bindings/sound/tlv320aic3x.txt @@ -11,6 +11,12 @@ Optional properties: - gpio-reset - gpio pin number used for codec reset - ai3x-gpio-func - - AIC3X_GPIO1 & AIC3X_GPIO2 Functionality +- ai3x-micbias-vg - MicBias Voltage required. + 1 - MICBIAS output is powered to 2.0V, + 2 - MICBIAS output is powered to 2.5V, + 3 - MICBIAS output is connected to AVDD, + If this node is not mentioned or if the value is incorrect, then MicBias + is powered down. Example: diff --git a/include/sound/tlv320aic3x.h b/include/sound/tlv320aic3x.h index ffd9bc793105..9407fd00363b 100644 --- a/include/sound/tlv320aic3x.h +++ b/include/sound/tlv320aic3x.h @@ -46,6 +46,13 @@ enum { AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15 }; +enum aic3x_micbias_voltage { + AIC3X_MICBIAS_OFF = 0, + AIC3X_MICBIAS_2_0V = 1, + AIC3X_MICBIAS_2_5V = 2, + AIC3X_MICBIAS_AVDDV = 3, +}; + struct aic3x_setup_data { unsigned int gpio_func[2]; }; @@ -53,6 +60,9 @@ struct aic3x_setup_data { struct aic3x_pdata { int gpio_reset; /* < 0 if not used */ struct aic3x_setup_data *setup; + + /* Selects the micbias voltage */ + enum aic3x_micbias_voltage micbias_vg; }; #endif diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 5708a973a776..ba82ba2a7133 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -85,6 +85,9 @@ struct aic3x_priv { #define AIC3X_MODEL_33 1 #define AIC3X_MODEL_3007 2 u16 model; + + /* Selects the micbias voltage */ + enum aic3x_micbias_voltage micbias_vg; }; /* @@ -195,6 +198,37 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, return ret; } +/* + * mic bias power on/off share the same register bits with + * output voltage of mic bias. when power on mic bias, we + * need reclaim it to voltage value. + * 0x0 = Powered off + * 0x1 = MICBIAS output is powered to 2.0V, + * 0x2 = MICBIAS output is powered to 2.5V + * 0x3 = MICBIAS output is connected to AVDD + */ +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias voltage to user defined */ + snd_soc_update_bits(codec, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, + aic3x->micbias_vg << MICBIAS_LEVEL_SHIFT); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, 0); + break; + } + return 0; +} + static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" }; static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" }; static const char *aic3x_left_hpcom_mux[] = @@ -596,12 +630,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0), /* Mic Bias */ - SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V", - MICBIAS_CTRL, 6, 3, 1, 0), - SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V", - MICBIAS_CTRL, 6, 3, 2, 0), - SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD", - MICBIAS_CTRL, 6, 3, 3, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias", MICBIAS_CTRL, 6, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), /* Output mixers */ SND_SOC_DAPM_MIXER("Left Line Mixer", SND_SOC_NOPM, 0, 0, @@ -1386,6 +1417,24 @@ static int aic3x_probe(struct snd_soc_codec *codec) if (aic3x->model == AIC3X_MODEL_3007) snd_soc_add_codec_controls(codec, &aic3x_classd_amp_gain_ctrl, 1); + /* set mic bias voltage */ + switch (aic3x->micbias_vg) { + case AIC3X_MICBIAS_2_0V: + case AIC3X_MICBIAS_2_5V: + case AIC3X_MICBIAS_AVDDV: + snd_soc_update_bits(codec, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, + (aic3x->micbias_vg) << MICBIAS_LEVEL_SHIFT); + break; + case AIC3X_MICBIAS_OFF: + /* + * noting to do. target won't enter here. This is just to avoid + * compile time warning "warning: enumeration value + * 'AIC3X_MICBIAS_OFF' not handled in switch" + */ + break; + } + aic3x_add_widgets(codec); list_add(&aic3x->list, &reset_list); @@ -1461,6 +1510,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, struct aic3x_setup_data *ai3x_setup; struct device_node *np = i2c->dev.of_node; int ret; + u32 value; aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { @@ -1474,6 +1524,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, if (pdata) { aic3x->gpio_reset = pdata->gpio_reset; aic3x->setup = pdata->setup; + aic3x->micbias_vg = pdata->micbias_vg; } else if (np) { ai3x_setup = devm_kzalloc(&i2c->dev, sizeof(*ai3x_setup), GFP_KERNEL); @@ -1493,6 +1544,26 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, aic3x->setup = ai3x_setup; } + if (!of_property_read_u32(np, "ai3x-micbias-vg", &value)) { + switch (value) { + case 1 : + aic3x->micbias_vg = AIC3X_MICBIAS_2_0V; + break; + case 2 : + aic3x->micbias_vg = AIC3X_MICBIAS_2_5V; + break; + case 3 : + aic3x->micbias_vg = AIC3X_MICBIAS_AVDDV; + break; + default : + aic3x->micbias_vg = AIC3X_MICBIAS_OFF; + dev_err(&i2c->dev, "Unsuitable MicBias voltage " + "found in DT\n"); + } + } else { + aic3x->micbias_vg = AIC3X_MICBIAS_OFF; + } + } else { aic3x->gpio_reset = -1; } diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index 6db3c41b0163..e521ac3ddde8 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -238,6 +238,10 @@ /* Default input volume */ #define DEFAULT_GAIN 0x20 +/* MICBIAS Control Register */ +#define MICBIAS_LEVEL_SHIFT (6) +#define MICBIAS_LEVEL_MASK (3 << 6) + /* headset detection / button API */ /* The AIC3x supports detection of stereo headsets (GND + left + right signal) diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index d55e6477bff0..484b22c5df5d 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -116,9 +116,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Line Out", NULL, "RLOUT"}, /* Mic connected to (MIC3L | MIC3R) */ - {"MIC3L", NULL, "Mic Bias 2V"}, - {"MIC3R", NULL, "Mic Bias 2V"}, - {"Mic Bias 2V", NULL, "Mic Jack"}, + {"MIC3L", NULL, "Mic Bias"}, + {"MIC3R", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, /* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */ {"LINE1L", NULL, "Line In"}, diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 230b8c144848..ee7cd53aa3ee 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -230,8 +230,8 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Ext Spk", NULL, "LLOUT"}, {"Ext Spk", NULL, "RLOUT"}, - {"DMic Rate 64", NULL, "Mic Bias 2V"}, - {"Mic Bias 2V", NULL, "DMic"}, + {"DMic Rate 64", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "DMic"}, }; static const char *spk_function[] = {"Off", "On"}; diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index d921ddbe3ecb..3cd525748975 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -248,16 +248,16 @@ static const struct snd_soc_dapm_route audio_map[] = { {"FM Transmitter", NULL, "LLOUT"}, {"FM Transmitter", NULL, "RLOUT"}, - {"DMic Rate 64", NULL, "Mic Bias 2V"}, - {"Mic Bias 2V", NULL, "DMic"}, + {"DMic Rate 64", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "DMic"}, }; static const struct snd_soc_dapm_route audio_mapb[] = { {"b LINE2R", NULL, "MONO_LOUT"}, {"Earphone", NULL, "b HPLOUT"}, - {"LINE1L", NULL, "b Mic Bias 2.5V"}, - {"b Mic Bias 2.5V", NULL, "HS Mic"} + {"LINE1L", NULL, "b Mic Bias"}, + {"b Mic Bias", NULL, "HS Mic"} }; static const char *spk_function[] = {"Off", "On"}; -- cgit v1.2.3 From a690a2a1eb32e533e2b2afb1daeef3c4011d47e9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 5 Feb 2013 12:24:22 +0100 Subject: ALSA: Fix wrong description about hw constraints The definitions of hw constraint functions are wrongly placed, and the description about the function is also wrong. hw_rule_channels_by_format actually refines the channels depending on the format, not vice versa. Reported-by: Peter Ujfalusi Signed-off-by: Takashi Iwai --- Documentation/DocBook/writing-an-alsa-driver.tmpl | 39 ++++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/writing-an-alsa-driver.tmpl b/Documentation/DocBook/writing-an-alsa-driver.tmpl index c0781bb1f9b5..c564faab981c 100644 --- a/Documentation/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/DocBook/writing-an-alsa-driver.tmpl @@ -3250,18 +3250,19 @@ struct _snd_pcm_runtime { Example of Hardware Constraints for Channels min < 2) { - fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_LE; - return snd_mask_refine(f, &fmt); + snd_interval_any(&ch); + if (f->bits[0] == SNDRV_PCM_FMTBIT_S16_LE) { + ch.min = ch.max = 1; + ch.integer = 1; + return snd_interval_refine(c, &ch); } return 0; } @@ -3285,27 +3286,27 @@ struct _snd_pcm_runtime { - The rule function is called when an application sets the number of - channels. But an application can set the format before the number of - channels. Thus you also need to define the inverse rule: + The rule function is called when an application sets the PCM + format, and it refines the number of channels accordingly. + But an application may set the number of channels before + setting the format. Thus you also need to define the inverse rule: - Example of Hardware Constraints for Channels + Example of Hardware Constraints for Formats bits[0] == SNDRV_PCM_FMTBIT_S16_LE) { - ch.min = ch.max = 1; - ch.integer = 1; - return snd_interval_refine(c, &ch); + snd_mask_any(&fmt); /* Init the struct */ + if (c->min < 2) { + fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_LE; + return snd_mask_refine(f, &fmt); } return 0; } -- cgit v1.2.3 From 16c5ab1d3a6d1b11ed2966fa33a3a4fecd13a2bc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 5 Feb 2013 14:23:53 +0100 Subject: ALSA: Replace 0 with NULL in writing-an-alsa-driver.tmpl Spotted while correcting the sentences. Signed-off-by: Takashi Iwai --- Documentation/DocBook/writing-an-alsa-driver.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/writing-an-alsa-driver.tmpl b/Documentation/DocBook/writing-an-alsa-driver.tmpl index c564faab981c..bd6fee22c4dd 100644 --- a/Documentation/DocBook/writing-an-alsa-driver.tmpl +++ b/Documentation/DocBook/writing-an-alsa-driver.tmpl @@ -3278,8 +3278,8 @@ struct _snd_pcm_runtime { runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - hw_rule_channels_by_format, 0, SNDRV_PCM_HW_PARAM_FORMAT, - -1); + hw_rule_channels_by_format, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, -1); ]]> @@ -3321,8 +3321,8 @@ struct _snd_pcm_runtime { runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, - hw_rule_format_by_channels, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - -1); + hw_rule_format_by_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); ]]> -- cgit v1.2.3 From d911149625e64ec3cbc92725a2c2c5d940b62ffb Mon Sep 17 00:00:00 2001 From: Fernando Luis Vázquez Cao Date: Tue, 12 Feb 2013 16:49:46 +0900 Subject: ALSA: hda - update documentation for no-primary-hp fixup The problem addressed by this fixup is not specific to Vaio Z, affecting some Vaio all-in-one desktop PCs too. Update the code comments accordingly. Signed-off-by: Fernando Luis Vazquez Cao Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 2 +- sound/pci/hda/patch_realtek.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index 16dfe57f1731..bb8b0dc532b8 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -53,7 +53,7 @@ ALC882/883/885/888/889 acer-aspire-8930g Acer Aspire 8330G/6935G acer-aspire Acer Aspire others inv-dmic Inverted internal mic workaround - no-primary-hp VAIO Z workaround (for fixed speaker DAC) + no-primary-hp VAIO Z/VGC-LN51JGB workaround (for fixed speaker DAC) ALC861/660 ========== diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 9eaa8b163952..48c9d10301b7 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1802,7 +1802,8 @@ static void alc889_fixup_imac91_vref(struct hda_codec *codec, } /* Don't take HP output as primary - * strangely, the speaker output doesn't work on VAIO Z through DAC 0x05 + * Strangely, the speaker output doesn't work on Vaio Z and some Vaio + * all-in-one desktop PCs (for example VGC-LN51JGB) through DAC 0x05 */ static void alc882_fixup_no_primary_hp(struct hda_codec *codec, const struct hda_fixup *fix, int action) -- cgit v1.2.3 From 9727b490e543de956b8ba356e2d5499097d0b7a2 Mon Sep 17 00:00:00 2001 From: Jeeja KP Date: Thu, 14 Feb 2013 16:52:51 +0530 Subject: ALSA: compress: add support for gapless playback this add new API for sound compress to support gapless playback. As noted in Documentation change, we add API to send metadata of encoder and padding delay to DSP. Also add API for indicating EOF and switching to subsequent track Also bump the compress API version Signed-off-by: Jeeja KP Signed-off-by: Vinod Koul Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/compress_offload.txt | 46 +++++++++++++ include/sound/compress_driver.h | 8 +++ include/uapi/sound/compress_offload.h | 31 ++++++++- sound/core/compress_offload.c | 96 +++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/compress_offload.txt b/Documentation/sound/alsa/compress_offload.txt index 90e9b3a11abc..0bcc55155911 100644 --- a/Documentation/sound/alsa/compress_offload.txt +++ b/Documentation/sound/alsa/compress_offload.txt @@ -145,6 +145,52 @@ Modifications include: - Addition of encoding options when required (derived from OpenMAX IL) - Addition of rateControlSupported (missing in OpenMAX AL) +Gapless Playback +================ +When playing thru an album, the decoders have the ability to skip the encoder +delay and padding and directly move from one track content to another. The end +user can perceive this as gapless playback as we dont have silence while +switching from one track to another + +Also, there might be low-intensity noises due to encoding. Perfect gapless is +difficult to reach with all types of compressed data, but works fine with most +music content. The decoder needs to know the encoder delay and encoder padding. +So we need to pass this to DSP. This metadata is extracted from ID3/MP4 headers +and are not present by default in the bitstream, hence the need for a new +interface to pass this information to the DSP. Also DSP and userspace needs to +switch from one track to another and start using data for second track. + +The main additions are: + +- set_metadata +This routine sets the encoder delay and encoder padding. This can be used by +decoder to strip the silence. This needs to be set before the data in the track +is written. + +- set_next_track +This routine tells DSP that metadata and write operation sent after this would +correspond to subsequent track + +- partial drain +This is called when end of file is reached. The userspace can inform DSP that +EOF is reached and now DSP can start skipping padding delay. Also next write +data would belong to next track + +Sequence flow for gapless would be: +- Open +- Get caps / codec caps +- Set params +- Set metadata of the first track +- Fill data of the first track +- Trigger start +- User-space finished sending all, +- Indicaite next track data by sending set_next_track +- Set metadata of the next track +- then call partial_drain to flush most of buffer in DSP +- Fill data of the next track +- DSP switches to second track +(note: order for partial_drain and write for next track can be reversed as well) + Not supported: - Support for VoIP/circuit-switched calls is not the target of this diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h index f2912abacdf3..ff6c74153fa1 100644 --- a/include/sound/compress_driver.h +++ b/include/sound/compress_driver.h @@ -71,6 +71,8 @@ struct snd_compr_runtime { * @runtime: pointer to runtime structure * @device: device pointer * @direction: stream direction, playback/recording + * @metadata_set: metadata set flag, true when set + * @next_track: has userspace signall next track transistion, true when set * @private_data: pointer to DSP private data */ struct snd_compr_stream { @@ -79,6 +81,8 @@ struct snd_compr_stream { struct snd_compr_runtime *runtime; struct snd_compr *device; enum snd_compr_direction direction; + bool metadata_set; + bool next_track; void *private_data; }; @@ -110,6 +114,10 @@ struct snd_compr_ops { struct snd_compr_params *params); int (*get_params)(struct snd_compr_stream *stream, struct snd_codec *params); + int (*set_metadata)(struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata); + int (*get_metadata)(struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata); int (*trigger)(struct snd_compr_stream *stream, int cmd); int (*pointer)(struct snd_compr_stream *stream, struct snd_compr_tstamp *tstamp); diff --git a/include/uapi/sound/compress_offload.h b/include/uapi/sound/compress_offload.h index 05341a43fedf..d630163b9a2e 100644 --- a/include/uapi/sound/compress_offload.h +++ b/include/uapi/sound/compress_offload.h @@ -30,7 +30,7 @@ #include -#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 0) +#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 1) /** * struct snd_compressed_buffer: compressed buffer * @fragment_size: size of buffer fragment in bytes @@ -121,6 +121,27 @@ struct snd_compr_codec_caps { struct snd_codec_desc descriptor[MAX_NUM_CODEC_DESCRIPTORS]; }; +/** + * @SNDRV_COMPRESS_ENCODER_PADDING: no of samples appended by the encoder at the + * end of the track + * @SNDRV_COMPRESS_ENCODER_DELAY: no of samples inserted by the encoder at the + * beginning of the track + */ +enum { + SNDRV_COMPRESS_ENCODER_PADDING = 1, + SNDRV_COMPRESS_ENCODER_DELAY = 2, +}; + +/** + * struct snd_compr_metadata: compressed stream metadata + * @key: key id + * @value: key value + */ +struct snd_compr_metadata { + __u32 key; + __u32 value[8]; +}; + /** * compress path ioctl definitions * SNDRV_COMPRESS_GET_CAPS: Query capability of DSP @@ -145,6 +166,10 @@ struct snd_compr_codec_caps { struct snd_compr_codec_caps) #define SNDRV_COMPRESS_SET_PARAMS _IOW('C', 0x12, struct snd_compr_params) #define SNDRV_COMPRESS_GET_PARAMS _IOR('C', 0x13, struct snd_codec) +#define SNDRV_COMPRESS_SET_METADATA _IOW('C', 0x14,\ + struct snd_compr_metadata) +#define SNDRV_COMPRESS_GET_METADATA _IOWR('C', 0x15,\ + struct snd_compr_metadata) #define SNDRV_COMPRESS_TSTAMP _IOR('C', 0x20, struct snd_compr_tstamp) #define SNDRV_COMPRESS_AVAIL _IOR('C', 0x21, struct snd_compr_avail) #define SNDRV_COMPRESS_PAUSE _IO('C', 0x30) @@ -152,10 +177,14 @@ struct snd_compr_codec_caps { #define SNDRV_COMPRESS_START _IO('C', 0x32) #define SNDRV_COMPRESS_STOP _IO('C', 0x33) #define SNDRV_COMPRESS_DRAIN _IO('C', 0x34) +#define SNDRV_COMPRESS_NEXT_TRACK _IO('C', 0x35) +#define SNDRV_COMPRESS_PARTIAL_DRAIN _IO('C', 0x36) /* * TODO * 1. add mmap support * */ #define SND_COMPR_TRIGGER_DRAIN 7 /*FIXME move this to pcm.h */ +#define SND_COMPR_TRIGGER_NEXT_TRACK 8 +#define SND_COMPR_TRIGGER_PARTIAL_DRAIN 9 #endif diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 2d620688cfb7..c84abc886e90 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -486,6 +486,8 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) goto out; stream->runtime->state = SNDRV_PCM_STATE_SETUP; + stream->metadata_set = false; + stream->next_track = false; } else { return -EPERM; } @@ -517,6 +519,49 @@ out: return retval; } +static int +snd_compr_get_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->get_metadata) + return -ENXIO; + + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->get_metadata(stream, &metadata); + if (retval != 0) + return retval; + + if (copy_to_user((void __user *)arg, &metadata, sizeof(metadata))) + return -EFAULT; + + return 0; +} + +static int +snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->set_metadata) + return -ENXIO; + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->set_metadata(stream, &metadata); + stream->metadata_set = true; + + return retval; +} + static inline int snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg) { @@ -600,6 +645,44 @@ static int snd_compr_drain(struct snd_compr_stream *stream) return retval; } +static int snd_compr_next_track(struct snd_compr_stream *stream) +{ + int retval; + + /* only a running stream can transition to next track */ + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) + return -EPERM; + + /* you can signal next track isf this is intended to be a gapless stream + * and current track metadata is set + */ + if (stream->metadata_set == false) + return -EPERM; + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_NEXT_TRACK); + if (retval != 0) + return retval; + stream->metadata_set = false; + stream->next_track = true; + return 0; +} + +static int snd_compr_partial_drain(struct snd_compr_stream *stream) +{ + int retval; + if (stream->runtime->state == SNDRV_PCM_STATE_PREPARED || + stream->runtime->state == SNDRV_PCM_STATE_SETUP) + return -EPERM; + /* stream can be drained only when next track has been signalled */ + if (stream->next_track == false) + return -EPERM; + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN); + + stream->next_track = false; + return retval; +} + static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) { struct snd_compr_file *data = f->private_data; @@ -629,6 +712,12 @@ static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS): retval = snd_compr_get_params(stream, arg); break; + case _IOC_NR(SNDRV_COMPRESS_SET_METADATA): + retval = snd_compr_set_metadata(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_METADATA): + retval = snd_compr_get_metadata(stream, arg); + break; case _IOC_NR(SNDRV_COMPRESS_TSTAMP): retval = snd_compr_tstamp(stream, arg); break; @@ -650,6 +739,13 @@ static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) case _IOC_NR(SNDRV_COMPRESS_DRAIN): retval = snd_compr_drain(stream); break; + case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN): + retval = snd_compr_partial_drain(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK): + retval = snd_compr_next_track(stream); + break; + } mutex_unlock(&stream->device->lock); return retval; -- cgit v1.2.3