diff options
Diffstat (limited to 'sound/soc')
212 files changed, 16939 insertions, 6854 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 0e9623368ab..7d5d6444a83 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -49,7 +49,6 @@ source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" -source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 534714a1ca4..865e090c806 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,10 +1,14 @@ snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o -snd-soc-core-objs += soc-pcm.o soc-compress.o soc-io.o soc-devres.o +snd-soc-core-objs += soc-pcm.o soc-compress.o soc-io.o soc-devres.o soc-ops.o ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),) snd-soc-core-objs += soc-generic-dmaengine-pcm.o endif +ifneq ($(CONFIG_SND_SOC_AC97_BUS),) +snd-soc-core-objs += soc-ac97.o +endif + obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ obj-$(CONFIG_SND_SOC) += generic/ @@ -26,7 +30,6 @@ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ -obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 27e3fc4a536..fb3878312bf 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -52,12 +52,3 @@ config SND_AT91_SOC_SAM9X5_WM8731 help Say Y if you want to add support for audio SoC on an at91sam9x5 based board that is using WM8731 codec. - -config SND_AT91_SOC_AFEB9260 - tristate "SoC Audio support for AFEB9260 board" - depends on ARCH_AT91 && ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC - select SND_ATMEL_SOC_PDC - select SND_ATMEL_SOC_SSC - select SND_SOC_TLV320AIC23_I2C - help - Say Y here to support sound on AFEB9260 board. diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 5baabc8bde3..466a821da98 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -17,4 +17,3 @@ snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o -obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index f403f399808..b1cc2a4a7fc 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -310,7 +310,10 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, * transmit and receive, so if a value has already * been set, it must match this value. */ - if (ssc_p->cmr_div == 0) + if (ssc_p->dir_mask != + (SSC_DIR_MASK_PLAYBACK | SSC_DIR_MASK_CAPTURE)) + ssc_p->cmr_div = div; + else if (ssc_p->cmr_div == 0) ssc_p->cmr_div = div; else if (div != ssc_p->cmr_div) diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c deleted file mode 100644 index 9579799ace5..00000000000 --- a/sound/soc/atmel/snd-soc-afeb9260.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * afeb9260.c -- SoC audio for AFEB9260 - * - * Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/kernel.h> -#include <linux/clk.h> -#include <linux/platform_device.h> - -#include <linux/atmel-ssc.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> - -#include <asm/mach-types.h> -#include <mach/hardware.h> -#include <linux/gpio.h> - -#include "../codecs/tlv320aic23.h" -#include "atmel-pcm.h" -#include "atmel_ssc_dai.h" - -#define CODEC_CLOCK 12000000 - -static int afeb9260_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int err; - - /* Set the codec system clock for DAC and ADC */ - err = - snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); - - if (err < 0) { - printk(KERN_ERR "can't set codec system clock\n"); - return err; - } - - return err; -} - -static struct snd_soc_ops afeb9260_ops = { - .hw_params = afeb9260_hw_params, -}; - -static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_LINE("Line In", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), -}; - -static const struct snd_soc_dapm_route afeb9260_audio_map[] = { - {"Headphone Jack", NULL, "LHPOUT"}, - {"Headphone Jack", NULL, "RHPOUT"}, - - {"LLINEIN", NULL, "Line In"}, - {"RLINEIN", NULL, "Line In"}, - - {"MICIN", NULL, "Mic Jack"}, -}; - - -/* Digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link afeb9260_dai = { - .name = "TLV320AIC23", - .stream_name = "AIC23", - .cpu_dai_name = "atmel-ssc-dai.0", - .codec_dai_name = "tlv320aic23-hifi", - .platform_name = "atmel_pcm-audio", - .codec_name = "tlv320aic23-codec.0-001a", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | - SND_SOC_DAIFMT_CBM_CFM, - .ops = &afeb9260_ops, -}; - -/* Audio machine driver */ -static struct snd_soc_card snd_soc_machine_afeb9260 = { - .name = "AFEB9260", - .owner = THIS_MODULE, - .dai_link = &afeb9260_dai, - .num_links = 1, - - .dapm_widgets = tlv320aic23_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), - .dapm_routes = afeb9260_audio_map, - .num_dapm_routes = ARRAY_SIZE(afeb9260_audio_map), -}; - -static struct platform_device *afeb9260_snd_device; - -static int __init afeb9260_soc_init(void) -{ - int err; - struct device *dev; - - if (!(machine_is_afeb9260())) - return -ENODEV; - - - afeb9260_snd_device = platform_device_alloc("soc-audio", -1); - if (!afeb9260_snd_device) { - printk(KERN_ERR "ASoC: Platform device allocation failed\n"); - return -ENOMEM; - } - - platform_set_drvdata(afeb9260_snd_device, &snd_soc_machine_afeb9260); - err = platform_device_add(afeb9260_snd_device); - if (err) - goto err1; - - dev = &afeb9260_snd_device->dev; - - return 0; -err1: - platform_device_put(afeb9260_snd_device); - return err; -} - -static void __exit afeb9260_soc_exit(void) -{ - platform_device_unregister(afeb9260_snd_device); -} - -module_init(afeb9260_soc_init); -module_exit(afeb9260_soc_exit); - -MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>"); -MODULE_DESCRIPTION("ALSA SoC for AFEB9260"); -MODULE_LICENSE("GPL"); - diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c index c8a2de103c5..5159a50a45a 100644 --- a/sound/soc/au1x/ac97c.c +++ b/sound/soc/au1x/ac97c.c @@ -205,7 +205,7 @@ static int au1xac97c_dai_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver au1xac97c_dai_driver = { .name = "alchemy-ac97c", - .ac97_control = 1, + .bus_control = true, .probe = au1xac97c_dai_probe, .playback = { .rates = AC97_RATES, diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c index 84f31e1f9d2..c6daec98ff8 100644 --- a/sound/soc/au1x/psc-ac97.c +++ b/sound/soc/au1x/psc-ac97.c @@ -343,7 +343,7 @@ static const struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = { }; static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = { - .ac97_control = 1, + .bus_control = true, .probe = au1xpsc_ac97_probe, .playback = { .rates = AC97_RATES, diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index e82eb373a73..6bf21a6c02e 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -260,7 +260,7 @@ static int bf5xx_ac97_resume(struct snd_soc_dai *dai) #endif static struct snd_soc_dai_driver bfin_ac97_dai = { - .ac97_control = 1, + .bus_control = true, .suspend = bf5xx_ac97_suspend, .resume = bf5xx_ac97_resume, .playback = { diff --git a/sound/soc/blackfin/bf5xx-ad1980.c b/sound/soc/blackfin/bf5xx-ad1980.c index 3450e8f9080..0fa81a523b8 100644 --- a/sound/soc/blackfin/bf5xx-ad1980.c +++ b/sound/soc/blackfin/bf5xx-ad1980.c @@ -46,8 +46,6 @@ #include <linux/gpio.h> #include <asm/portmux.h> -#include "../codecs/ad1980.h" - #include "bf5xx-ac97.h" static struct snd_soc_card bf5xx_board; diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig index 5477c547592..7b7fbcd49e5 100644 --- a/sound/soc/cirrus/Kconfig +++ b/sound/soc/cirrus/Kconfig @@ -36,7 +36,8 @@ config SND_EP93XX_SOC_EDB93XX tristate "SoC Audio support for Cirrus Logic EDB93xx boards" depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A) select SND_EP93XX_SOC_I2S - select SND_SOC_CS4271 + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER help Say Y or M here if you want to add support for I2S audio on the Cirrus Logic EDB93xx boards. diff --git a/sound/soc/cirrus/ep93xx-ac97.c b/sound/soc/cirrus/ep93xx-ac97.c index f30dadf85b9..6b8a366b021 100644 --- a/sound/soc/cirrus/ep93xx-ac97.c +++ b/sound/soc/cirrus/ep93xx-ac97.c @@ -338,7 +338,7 @@ static const struct snd_soc_dai_ops ep93xx_ac97_dai_ops = { static struct snd_soc_dai_driver ep93xx_ac97_dai = { .name = "ep93xx-ac97", .id = 0, - .ac97_control = 1, + .bus_control = true, .probe = ep93xx_ac97_dai_probe, .playback = { .stream_name = "AC97 Playback", diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index a68d1731a8f..883c5778b30 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -50,7 +50,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS42L73 if I2C select SND_SOC_CS4265 if I2C select SND_SOC_CS4270 if I2C - select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER select SND_SOC_CS42XX8_I2C if I2C select SND_SOC_CX20442 if TTY select SND_SOC_DA7210 if I2C @@ -85,7 +86,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_RT5645 if I2C select SND_SOC_RT5651 if I2C select SND_SOC_RT5670 if I2C - select SND_SOC_RT5677 if I2C + select SND_SOC_RT5677 if I2C && SPI_MASTER select SND_SOC_SGTL5000 if I2C select SND_SOC_SI476X if MFD_SI476X_CORE select SND_SOC_SIRF_AUDIO_CODEC @@ -101,6 +102,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C + select SND_SOC_TFA9879 if I2C select SND_SOC_TLV320AIC23_I2C if I2C select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER @@ -109,6 +111,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C + select SND_SOC_TS3A227E if I2C select SND_SOC_TWL4030 if TWL4030_CORE select SND_SOC_TWL6040 if TWL6040_CORE select SND_SOC_UDA134X @@ -223,6 +226,7 @@ config SND_SOC_AD193X_I2C select SND_SOC_AD193X config SND_SOC_AD1980 + select REGMAP_AC97 tristate config SND_SOC_AD73311 @@ -336,7 +340,8 @@ config SND_SOC_CS42L51 tristate config SND_SOC_CS42L51_I2C - tristate + tristate "Cirrus Logic CS42L51 CODEC (I2C)" + depends on I2C select SND_SOC_CS42L51 config SND_SOC_CS42L52 @@ -370,8 +375,19 @@ config SND_SOC_CS4270_VD33_ERRATA depends on SND_SOC_CS4270 config SND_SOC_CS4271 - tristate "Cirrus Logic CS4271 CODEC" - depends on SND_SOC_I2C_AND_SPI + tristate + +config SND_SOC_CS4271_I2C + tristate "Cirrus Logic CS4271 CODEC (I2C)" + depends on I2C + select SND_SOC_CS4271 + select REGMAP_I2C + +config SND_SOC_CS4271_SPI + tristate "Cirrus Logic CS4271 CODEC (SPI)" + depends on SPI_MASTER + select SND_SOC_CS4271 + select REGMAP_SPI config SND_SOC_CS42XX8 tristate @@ -487,7 +503,8 @@ config SND_SOC_RT286 depends on I2C config SND_SOC_RT5631 - tristate + tristate "Realtek ALC5631/RT5631 CODEC" + depends on I2C config SND_SOC_RT5640 tristate @@ -504,6 +521,10 @@ config SND_SOC_RT5670 config SND_SOC_RT5677 tristate +config SND_SOC_RT5677_SPI + tristate + default SND_SOC_RT5677 + #Freescale sgtl5000 codec config SND_SOC_SGTL5000 tristate "Freescale SGTL5000 CODEC" @@ -577,15 +598,21 @@ config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C +config SND_SOC_TFA9879 + tristate "NXP Semiconductors TFA9879 amplifier" + depends on I2C + config SND_SOC_TLV320AIC23 tristate config SND_SOC_TLV320AIC23_I2C - tristate + tristate "Texas Instruments TLV320AIC23 audio CODEC - I2C" + depends on I2C select SND_SOC_TLV320AIC23 config SND_SOC_TLV320AIC23_SPI - tristate + tristate "Texas Instruments TLV320AIC23 audio CODEC - SPI" + depends on SPI_MASTER select SND_SOC_TLV320AIC23 config SND_SOC_TLV320AIC26 @@ -607,6 +634,10 @@ config SND_SOC_TLV320AIC3X config SND_SOC_TLV320DAC33 tristate +config SND_SOC_TS3A227E + tristate "TI Headset/Mic detect and keypress chip" + depends on I2C + config SND_SOC_TWL4030 select MFD_TWL4030_AUDIO tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5dce451661e..bbdfd1e1c18 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -41,6 +41,8 @@ snd-soc-cs42l73-objs := cs42l73.o snd-soc-cs4265-objs := cs4265.o snd-soc-cs4270-objs := cs4270.o snd-soc-cs4271-objs := cs4271.o +snd-soc-cs4271-i2c-objs := cs4271-i2c.o +snd-soc-cs4271-spi-objs := cs4271-spi.o snd-soc-cs42xx8-objs := cs42xx8.o snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o snd-soc-cx20442-objs := cx20442.o @@ -80,6 +82,7 @@ snd-soc-rt5645-objs := rt5645.o snd-soc-rt5651-objs := rt5651.o snd-soc-rt5670-objs := rt5670.o snd-soc-rt5677-objs := rt5677.o +snd-soc-rt5677-spi-objs := rt5677-spi.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o @@ -101,6 +104,7 @@ snd-soc-sta350-objs := sta350.o snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o snd-soc-tas5086-objs := tas5086.o +snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o @@ -109,6 +113,7 @@ snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o +snd-soc-ts3a227e-objs := ts3a227e.o snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o @@ -217,6 +222,8 @@ obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o obj-$(CONFIG_SND_SOC_CS4265) += snd-soc-cs4265.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o +obj-$(CONFIG_SND_SOC_CS4271_I2C) += snd-soc-cs4271-i2c.o +obj-$(CONFIG_SND_SOC_CS4271_SPI) += snd-soc-cs4271-spi.o obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o @@ -256,6 +263,7 @@ obj-$(CONFIG_SND_SOC_RT5645) += snd-soc-rt5645.o obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o +obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o @@ -274,6 +282,7 @@ obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o +obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o @@ -282,6 +291,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o +obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index fd43827bb85..7dfbc9921e9 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -126,13 +126,13 @@ struct ab8500_codec_drvdata_dbg { /* Private data for AB8500 device-driver */ struct ab8500_codec_drvdata { struct regmap *regmap; + struct mutex ctrl_lock; /* Sidetone */ long *sid_fir_values; enum sid_state sid_status; /* ANC */ - struct mutex anc_lock; long *anc_fir_values; long *anc_iir_values; enum anc_state anc_status; @@ -1129,9 +1129,9 @@ static int sid_status_control_get(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(codec->dev); - mutex_lock(&codec->mutex); + mutex_lock(&drvdata->ctrl_lock); ucontrol->value.integer.value[0] = drvdata->sid_status; - mutex_unlock(&codec->mutex); + mutex_unlock(&drvdata->ctrl_lock); return 0; } @@ -1154,7 +1154,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, return -EIO; } - mutex_lock(&codec->mutex); + mutex_lock(&drvdata->ctrl_lock); sidconf = snd_soc_read(codec, AB8500_SIDFIRCONF); if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { @@ -1185,7 +1185,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, drvdata->sid_status = SID_FIR_CONFIGURED; out: - mutex_unlock(&codec->mutex); + mutex_unlock(&drvdata->ctrl_lock); dev_dbg(codec->dev, "%s: Exit\n", __func__); @@ -1198,9 +1198,9 @@ static int anc_status_control_get(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(codec->dev); - mutex_lock(&codec->mutex); + mutex_lock(&drvdata->ctrl_lock); ucontrol->value.integer.value[0] = drvdata->anc_status; - mutex_unlock(&codec->mutex); + mutex_unlock(&drvdata->ctrl_lock); return 0; } @@ -1217,7 +1217,7 @@ static int anc_status_control_put(struct snd_kcontrol *kcontrol, dev_dbg(dev, "%s: Enter.\n", __func__); - mutex_lock(&drvdata->anc_lock); + mutex_lock(&drvdata->ctrl_lock); req = ucontrol->value.integer.value[0]; if (req >= ARRAY_SIZE(enum_anc_state)) { @@ -1244,9 +1244,7 @@ static int anc_status_control_put(struct snd_kcontrol *kcontrol, } snd_soc_dapm_sync(&codec->dapm); - mutex_lock(&codec->mutex); anc_configure(codec, apply_fir, apply_iir); - mutex_unlock(&codec->mutex); if (apply_fir) { if (drvdata->anc_status == ANC_IIR_CONFIGURED) @@ -1265,7 +1263,7 @@ static int anc_status_control_put(struct snd_kcontrol *kcontrol, snd_soc_dapm_sync(&codec->dapm); cleanup: - mutex_unlock(&drvdata->anc_lock); + mutex_unlock(&drvdata->ctrl_lock); if (status < 0) dev_err(dev, "%s: Unable to configure ANC! (status = %d)\n", @@ -1294,14 +1292,15 @@ static int filter_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ab8500_codec_drvdata *drvdata = snd_soc_codec_get_drvdata(codec); struct filter_control *fc = (struct filter_control *)kcontrol->private_value; unsigned int i; - mutex_lock(&codec->mutex); + mutex_lock(&drvdata->ctrl_lock); for (i = 0; i < fc->count; i++) ucontrol->value.integer.value[i] = fc->value[i]; - mutex_unlock(&codec->mutex); + mutex_unlock(&drvdata->ctrl_lock); return 0; } @@ -1310,14 +1309,15 @@ static int filter_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct ab8500_codec_drvdata *drvdata = snd_soc_codec_get_drvdata(codec); struct filter_control *fc = (struct filter_control *)kcontrol->private_value; unsigned int i; - mutex_lock(&codec->mutex); + mutex_lock(&drvdata->ctrl_lock); for (i = 0; i < fc->count; i++) fc->value[i] = ucontrol->value.integer.value[i]; - mutex_unlock(&codec->mutex); + mutex_unlock(&drvdata->ctrl_lock); return 0; } @@ -2545,7 +2545,7 @@ static int ab8500_codec_probe(struct snd_soc_codec *codec) (void)snd_soc_dapm_disable_pin(&codec->dapm, "ANC Configure Input"); - mutex_init(&drvdata->anc_lock); + mutex_init(&drvdata->ctrl_lock); return status; } diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index bd9b1839c8b..c6e5a313ebf 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -37,10 +37,11 @@ static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; - return snd_ac97_set_rate(codec->ac97, reg, substream->runtime->rate); + return snd_ac97_set_rate(ac97, reg, substream->runtime->rate); } #define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ @@ -53,7 +54,6 @@ static const struct snd_soc_dai_ops ac97_dai_ops = { static struct snd_soc_dai_driver ac97_dai = { .name = "ac97-hifi", - .ac97_control = 1, .playback = { .stream_name = "AC97 Playback", .channels_min = 1, @@ -71,6 +71,7 @@ static struct snd_soc_dai_driver ac97_dai = { static int ac97_soc_probe(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97; struct snd_ac97_bus *ac97_bus; struct snd_ac97_template ac97_template; int ret; @@ -82,24 +83,31 @@ static int ac97_soc_probe(struct snd_soc_codec *codec) return ret; memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); - ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97); if (ret < 0) return ret; + snd_soc_codec_set_drvdata(codec, ac97); + return 0; } #ifdef CONFIG_PM static int ac97_soc_suspend(struct snd_soc_codec *codec) { - snd_ac97_suspend(codec->ac97); + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + snd_ac97_suspend(ac97); return 0; } static int ac97_soc_resume(struct snd_soc_codec *codec) { - snd_ac97_resume(codec->ac97); + + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + snd_ac97_resume(ac97); return 0; } diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index 6844d0b2af6..387530b0b0f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -72,11 +72,13 @@ static const struct snd_kcontrol_new ad193x_snd_controls[] = { }; static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = { - SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("DAC Output", AD193X_DAC_CTRL0, 0, 1, NULL, 0), SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0), SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0), SND_SOC_DAPM_SUPPLY("SYSCLK", AD193X_PLL_CLK_CTRL0, 7, 0, NULL, 0), + SND_SOC_DAPM_VMID("VMID"), SND_SOC_DAPM_OUTPUT("DAC1OUT"), SND_SOC_DAPM_OUTPUT("DAC2OUT"), SND_SOC_DAPM_OUTPUT("DAC3OUT"), @@ -87,13 +89,15 @@ static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = { static const struct snd_soc_dapm_route audio_paths[] = { { "DAC", NULL, "SYSCLK" }, + { "DAC Output", NULL, "DAC" }, + { "DAC Output", NULL, "VMID" }, { "ADC", NULL, "SYSCLK" }, { "DAC", NULL, "ADC_PWR" }, { "ADC", NULL, "ADC_PWR" }, - { "DAC1OUT", NULL, "DAC" }, - { "DAC2OUT", NULL, "DAC" }, - { "DAC3OUT", NULL, "DAC" }, - { "DAC4OUT", NULL, "DAC" }, + { "DAC1OUT", NULL, "DAC Output" }, + { "DAC2OUT", NULL, "DAC Output" }, + { "DAC3OUT", NULL, "DAC Output" }, + { "DAC4OUT", NULL, "DAC Output" }, { "ADC", NULL, "ADC1IN" }, { "ADC", NULL, "ADC2IN" }, { "SYSCLK", NULL, "PLL_PWR" }, diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c index 304d3003339..2860eef8610 100644 --- a/sound/soc/codecs/ad1980.c +++ b/sound/soc/codecs/ad1980.c @@ -24,34 +24,86 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> +#include <linux/regmap.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/soc.h> -#include "ad1980.h" +static const struct reg_default ad1980_reg_defaults[] = { + { 0x02, 0x8000 }, + { 0x04, 0x8000 }, + { 0x06, 0x8000 }, + { 0x0c, 0x8008 }, + { 0x0e, 0x8008 }, + { 0x10, 0x8808 }, + { 0x12, 0x8808 }, + { 0x16, 0x8808 }, + { 0x18, 0x8808 }, + { 0x1a, 0x0000 }, + { 0x1c, 0x8000 }, + { 0x20, 0x0000 }, + { 0x28, 0x03c7 }, + { 0x2c, 0xbb80 }, + { 0x2e, 0xbb80 }, + { 0x30, 0xbb80 }, + { 0x32, 0xbb80 }, + { 0x36, 0x8080 }, + { 0x38, 0x8080 }, + { 0x3a, 0x2000 }, + { 0x60, 0x0000 }, + { 0x62, 0x0000 }, + { 0x72, 0x0000 }, + { 0x74, 0x1001 }, + { 0x76, 0x0000 }, +}; -/* - * AD1980 register cache - */ -static const u16 ad1980_reg[] = { - 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */ - 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */ - 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */ - 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */ - 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */ - 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */ - 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */ - 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */ - 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ - 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ - 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ - 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ - 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */ - 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ - 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */ - 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */ +static bool ad1980_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_RESET ... AC97_MASTER_MONO: + case AC97_PHONE ... AC97_CD: + case AC97_AUX ... AC97_GENERAL_PURPOSE: + case AC97_POWERDOWN ... AC97_PCM_LR_ADC_RATE: + case AC97_SPDIF: + case AC97_CODEC_CLASS_REV: + case AC97_PCI_SVID: + case AC97_AD_CODEC_CFG: + case AC97_AD_JACK_SPDIF: + case AC97_AD_SERIAL_CFG: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool ad1980_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return false; + default: + return ad1980_readable_reg(dev, reg); + } +} + +static const struct regmap_config ad1980_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x7e, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = regmap_ac97_default_volatile, + .readable_reg = ad1980_readable_reg, + .writeable_reg = ad1980_writeable_reg, + + .reg_defaults = ad1980_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ad1980_reg_defaults), }; static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", @@ -134,45 +186,8 @@ static const struct snd_soc_dapm_route ad1980_dapm_routes[] = { { "HP_OUT_R", NULL, "Playback" }, }; -static unsigned int ac97_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - - switch (reg) { - case AC97_RESET: - case AC97_INT_PAGING: - case AC97_POWERDOWN: - case AC97_EXTENDED_STATUS: - case AC97_VENDOR_ID1: - case AC97_VENDOR_ID2: - return soc_ac97_ops->read(codec->ac97, reg); - default: - reg = reg >> 1; - - if (reg >= ARRAY_SIZE(ad1980_reg)) - return -EINVAL; - - return cache[reg]; - } -} - -static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int val) -{ - u16 *cache = codec->reg_cache; - - soc_ac97_ops->write(codec->ac97, reg, val); - reg = reg >> 1; - if (reg < ARRAY_SIZE(ad1980_reg)) - cache[reg] = val; - - return 0; -} - static struct snd_soc_dai_driver ad1980_dai = { .name = "ad1980-hifi", - .ac97_control = 1, .playback = { .stream_name = "Playback", .channels_min = 2, @@ -189,108 +204,115 @@ static struct snd_soc_dai_driver ad1980_dai = { static int ad1980_reset(struct snd_soc_codec *codec, int try_warm) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); unsigned int retry_cnt = 0; do { if (try_warm && soc_ac97_ops->warm_reset) { - soc_ac97_ops->warm_reset(codec->ac97); - if (ac97_read(codec, AC97_RESET) == 0x0090) + soc_ac97_ops->warm_reset(ac97); + if (snd_soc_read(codec, AC97_RESET) == 0x0090) return 1; } - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(ac97); /* * Set bit 16slot in register 74h, then every slot will has only * 16 bits. This command is sent out in 20bit mode, in which * case the first nibble of data is eaten by the addr. (Tag is * always 16 bit) */ - ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900); + snd_soc_write(codec, AC97_AD_SERIAL_CFG, 0x9900); - if (ac97_read(codec, AC97_RESET) == 0x0090) + if (snd_soc_read(codec, AC97_RESET) == 0x0090) return 0; } while (retry_cnt++ < 10); - printk(KERN_ERR "AD1980 AC97 reset failed\n"); + dev_err(codec->dev, "Failed to reset: AC97 link error\n"); + return -EIO; } static int ad1980_soc_probe(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97; + struct regmap *regmap; int ret; u16 vendor_id2; u16 ext_status; - printk(KERN_INFO "AD1980 SoC Audio Codec\n"); - - ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0); - if (ret < 0) { - printk(KERN_ERR "ad1980: failed to register AC97 codec\n"); + ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(ac97)) { + ret = PTR_ERR(ac97); + dev_err(codec->dev, "Failed to register AC97 codec: %d\n", ret); return ret; } + regmap = regmap_init_ac97(ac97, &ad1980_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + goto err_free_ac97; + } + + snd_soc_codec_init_regmap(codec, regmap); + snd_soc_codec_set_drvdata(codec, ac97); + ret = ad1980_reset(codec, 0); - if (ret < 0) { - printk(KERN_ERR "Failed to reset AD1980: AC97 link error\n"); + if (ret < 0) goto reset_err; - } /* Read out vendor ID to make sure it is ad1980 */ - if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) { + if (snd_soc_read(codec, AC97_VENDOR_ID1) != 0x4144) { ret = -ENODEV; goto reset_err; } - vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2); + vendor_id2 = snd_soc_read(codec, AC97_VENDOR_ID2); if (vendor_id2 != 0x5370) { if (vendor_id2 != 0x5374) { ret = -ENODEV; goto reset_err; } else { - printk(KERN_WARNING "ad1980: " - "Found AD1981 - only 2/2 IN/OUT Channels " - "supported\n"); + dev_warn(codec->dev, + "Found AD1981 - only 2/2 IN/OUT Channels supported\n"); } } /* unmute captures and playbacks volume */ - ac97_write(codec, AC97_MASTER, 0x0000); - ac97_write(codec, AC97_PCM, 0x0000); - ac97_write(codec, AC97_REC_GAIN, 0x0000); - ac97_write(codec, AC97_CENTER_LFE_MASTER, 0x0000); - ac97_write(codec, AC97_SURROUND_MASTER, 0x0000); + snd_soc_write(codec, AC97_MASTER, 0x0000); + snd_soc_write(codec, AC97_PCM, 0x0000); + snd_soc_write(codec, AC97_REC_GAIN, 0x0000); + snd_soc_write(codec, AC97_CENTER_LFE_MASTER, 0x0000); + snd_soc_write(codec, AC97_SURROUND_MASTER, 0x0000); /*power on LFE/CENTER/Surround DACs*/ - ext_status = ac97_read(codec, AC97_EXTENDED_STATUS); - ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800); - - snd_soc_add_codec_controls(codec, ad1980_snd_ac97_controls, - ARRAY_SIZE(ad1980_snd_ac97_controls)); + ext_status = snd_soc_read(codec, AC97_EXTENDED_STATUS); + snd_soc_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800); return 0; reset_err: - snd_soc_free_ac97_codec(codec); + snd_soc_codec_exit_regmap(codec); +err_free_ac97: + snd_soc_free_ac97_codec(ac97); return ret; } static int ad1980_soc_remove(struct snd_soc_codec *codec) { - snd_soc_free_ac97_codec(codec); + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + snd_soc_codec_exit_regmap(codec); + snd_soc_free_ac97_codec(ac97); return 0; } static struct snd_soc_codec_driver soc_codec_dev_ad1980 = { .probe = ad1980_soc_probe, .remove = ad1980_soc_remove, - .reg_cache_size = ARRAY_SIZE(ad1980_reg), - .reg_word_size = sizeof(u16), - .reg_cache_default = ad1980_reg, - .reg_cache_step = 2, - .write = ac97_write, - .read = ac97_read, + .controls = ad1980_snd_ac97_controls, + .num_controls = ARRAY_SIZE(ad1980_snd_ac97_controls), .dapm_widgets = ad1980_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(ad1980_dapm_widgets), .dapm_routes = ad1980_dapm_routes, diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h deleted file mode 100644 index eb0af44ad3d..00000000000 --- a/sound/soc/codecs/ad1980.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * ad1980.h -- ad1980 Soc Audio driver - * - * WARNING: - * - * Because Analog Devices Inc. discontinued the ad1980 sound chip since - * Sep. 2009, this ad1980 driver is not maintained, tested and supported - * by ADI now. - */ - -#ifndef _AD1980_H -#define _AD1980_H -/* Bit definition of Power-Down Control/Status Register */ -#define ADC 0x0001 -#define DAC 0x0002 -#define ANL 0x0004 -#define REF 0x0008 -#define PR0 0x0100 -#define PR1 0x0200 -#define PR2 0x0400 -#define PR3 0x0800 -#define PR4 0x1000 -#define PR5 0x2000 -#define PR6 0x4000 - -#endif diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index 7c784ad3e8b..783dcb57043 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -551,7 +551,7 @@ static const struct snd_kcontrol_new adau1373_drc_controls[] = { static int adau1373_pll_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int pll_id = w->name[3] - '1'; unsigned int val; @@ -823,7 +823,7 @@ static const struct snd_soc_dapm_widget adau1373_dapm_widgets[] = { static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - struct snd_soc_codec *codec = source->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int dai; const char *clk; @@ -844,7 +844,7 @@ static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source, static int adau1373_check_src(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - struct snd_soc_codec *codec = source->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int dai; diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 370b742117e..d4e219b6b98 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -22,9 +22,14 @@ #include <sound/pcm_params.h> #include <sound/soc.h> +#include <asm/unaligned.h> + #include "sigmadsp.h" #include "adau1701.h" +#define ADAU1701_SAFELOAD_DATA(i) (0x0810 + (i)) +#define ADAU1701_SAFELOAD_ADDR(i) (0x0815 + (i)) + #define ADAU1701_DSPCTRL 0x081c #define ADAU1701_SEROCTL 0x081e #define ADAU1701_SERICTL 0x081f @@ -42,6 +47,7 @@ #define ADAU1701_DSPCTRL_CR (1 << 2) #define ADAU1701_DSPCTRL_DAM (1 << 3) #define ADAU1701_DSPCTRL_ADM (1 << 4) +#define ADAU1701_DSPCTRL_IST (1 << 5) #define ADAU1701_DSPCTRL_SR_48 0x00 #define ADAU1701_DSPCTRL_SR_96 0x01 #define ADAU1701_DSPCTRL_SR_192 0x02 @@ -102,7 +108,10 @@ struct adau1701 { unsigned int pll_clkdiv; unsigned int sysclk; struct regmap *regmap; + struct i2c_client *client; u8 pin_config[12]; + + struct sigmadsp *sigmadsp; }; static const struct snd_kcontrol_new adau1701_controls[] = { @@ -159,6 +168,7 @@ static bool adau1701_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case ADAU1701_DACSET: + case ADAU1701_DSPCTRL: return true; default: return false; @@ -238,12 +248,58 @@ static int adau1701_reg_read(void *context, unsigned int reg, return 0; } -static int adau1701_reset(struct snd_soc_codec *codec, unsigned int clkdiv) +static int adau1701_safeload(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t bytes[], size_t len) +{ + struct i2c_client *client = to_i2c_client(sigmadsp->dev); + struct adau1701 *adau1701 = i2c_get_clientdata(client); + unsigned int val; + unsigned int i; + uint8_t buf[10]; + int ret; + + ret = regmap_read(adau1701->regmap, ADAU1701_DSPCTRL, &val); + if (ret) + return ret; + + if (val & ADAU1701_DSPCTRL_IST) + msleep(50); + + for (i = 0; i < len / 4; i++) { + put_unaligned_le16(ADAU1701_SAFELOAD_DATA(i), buf); + buf[2] = 0x00; + memcpy(buf + 3, bytes + i * 4, 4); + ret = i2c_master_send(client, buf, 7); + if (ret < 0) + return ret; + else if (ret != 7) + return -EIO; + + put_unaligned_le16(ADAU1701_SAFELOAD_ADDR(i), buf); + put_unaligned_le16(addr + i, buf + 2); + ret = i2c_master_send(client, buf, 4); + if (ret < 0) + return ret; + else if (ret != 4) + return -EIO; + } + + return regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, + ADAU1701_DSPCTRL_IST, ADAU1701_DSPCTRL_IST); +} + +static const struct sigmadsp_ops adau1701_sigmadsp_ops = { + .safeload = adau1701_safeload, +}; + +static int adau1701_reset(struct snd_soc_codec *codec, unsigned int clkdiv, + unsigned int rate) { struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec); - struct i2c_client *client = to_i2c_client(codec->dev); int ret; + sigmadsp_reset(adau1701->sigmadsp); + if (clkdiv != ADAU1707_CLKDIV_UNSET && gpio_is_valid(adau1701->gpio_pll_mode[0]) && gpio_is_valid(adau1701->gpio_pll_mode[1])) { @@ -284,7 +340,7 @@ static int adau1701_reset(struct snd_soc_codec *codec, unsigned int clkdiv) * know the correct PLL setup */ if (clkdiv != ADAU1707_CLKDIV_UNSET) { - ret = process_sigma_firmware(client, ADAU1701_FIRMWARE); + ret = sigmadsp_setup(adau1701->sigmadsp, rate); if (ret) { dev_warn(codec->dev, "Failed to load firmware\n"); return ret; @@ -385,7 +441,7 @@ static int adau1701_hw_params(struct snd_pcm_substream *substream, * firmware upload. */ if (clkdiv != adau1701->pll_clkdiv) { - ret = adau1701_reset(codec, clkdiv); + ret = adau1701_reset(codec, clkdiv, params_rate(params)); if (ret < 0) return ret; } @@ -554,6 +610,14 @@ static int adau1701_set_sysclk(struct snd_soc_codec *codec, int clk_id, return 0; } +static int adau1701_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(dai->codec); + + return sigmadsp_restrict_params(adau1701->sigmadsp, substream); +} + #define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ SNDRV_PCM_RATE_192000) @@ -564,6 +628,7 @@ static const struct snd_soc_dai_ops adau1701_dai_ops = { .set_fmt = adau1701_set_dai_fmt, .hw_params = adau1701_hw_params, .digital_mute = adau1701_digital_mute, + .startup = adau1701_startup, }; static struct snd_soc_dai_driver adau1701_dai = { @@ -600,6 +665,10 @@ static int adau1701_probe(struct snd_soc_codec *codec) unsigned int val; struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec); + ret = sigmadsp_attach(adau1701->sigmadsp, &codec->component); + if (ret) + return ret; + /* * Let the pll_clkdiv variable default to something that won't happen * at runtime. That way, we can postpone the firmware download from @@ -609,7 +678,7 @@ static int adau1701_probe(struct snd_soc_codec *codec) adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET; /* initalize with pre-configured pll mode settings */ - ret = adau1701_reset(codec, adau1701->pll_clkdiv); + ret = adau1701_reset(codec, adau1701->pll_clkdiv, 0); if (ret < 0) return ret; @@ -667,6 +736,7 @@ static int adau1701_i2c_probe(struct i2c_client *client, if (!adau1701) return -ENOMEM; + adau1701->client = client; adau1701->regmap = devm_regmap_init(dev, NULL, client, &adau1701_regmap); if (IS_ERR(adau1701->regmap)) @@ -722,6 +792,12 @@ static int adau1701_i2c_probe(struct i2c_client *client, adau1701->gpio_pll_mode[1] = gpio_pll_mode[1]; i2c_set_clientdata(client, adau1701); + + adau1701->sigmadsp = devm_sigmadsp_init_i2c(client, + &adau1701_sigmadsp_ops, ADAU1701_FIRMWARE); + if (IS_ERR(adau1701->sigmadsp)) + return PTR_ERR(adau1701->sigmadsp); + ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv, &adau1701_dai, 1); return ret; diff --git a/sound/soc/codecs/adau1761.c b/sound/soc/codecs/adau1761.c index 5518ebd6947..a1baeee160f 100644 --- a/sound/soc/codecs/adau1761.c +++ b/sound/soc/codecs/adau1761.c @@ -255,7 +255,8 @@ static const struct snd_kcontrol_new adau1761_input_mux_control = static int adau1761_dejitter_fixup(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct adau *adau = snd_soc_codec_get_drvdata(w->codec); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct adau *adau = snd_soc_codec_get_drvdata(codec); /* After any power changes have been made the dejitter circuit * has to be reinitialized. */ @@ -405,6 +406,7 @@ static const struct snd_soc_dapm_widget adau1761_dapm_widgets[] = { 2, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("Slew Clock", ADAU1761_CLK_ENABLE0, 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ALC Clock", ADAU1761_CLK_ENABLE0, 5, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("Digital Clock 0", 1, ADAU1761_CLK_ENABLE1, 0, 0, NULL, 0), @@ -436,6 +438,9 @@ static const struct snd_soc_dapm_route adau1761_dapm_routes[] = { { "Right Playback Mixer", NULL, "Slew Clock" }, { "Left Playback Mixer", NULL, "Slew Clock" }, + { "Left Input Mixer", NULL, "ALC Clock" }, + { "Right Input Mixer", NULL, "ALC Clock" }, + { "Digital Clock 0", NULL, "SYSCLK" }, { "Digital Clock 1", NULL, "SYSCLK" }, }; @@ -698,11 +703,6 @@ static int adau1761_codec_probe(struct snd_soc_codec *codec) ARRAY_SIZE(adau1761_dapm_routes)); if (ret) return ret; - - ret = adau17x1_load_firmware(adau, codec->dev, - ADAU1761_FIRMWARE); - if (ret) - dev_warn(codec->dev, "Failed to firmware\n"); } ret = adau17x1_add_routes(codec); @@ -771,16 +771,20 @@ int adau1761_probe(struct device *dev, struct regmap *regmap, enum adau17x1_type type, void (*switch_mode)(struct device *dev)) { struct snd_soc_dai_driver *dai_drv; + const char *firmware_name; int ret; - ret = adau17x1_probe(dev, regmap, type, switch_mode); - if (ret) - return ret; - - if (type == ADAU1361) + if (type == ADAU1361) { dai_drv = &adau1361_dai_driver; - else + firmware_name = NULL; + } else { dai_drv = &adau1761_dai_driver; + firmware_name = ADAU1761_FIRMWARE; + } + + ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name); + if (ret) + return ret; return snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1); } @@ -794,6 +798,7 @@ const struct regmap_config adau1761_regmap_config = { .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults), .readable_reg = adau1761_readable_register, .volatile_reg = adau17x1_volatile_register, + .precious_reg = adau17x1_precious_register, .cache_type = REGCACHE_RBTREE, }; EXPORT_SYMBOL_GPL(adau1761_regmap_config); diff --git a/sound/soc/codecs/adau1781.c b/sound/soc/codecs/adau1781.c index e9fc00fb13d..35581f43fa6 100644 --- a/sound/soc/codecs/adau1781.c +++ b/sound/soc/codecs/adau1781.c @@ -174,7 +174,7 @@ static const struct snd_kcontrol_new adau1781_mono_mixer_controls[] = { static int adau1781_dejitter_fixup(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct adau *adau = snd_soc_codec_get_drvdata(codec); /* After any power changes have been made the dejitter circuit @@ -385,7 +385,6 @@ static int adau1781_codec_probe(struct snd_soc_codec *codec) { struct adau1781_platform_data *pdata = dev_get_platdata(codec->dev); struct adau *adau = snd_soc_codec_get_drvdata(codec); - const char *firmware; int ret; ret = adau17x1_add_widgets(codec); @@ -422,25 +421,10 @@ static int adau1781_codec_probe(struct snd_soc_codec *codec) return ret; } - switch (adau->type) { - case ADAU1381: - firmware = ADAU1381_FIRMWARE; - break; - case ADAU1781: - firmware = ADAU1781_FIRMWARE; - break; - default: - return -EINVAL; - } - ret = adau17x1_add_routes(codec); if (ret < 0) return ret; - ret = adau17x1_load_firmware(adau, codec->dev, firmware); - if (ret) - dev_warn(codec->dev, "Failed to load firmware\n"); - return 0; } @@ -488,6 +472,7 @@ const struct regmap_config adau1781_regmap_config = { .num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults), .readable_reg = adau1781_readable_register, .volatile_reg = adau17x1_volatile_register, + .precious_reg = adau17x1_precious_register, .cache_type = REGCACHE_RBTREE, }; EXPORT_SYMBOL_GPL(adau1781_regmap_config); @@ -495,9 +480,21 @@ EXPORT_SYMBOL_GPL(adau1781_regmap_config); int adau1781_probe(struct device *dev, struct regmap *regmap, enum adau17x1_type type, void (*switch_mode)(struct device *dev)) { + const char *firmware_name; int ret; - ret = adau17x1_probe(dev, regmap, type, switch_mode); + switch (type) { + case ADAU1381: + firmware_name = ADAU1381_FIRMWARE; + break; + case ADAU1781: + firmware_name = ADAU1781_FIRMWARE; + break; + default: + return -EINVAL; + } + + ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name); if (ret) return ret; diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 3e16c1c6411..fa2e690e51c 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -61,7 +61,8 @@ static const struct snd_kcontrol_new adau17x1_controls[] = { static int adau17x1_pll_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct adau *adau = snd_soc_codec_get_drvdata(w->codec); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct adau *adau = snd_soc_codec_get_drvdata(codec); int ret; if (SND_SOC_DAPM_EVENT_ON(event)) { @@ -307,6 +308,7 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, struct adau *adau = snd_soc_codec_get_drvdata(codec); unsigned int val, div, dsp_div; unsigned int freq; + int ret; if (adau->clk_src == ADAU17X1_CLK_SRC_PLL) freq = adau->pll_freq; @@ -356,6 +358,12 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dsp_div); } + if (adau->sigmadsp) { + ret = adau17x1_setup_firmware(adau, params_rate(params)); + if (ret < 0) + return ret; + } + if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) return 0; @@ -661,12 +669,24 @@ static int adau17x1_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } +static int adau17x1_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + + if (adau->sigmadsp) + return sigmadsp_restrict_params(adau->sigmadsp, substream); + + return 0; +} + const struct snd_soc_dai_ops adau17x1_dai_ops = { .hw_params = adau17x1_hw_params, .set_sysclk = adau17x1_set_dai_sysclk, .set_fmt = adau17x1_set_dai_fmt, .set_pll = adau17x1_set_dai_pll, .set_tdm_slot = adau17x1_set_dai_tdm_slot, + .startup = adau17x1_startup, }; EXPORT_SYMBOL_GPL(adau17x1_dai_ops); @@ -687,8 +707,22 @@ int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(adau17x1_set_micbias_voltage); +bool adau17x1_precious_register(struct device *dev, unsigned int reg) +{ + /* SigmaDSP parameter memory */ + if (reg < 0x400) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_precious_register); + bool adau17x1_readable_register(struct device *dev, unsigned int reg) { + /* SigmaDSP parameter memory */ + if (reg < 0x400) + return true; + switch (reg) { case ADAU17X1_CLOCK_CONTROL: case ADAU17X1_PLL_CONTROL: @@ -745,8 +779,7 @@ bool adau17x1_volatile_register(struct device *dev, unsigned int reg) } EXPORT_SYMBOL_GPL(adau17x1_volatile_register); -int adau17x1_load_firmware(struct adau *adau, struct device *dev, - const char *firmware) +int adau17x1_setup_firmware(struct adau *adau, unsigned int rate) { int ret; int dspsr; @@ -758,7 +791,7 @@ int adau17x1_load_firmware(struct adau *adau, struct device *dev, regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 1); regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, 0xf); - ret = process_sigma_firmware_regmap(dev, adau->regmap, firmware); + ret = sigmadsp_setup(adau->sigmadsp, rate); if (ret) { regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 0); return ret; @@ -767,7 +800,7 @@ int adau17x1_load_firmware(struct adau *adau, struct device *dev, return 0; } -EXPORT_SYMBOL_GPL(adau17x1_load_firmware); +EXPORT_SYMBOL_GPL(adau17x1_setup_firmware); int adau17x1_add_widgets(struct snd_soc_codec *codec) { @@ -787,8 +820,21 @@ int adau17x1_add_widgets(struct snd_soc_codec *codec) ret = snd_soc_dapm_new_controls(&codec->dapm, adau17x1_dsp_dapm_widgets, ARRAY_SIZE(adau17x1_dsp_dapm_widgets)); + if (ret) + return ret; + + if (!adau->sigmadsp) + return 0; + + ret = sigmadsp_attach(adau->sigmadsp, &codec->component); + if (ret) { + dev_err(codec->dev, "Failed to attach firmware: %d\n", + ret); + return ret; + } } - return ret; + + return 0; } EXPORT_SYMBOL_GPL(adau17x1_add_widgets); @@ -829,7 +875,8 @@ int adau17x1_resume(struct snd_soc_codec *codec) EXPORT_SYMBOL_GPL(adau17x1_resume); int adau17x1_probe(struct device *dev, struct regmap *regmap, - enum adau17x1_type type, void (*switch_mode)(struct device *dev)) + enum adau17x1_type type, void (*switch_mode)(struct device *dev), + const char *firmware_name) { struct adau *adau; @@ -846,6 +893,16 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, dev_set_drvdata(dev, adau); + if (firmware_name) { + adau->sigmadsp = devm_sigmadsp_init_regmap(dev, regmap, NULL, + firmware_name); + if (IS_ERR(adau->sigmadsp)) { + dev_warn(dev, "Could not find firmware file: %ld\n", + PTR_ERR(adau->sigmadsp)); + adau->sigmadsp = NULL; + } + } + if (switch_mode) switch_mode(dev); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h index e4a557fd715..e13583e6ff5 100644 --- a/sound/soc/codecs/adau17x1.h +++ b/sound/soc/codecs/adau17x1.h @@ -4,6 +4,8 @@ #include <linux/regmap.h> #include <linux/platform_data/adau17x1.h> +#include "sigmadsp.h" + enum adau17x1_type { ADAU1361, ADAU1761, @@ -42,22 +44,24 @@ struct adau { bool dsp_bypass[2]; struct regmap *regmap; + struct sigmadsp *sigmadsp; }; int adau17x1_add_widgets(struct snd_soc_codec *codec); int adau17x1_add_routes(struct snd_soc_codec *codec); int adau17x1_probe(struct device *dev, struct regmap *regmap, - enum adau17x1_type type, void (*switch_mode)(struct device *dev)); + enum adau17x1_type type, void (*switch_mode)(struct device *dev), + const char *firmware_name); int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, enum adau17x1_micbias_voltage micbias); bool adau17x1_readable_register(struct device *dev, unsigned int reg); bool adau17x1_volatile_register(struct device *dev, unsigned int reg); +bool adau17x1_precious_register(struct device *dev, unsigned int reg); int adau17x1_resume(struct snd_soc_codec *codec); extern const struct snd_soc_dai_ops adau17x1_dai_ops; -int adau17x1_load_firmware(struct adau *adau, struct device *dev, - const char *firmware); +int adau17x1_setup_firmware(struct adau *adau, unsigned int rate); bool adau17x1_has_dsp(struct adau *adau); #define ADAU17X1_CLOCK_CONTROL 0x4000 diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c index ce3cdca9fc6..b67480f1b1a 100644 --- a/sound/soc/codecs/adav80x.c +++ b/sound/soc/codecs/adav80x.c @@ -212,7 +212,7 @@ static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = { static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - struct snd_soc_codec *codec = source->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); const char *clk; @@ -236,7 +236,7 @@ static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source, static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { - struct snd_soc_codec *codec = source->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL; diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 30e297890fe..9130d916f2f 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -373,33 +373,9 @@ static struct snd_soc_dai_driver ak4535_dai = { .ops = &ak4535_dai_ops, }; -static int ak4535_suspend(struct snd_soc_codec *codec) -{ - ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static int ak4535_resume(struct snd_soc_codec *codec) { snd_soc_cache_sync(codec); - ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - -static int ak4535_probe(struct snd_soc_codec *codec) -{ - /* power on device */ - ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - snd_soc_add_codec_controls(codec, ak4535_snd_controls, - ARRAY_SIZE(ak4535_snd_controls)); - return 0; -} - -/* power down chip */ -static int ak4535_remove(struct snd_soc_codec *codec) -{ - ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -416,11 +392,12 @@ static const struct regmap_config ak4535_regmap = { }; static struct snd_soc_codec_driver soc_codec_dev_ak4535 = { - .probe = ak4535_probe, - .remove = ak4535_remove, - .suspend = ak4535_suspend, .resume = ak4535_resume, .set_bias_level = ak4535_set_bias_level, + .suspend_bias_off = true, + + .controls = ak4535_snd_controls, + .num_controls = ARRAY_SIZE(ak4535_snd_controls), .dapm_widgets = ak4535_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(ak4535_dapm_widgets), .dapm_routes = ak4535_audio_map, diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c index 7afe8f48208..70861c7b163 100644 --- a/sound/soc/codecs/ak4641.c +++ b/sound/soc/codecs/ak4641.c @@ -505,39 +505,7 @@ static struct snd_soc_dai_driver ak4641_dai[] = { }, }; -static int ak4641_suspend(struct snd_soc_codec *codec) -{ - ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int ak4641_resume(struct snd_soc_codec *codec) -{ - ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - -static int ak4641_probe(struct snd_soc_codec *codec) -{ - /* power on device */ - ak4641_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int ak4641_remove(struct snd_soc_codec *codec) -{ - ak4641_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - - static struct snd_soc_codec_driver soc_codec_dev_ak4641 = { - .probe = ak4641_probe, - .remove = ak4641_remove, - .suspend = ak4641_suspend, - .resume = ak4641_resume, .controls = ak4641_snd_controls, .num_controls = ARRAY_SIZE(ak4641_snd_controls), .dapm_widgets = ak4641_dapm_widgets, @@ -545,6 +513,7 @@ static struct snd_soc_codec_driver soc_codec_dev_ak4641 = { .dapm_routes = ak4641_audio_map, .num_dapm_routes = ARRAY_SIZE(ak4641_audio_map), .set_bias_level = ak4641_set_bias_level, + .suspend_bias_off = true, }; static const struct regmap_config ak4641_regmap = { diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 041712592e2..dde8b49c19a 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -491,23 +491,7 @@ static int ak4642_resume(struct snd_soc_codec *codec) return 0; } - -static int ak4642_probe(struct snd_soc_codec *codec) -{ - ak4642_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int ak4642_remove(struct snd_soc_codec *codec) -{ - ak4642_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_ak4642 = { - .probe = ak4642_probe, - .remove = ak4642_remove, .resume = ak4642_resume, .set_bias_level = ak4642_set_bias_level, .controls = ak4642_snd_controls, diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index 998fa0c5a0b..686cacb0e83 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -611,20 +611,7 @@ static struct snd_soc_dai_driver ak4671_dai = { .ops = &ak4671_dai_ops, }; -static int ak4671_probe(struct snd_soc_codec *codec) -{ - return ak4671_set_bias_level(codec, SND_SOC_BIAS_STANDBY); -} - -static int ak4671_remove(struct snd_soc_codec *codec) -{ - ak4671_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_ak4671 = { - .probe = ak4671_probe, - .remove = ak4671_remove, .set_bias_level = ak4671_set_bias_level, .controls = ak4671_snd_controls, .num_controls = ARRAY_SIZE(ak4671_snd_controls), diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c index 9d0755aa1d1..bdf8c5ac8ca 100644 --- a/sound/soc/codecs/alc5623.c +++ b/sound/soc/codecs/alc5623.c @@ -866,7 +866,6 @@ static int alc5623_suspend(struct snd_soc_codec *codec) { struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec); - alc5623_set_bias_level(codec, SND_SOC_BIAS_OFF); regcache_cache_only(alc5623->regmap, true); return 0; @@ -887,15 +886,6 @@ static int alc5623_resume(struct snd_soc_codec *codec) return ret; } - alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - /* charge alc5623 caps */ - if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) { - alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - codec->dapm.bias_level = SND_SOC_BIAS_ON; - alc5623_set_bias_level(codec, codec->dapm.bias_level); - } - return 0; } @@ -906,9 +896,6 @@ static int alc5623_probe(struct snd_soc_codec *codec) alc5623_reset(codec); - /* power on device */ - alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (alc5623->add_ctrl) { snd_soc_write(codec, ALC5623_ADD_CTRL_REG, alc5623->add_ctrl); @@ -964,19 +951,12 @@ static int alc5623_probe(struct snd_soc_codec *codec) return 0; } -/* power down chip */ -static int alc5623_remove(struct snd_soc_codec *codec) -{ - alc5623_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_device_alc5623 = { .probe = alc5623_probe, - .remove = alc5623_remove, .suspend = alc5623_suspend, .resume = alc5623_resume, .set_bias_level = alc5623_set_bias_level, + .suspend_bias_off = true, }; static const struct regmap_config alc5623_regmap = { diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c index 85942ca36cb..d1fdbc26663 100644 --- a/sound/soc/codecs/alc5632.c +++ b/sound/soc/codecs/alc5632.c @@ -1038,23 +1038,15 @@ static struct snd_soc_dai_driver alc5632_dai = { }; #ifdef CONFIG_PM -static int alc5632_suspend(struct snd_soc_codec *codec) -{ - alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static int alc5632_resume(struct snd_soc_codec *codec) { struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec); regcache_sync(alc5632->regmap); - alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } #else -#define alc5632_suspend NULL #define alc5632_resume NULL #endif @@ -1062,9 +1054,6 @@ static int alc5632_probe(struct snd_soc_codec *codec) { struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec); - /* power on device */ - alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - switch (alc5632->id) { case 0x5c: snd_soc_add_codec_controls(codec, alc5632_vol_snd_controls, @@ -1077,19 +1066,12 @@ static int alc5632_probe(struct snd_soc_codec *codec) return 0; } -/* power down chip */ -static int alc5632_remove(struct snd_soc_codec *codec) -{ - alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_device_alc5632 = { .probe = alc5632_probe, - .remove = alc5632_remove, - .suspend = alc5632_suspend, .resume = alc5632_resume, .set_bias_level = alc5632_set_bias_level, + .suspend_bias_off = true, + .controls = alc5632_snd_controls, .num_controls = ARRAY_SIZE(alc5632_snd_controls), .dapm_widgets = alc5632_dapm_widgets, diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 0c05e7a7945..9550d7433ad 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -61,6 +61,11 @@ #define ARIZONA_FLL_MIN_OUTDIV 2 #define ARIZONA_FLL_MAX_OUTDIV 7 +#define ARIZONA_FMT_DSP_MODE_A 0 +#define ARIZONA_FMT_DSP_MODE_B 1 +#define ARIZONA_FMT_I2S_MODE 2 +#define ARIZONA_FMT_LEFT_JUSTIFIED_MODE 3 + #define arizona_fll_err(_fll, fmt, ...) \ dev_err(_fll->arizona->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) #define arizona_fll_warn(_fll, fmt, ...) \ @@ -648,7 +653,7 @@ SOC_ENUM_SINGLE_DECL(arizona_in_hpf_cut_enum, EXPORT_SYMBOL_GPL(arizona_in_hpf_cut_enum); static const char * const arizona_in_dmic_osr_text[] = { - "1.536MHz", "3.072MHz", "6.144MHz", + "1.536MHz", "3.072MHz", "6.144MHz", "768kHz", }; const struct soc_enum arizona_in_dmic_osr[] = { @@ -946,10 +951,26 @@ static int arizona_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_DSP_A: - mode = 0; + mode = ARIZONA_FMT_DSP_MODE_A; + break; + case SND_SOC_DAIFMT_DSP_B: + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) + != SND_SOC_DAIFMT_CBM_CFM) { + arizona_aif_err(dai, "DSP_B not valid in slave mode\n"); + return -EINVAL; + } + mode = ARIZONA_FMT_DSP_MODE_B; break; case SND_SOC_DAIFMT_I2S: - mode = 2; + mode = ARIZONA_FMT_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) + != SND_SOC_DAIFMT_CBM_CFM) { + arizona_aif_err(dai, "LEFT_J not valid in slave mode\n"); + return -EINVAL; + } + mode = ARIZONA_FMT_LEFT_JUSTIFIED_MODE; break; default: arizona_aif_err(dai, "Unsupported DAI format %d\n", @@ -1164,13 +1185,13 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_codec *codec, { 0x80, 0x0 }, }; - mutex_lock(&codec->mutex); + mutex_lock(&arizona->dac_comp_lock); dac_comp[1].def = arizona->dac_comp_coeff; if (rate >= 176400) dac_comp[2].def = arizona->dac_comp_enabled; - mutex_unlock(&codec->mutex); + mutex_unlock(&arizona->dac_comp_lock); regmap_multi_reg_write(arizona->regmap, dac_comp, @@ -1298,7 +1319,8 @@ static int arizona_hw_params(struct snd_pcm_substream *substream, /* Force multiple of 2 channels for I2S mode */ val = snd_soc_read(codec, base + ARIZONA_AIF_FORMAT); - if ((channels & 1) && (val & ARIZONA_AIF1_FMT_MASK)) { + val &= ARIZONA_AIF1_FMT_MASK; + if ((channels & 1) && (val == ARIZONA_FMT_I2S_MODE)) { arizona_aif_dbg(dai, "Forcing stereo mode\n"); bclk_target /= channels; bclk_target *= channels + 1; diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c index 537327c7f7f..8d638e8aa8e 100644 --- a/sound/soc/codecs/cq93vc.c +++ b/sound/soc/codecs/cq93vc.c @@ -62,14 +62,10 @@ static int cq93vc_mute(struct snd_soc_dai *dai, int mute) static int cq93vc_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { - struct snd_soc_codec *codec = codec_dai->codec; - struct davinci_vc *davinci_vc = codec->dev->platform_data; - switch (freq) { case 22579200: case 27000000: case 33868800: - davinci_vc->cq93vc.sysclk = freq; return 0; } @@ -126,32 +122,6 @@ static struct snd_soc_dai_driver cq93vc_dai = { .ops = &cq93vc_dai_ops, }; -static int cq93vc_resume(struct snd_soc_codec *codec) -{ - cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int cq93vc_probe(struct snd_soc_codec *codec) -{ - struct davinci_vc *davinci_vc = codec->dev->platform_data; - - davinci_vc->cq93vc.codec = codec; - - /* Off, with power on */ - cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int cq93vc_remove(struct snd_soc_codec *codec) -{ - cq93vc_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static struct regmap *cq93vc_get_regmap(struct device *dev) { struct davinci_vc *davinci_vc = dev->platform_data; @@ -161,9 +131,6 @@ static struct regmap *cq93vc_get_regmap(struct device *dev) static struct snd_soc_codec_driver soc_codec_dev_cq93vc = { .set_bias_level = cq93vc_set_bias_level, - .probe = cq93vc_probe, - .remove = cq93vc_remove, - .resume = cq93vc_resume, .get_regmap = cq93vc_get_regmap, .controls = cq93vc_snd_controls, .num_controls = ARRAY_SIZE(cq93vc_snd_controls), diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c index 4fdd47d700e..ce6086835eb 100644 --- a/sound/soc/codecs/cs4265.c +++ b/sound/soc/codecs/cs4265.c @@ -32,7 +32,6 @@ #include "cs4265.h" struct cs4265_private { - struct device *dev; struct regmap *regmap; struct gpio_desc *reset_gpio; u8 format; @@ -598,7 +597,6 @@ static int cs4265_i2c_probe(struct i2c_client *i2c_client, GFP_KERNEL); if (cs4265 == NULL) return -ENOMEM; - cs4265->dev = &i2c_client->dev; cs4265->regmap = devm_regmap_init_i2c(i2c_client, &cs4265_regmap); if (IS_ERR(cs4265->regmap)) { diff --git a/sound/soc/codecs/cs4271-i2c.c b/sound/soc/codecs/cs4271-i2c.c new file mode 100644 index 00000000000..b264da03034 --- /dev/null +++ b/sound/soc/codecs/cs4271-i2c.c @@ -0,0 +1,62 @@ +/* + * CS4271 I2C audio driver + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "cs4271.h" + +static int cs4271_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = cs4271_regmap_config; + config.reg_bits = 8; + config.val_bits = 8; + + return cs4271_probe(&client->dev, + devm_regmap_init_i2c(client, &config)); +} + +static int cs4271_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id cs4271_i2c_id[] = { + { "cs4271", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id); + +static struct i2c_driver cs4271_i2c_driver = { + .driver = { + .name = "cs4271", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cs4271_dt_ids), + }, + .probe = cs4271_i2c_probe, + .remove = cs4271_i2c_remove, + .id_table = cs4271_i2c_id, +}; +module_i2c_driver(cs4271_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS4271 I2C Driver"); +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271-spi.c b/sound/soc/codecs/cs4271-spi.c new file mode 100644 index 00000000000..acd49d86e70 --- /dev/null +++ b/sound/soc/codecs/cs4271-spi.c @@ -0,0 +1,55 @@ +/* + * CS4271 SPI audio driver + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "cs4271.h" + +static int cs4271_spi_probe(struct spi_device *spi) +{ + struct regmap_config config; + + config = cs4271_regmap_config; + config.reg_bits = 16; + config.val_bits = 8; + config.read_flag_mask = 0x21; + config.write_flag_mask = 0x20; + + return cs4271_probe(&spi->dev, devm_regmap_init_spi(spi, &config)); +} + +static int cs4271_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static struct spi_driver cs4271_spi_driver = { + .driver = { + .name = "cs4271", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cs4271_dt_ids), + }, + .probe = cs4271_spi_probe, + .remove = cs4271_spi_remove, +}; +module_spi_driver(cs4271_spi_driver); + +MODULE_DESCRIPTION("ASoC CS4271 SPI Driver"); +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 93cec52f473..79a4efcb894 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -23,8 +23,6 @@ #include <linux/slab.h> #include <linux/delay.h> #include <linux/gpio.h> -#include <linux/i2c.h> -#include <linux/spi/spi.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_gpio.h> @@ -32,6 +30,7 @@ #include <sound/soc.h> #include <sound/tlv.h> #include <sound/cs4271.h> +#include "cs4271.h" #define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ @@ -527,14 +526,15 @@ static int cs4271_soc_resume(struct snd_soc_codec *codec) #endif /* CONFIG_PM */ #ifdef CONFIG_OF -static const struct of_device_id cs4271_dt_ids[] = { +const struct of_device_id cs4271_dt_ids[] = { { .compatible = "cirrus,cs4271", }, { } }; MODULE_DEVICE_TABLE(of, cs4271_dt_ids); +EXPORT_SYMBOL_GPL(cs4271_dt_ids); #endif -static int cs4271_probe(struct snd_soc_codec *codec) +static int cs4271_codec_probe(struct snd_soc_codec *codec) { struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); struct cs4271_platform_data *cs4271plat = codec->dev->platform_data; @@ -587,7 +587,7 @@ static int cs4271_probe(struct snd_soc_codec *codec) return 0; } -static int cs4271_remove(struct snd_soc_codec *codec) +static int cs4271_codec_remove(struct snd_soc_codec *codec) { struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); @@ -599,8 +599,8 @@ static int cs4271_remove(struct snd_soc_codec *codec) }; static struct snd_soc_codec_driver soc_codec_dev_cs4271 = { - .probe = cs4271_probe, - .remove = cs4271_remove, + .probe = cs4271_codec_probe, + .remove = cs4271_codec_remove, .suspend = cs4271_soc_suspend, .resume = cs4271_soc_resume, @@ -642,14 +642,8 @@ static int cs4271_common_probe(struct device *dev, return 0; } -#if defined(CONFIG_SPI_MASTER) - -static const struct regmap_config cs4271_spi_regmap = { - .reg_bits = 16, - .val_bits = 8, +const struct regmap_config cs4271_regmap_config = { .max_register = CS4271_LASTREG, - .read_flag_mask = 0x21, - .write_flag_mask = 0x20, .reg_defaults = cs4271_reg_defaults, .num_reg_defaults = ARRAY_SIZE(cs4271_reg_defaults), @@ -657,140 +651,27 @@ static const struct regmap_config cs4271_spi_regmap = { .volatile_reg = cs4271_volatile_reg, }; +EXPORT_SYMBOL_GPL(cs4271_regmap_config); -static int cs4271_spi_probe(struct spi_device *spi) +int cs4271_probe(struct device *dev, struct regmap *regmap) { struct cs4271_private *cs4271; int ret; - ret = cs4271_common_probe(&spi->dev, &cs4271); - if (ret < 0) - return ret; - - spi_set_drvdata(spi, cs4271); - cs4271->regmap = devm_regmap_init_spi(spi, &cs4271_spi_regmap); - if (IS_ERR(cs4271->regmap)) - return PTR_ERR(cs4271->regmap); - - return snd_soc_register_codec(&spi->dev, &soc_codec_dev_cs4271, - &cs4271_dai, 1); -} - -static int cs4271_spi_remove(struct spi_device *spi) -{ - snd_soc_unregister_codec(&spi->dev); - return 0; -} - -static struct spi_driver cs4271_spi_driver = { - .driver = { - .name = "cs4271", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(cs4271_dt_ids), - }, - .probe = cs4271_spi_probe, - .remove = cs4271_spi_remove, -}; -#endif /* defined(CONFIG_SPI_MASTER) */ - -#if IS_ENABLED(CONFIG_I2C) -static const struct i2c_device_id cs4271_i2c_id[] = { - {"cs4271", 0}, - {} -}; -MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); -static const struct regmap_config cs4271_i2c_regmap = { - .reg_bits = 8, - .val_bits = 8, - .max_register = CS4271_LASTREG, - - .reg_defaults = cs4271_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(cs4271_reg_defaults), - .cache_type = REGCACHE_RBTREE, - - .volatile_reg = cs4271_volatile_reg, -}; - -static int cs4271_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct cs4271_private *cs4271; - int ret; - - ret = cs4271_common_probe(&client->dev, &cs4271); + ret = cs4271_common_probe(dev, &cs4271); if (ret < 0) return ret; - i2c_set_clientdata(client, cs4271); - cs4271->regmap = devm_regmap_init_i2c(client, &cs4271_i2c_regmap); - if (IS_ERR(cs4271->regmap)) - return PTR_ERR(cs4271->regmap); + dev_set_drvdata(dev, cs4271); + cs4271->regmap = regmap; - return snd_soc_register_codec(&client->dev, &soc_codec_dev_cs4271, - &cs4271_dai, 1); -} - -static int cs4271_i2c_remove(struct i2c_client *client) -{ - snd_soc_unregister_codec(&client->dev); - return 0; -} - -static struct i2c_driver cs4271_i2c_driver = { - .driver = { - .name = "cs4271", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(cs4271_dt_ids), - }, - .id_table = cs4271_i2c_id, - .probe = cs4271_i2c_probe, - .remove = cs4271_i2c_remove, -}; -#endif /* IS_ENABLED(CONFIG_I2C) */ - -/* - * We only register our serial bus driver here without - * assignment to particular chip. So if any of the below - * fails, there is some problem with I2C or SPI subsystem. - * In most cases this module will be compiled with support - * of only one serial bus. - */ -static int __init cs4271_modinit(void) -{ - int ret; - -#if IS_ENABLED(CONFIG_I2C) - ret = i2c_add_driver(&cs4271_i2c_driver); - if (ret) { - pr_err("Failed to register CS4271 I2C driver: %d\n", ret); - return ret; - } -#endif - -#if defined(CONFIG_SPI_MASTER) - ret = spi_register_driver(&cs4271_spi_driver); - if (ret) { - pr_err("Failed to register CS4271 SPI driver: %d\n", ret); - return ret; - } -#endif - - return 0; -} -module_init(cs4271_modinit); - -static void __exit cs4271_modexit(void) -{ -#if defined(CONFIG_SPI_MASTER) - spi_unregister_driver(&cs4271_spi_driver); -#endif - -#if IS_ENABLED(CONFIG_I2C) - i2c_del_driver(&cs4271_i2c_driver); -#endif + return snd_soc_register_codec(dev, &soc_codec_dev_cs4271, &cs4271_dai, + 1); } -module_exit(cs4271_modexit); +EXPORT_SYMBOL_GPL(cs4271_probe); MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver"); diff --git a/sound/soc/codecs/cs4271.h b/sound/soc/codecs/cs4271.h new file mode 100644 index 00000000000..9adad8eefdc --- /dev/null +++ b/sound/soc/codecs/cs4271.h @@ -0,0 +1,11 @@ +#ifndef _CS4271_PRIV_H +#define _CS4271_PRIV_H + +#include <linux/regmap.h> + +extern const struct of_device_id cs4271_dt_ids[]; +extern const struct regmap_config cs4271_regmap_config; + +int cs4271_probe(struct device *dev, struct regmap *regmap); + +#endif diff --git a/sound/soc/codecs/cs42l51-i2c.c b/sound/soc/codecs/cs42l51-i2c.c index cee51ae177c..c40428f25ba 100644 --- a/sound/soc/codecs/cs42l51-i2c.c +++ b/sound/soc/codecs/cs42l51-i2c.c @@ -46,6 +46,7 @@ static struct i2c_driver cs42l51_i2c_driver = { .driver = { .name = "cs42l51", .owner = THIS_MODULE, + .of_match_table = cs42l51_of_match, }, .probe = cs42l51_i2c_probe, .remove = cs42l51_i2c_remove, diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index 09488d97de6..b3951524339 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -153,15 +153,17 @@ static const struct snd_kcontrol_new cs42l51_snd_controls[] = { static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + switch (event) { case SND_SOC_DAPM_PRE_PMD: - snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, + snd_soc_update_bits(codec, CS42L51_POWER_CTL1, CS42L51_POWER_CTL1_PDN, CS42L51_POWER_CTL1_PDN); break; default: case SND_SOC_DAPM_POST_PMD: - snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, + snd_soc_update_bits(codec, CS42L51_POWER_CTL1, CS42L51_POWER_CTL1_PDN, 0); break; } @@ -558,11 +560,13 @@ error: } EXPORT_SYMBOL_GPL(cs42l51_probe); -static const struct of_device_id cs42l51_of_match[] = { +const struct of_device_id cs42l51_of_match[] = { { .compatible = "cirrus,cs42l51", }, { } }; MODULE_DEVICE_TABLE(of, cs42l51_of_match); +EXPORT_SYMBOL_GPL(cs42l51_of_match); + MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h index 8c55bf384bc..0ca805492ac 100644 --- a/sound/soc/codecs/cs42l51.h +++ b/sound/soc/codecs/cs42l51.h @@ -22,6 +22,7 @@ struct device; extern const struct regmap_config cs42l51_regmap; int cs42l51_probe(struct device *dev, struct regmap *regmap); +extern const struct of_device_id cs42l51_of_match[]; #define CS42L51_CHIP_ID 0x1B #define CS42L51_CHIP_REV_A 0x00 diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 2f8b94683e8..7c55537c69c 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -584,7 +584,7 @@ static const struct snd_kcontrol_new cs42l73_snd_controls[] = { static int cs42l73_spklo_spk_amp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct cs42l73_private *priv = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMD: @@ -600,7 +600,7 @@ static int cs42l73_spklo_spk_amp_event(struct snd_soc_dapm_widget *w, static int cs42l73_ear_amp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct cs42l73_private *priv = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMD: @@ -618,7 +618,7 @@ static int cs42l73_ear_amp_event(struct snd_soc_dapm_widget *w, static int cs42l73_hp_amp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct cs42l73_private *priv = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMD: diff --git a/sound/soc/codecs/es8328-i2c.c b/sound/soc/codecs/es8328-i2c.c index aae410d122e..2d05b5d3a6c 100644 --- a/sound/soc/codecs/es8328-i2c.c +++ b/sound/soc/codecs/es8328-i2c.c @@ -19,7 +19,7 @@ #include "es8328.h" static const struct i2c_device_id es8328_id[] = { - { "everest,es8328", 0 }, + { "es8328", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, es8328_id); diff --git a/sound/soc/codecs/hdmi.c b/sound/soc/codecs/hdmi.c index 1087fd5f991..1391ad50f95 100644 --- a/sound/soc/codecs/hdmi.c +++ b/sound/soc/codecs/hdmi.c @@ -47,6 +47,7 @@ static struct snd_soc_dai_driver hdmi_codec_dai = { SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, }, .capture = { .stream_name = "Capture", @@ -75,6 +76,7 @@ static struct snd_soc_codec_driver hdmi_codec = { .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), .dapm_routes = hdmi_routes, .num_dapm_routes = ARRAY_SIZE(hdmi_routes), + .ignore_pmdown_time = true, }; static int hdmi_codec_probe(struct platform_device *pdev) diff --git a/sound/soc/codecs/lm49453.c b/sound/soc/codecs/lm49453.c index c1ae5764983..c4dfde9bdf1 100644 --- a/sound/soc/codecs/lm49453.c +++ b/sound/soc/codecs/lm49453.c @@ -1395,15 +1395,7 @@ static struct snd_soc_dai_driver lm49453_dai[] = { }, }; -/* power down chip */ -static int lm49453_remove(struct snd_soc_codec *codec) -{ - lm49453_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_lm49453 = { - .remove = lm49453_remove, .set_bias_level = lm49453_set_bias_level, .controls = lm49453_snd_controls, .num_controls = ARRAY_SIZE(lm49453_snd_controls), diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index 2cd3e542744..805b3f8cd39 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -875,7 +875,7 @@ static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = { static int max98088_mic_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec); switch (event) { @@ -905,7 +905,7 @@ static int max98088_mic_event(struct snd_soc_dapm_widget *w, static int max98088_line_pga(struct snd_soc_dapm_widget *w, int event, int line, u8 channel) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec); u8 *state; @@ -1887,25 +1887,6 @@ static void max98088_handle_pdata(struct snd_soc_codec *codec) max98088_handle_eq_pdata(codec); } -#ifdef CONFIG_PM -static int max98088_suspend(struct snd_soc_codec *codec) -{ - max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int max98088_resume(struct snd_soc_codec *codec) -{ - max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} -#else -#define max98088_suspend NULL -#define max98088_resume NULL -#endif - static int max98088_probe(struct snd_soc_codec *codec) { struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec); @@ -1946,9 +1927,6 @@ static int max98088_probe(struct snd_soc_codec *codec) snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV); - /* initialize registers cache to hardware default */ - max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00); snd_soc_write(codec, M98088_REG_22_MIX_DAC, @@ -1974,7 +1952,6 @@ static int max98088_remove(struct snd_soc_codec *codec) { struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec); - max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); kfree(max98088->eq_texts); return 0; @@ -1983,9 +1960,9 @@ static int max98088_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_max98088 = { .probe = max98088_probe, .remove = max98088_remove, - .suspend = max98088_suspend, - .resume = max98088_resume, .set_bias_level = max98088_set_bias_level, + .suspend_bias_off = true, + .controls = max98088_snd_controls, .num_controls = ARRAY_SIZE(max98088_snd_controls), .dapm_widgets = max98088_dapm_widgets, diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index d519294f57c..151f718241e 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -806,7 +806,7 @@ static const struct snd_kcontrol_new max98091_snd_controls[] = { static int max98090_micinput_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); unsigned int val = snd_soc_read(codec, w->reg); @@ -1311,6 +1311,10 @@ static const struct snd_soc_dapm_route max98090_dapm_routes[] = { {"MIC1 Input", NULL, "MIC1"}, {"MIC2 Input", NULL, "MIC2"}, + {"DMICL", NULL, "DMICL_ENA"}, + {"DMICL", NULL, "DMICR_ENA"}, + {"DMICR", NULL, "DMICL_ENA"}, + {"DMICR", NULL, "DMICR_ENA"}, {"DMICL", NULL, "AHPF"}, {"DMICR", NULL, "AHPF"}, @@ -1368,8 +1372,6 @@ static const struct snd_soc_dapm_route max98090_dapm_routes[] = { {"DMIC Mux", "ADC", "ADCR"}, {"DMIC Mux", "DMIC", "DMICL"}, {"DMIC Mux", "DMIC", "DMICR"}, - {"DMIC Mux", "DMIC", "DMICL_ENA"}, - {"DMIC Mux", "DMIC", "DMICR_ENA"}, {"LBENL Mux", "Normal", "DMIC Mux"}, {"LBENL Mux", "Loopback", "LTENL Mux"}, @@ -1395,8 +1397,8 @@ static const struct snd_soc_dapm_route max98090_dapm_routes[] = { {"STENL Mux", "Sidetone Left", "DMICL"}, {"STENR Mux", "Sidetone Right", "ADCR"}, {"STENR Mux", "Sidetone Right", "DMICR"}, - {"DACL", "NULL", "STENL Mux"}, - {"DACR", "NULL", "STENL Mux"}, + {"DACL", NULL, "STENL Mux"}, + {"DACR", NULL, "STENR Mux"}, {"AIFINL", NULL, "SHDN"}, {"AIFINR", NULL, "SHDN"}, @@ -1826,27 +1828,155 @@ static int max98090_set_bias_level(struct snd_soc_codec *codec, return 0; } -static const int comp_pclk_rates[] = { - 11289600, 12288000, 12000000, 13000000, 19200000 -}; - -static const int dmic_micclk[] = { - 2, 2, 2, 2, 4, 2 -}; +static const int dmic_divisors[] = { 2, 3, 4, 5, 6, 8 }; static const int comp_lrclk_rates[] = { 8000, 16000, 32000, 44100, 48000, 96000 }; -static const int dmic_comp[6][6] = { - {7, 8, 3, 3, 3, 3}, - {7, 8, 3, 3, 3, 3}, - {7, 8, 3, 3, 3, 3}, - {7, 8, 3, 1, 1, 1}, - {7, 8, 3, 1, 2, 2}, - {7, 8, 3, 3, 3, 3} +struct dmic_table { + int pclk; + struct { + int freq; + int comp[6]; /* One each for 8, 16, 32, 44.1, 48, and 96 kHz */ + } settings[6]; /* One for each dmic divisor. */ }; +static const struct dmic_table dmic_table[] = { /* One for each pclk freq. */ + { + .pclk = 11289600, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 6, 6, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + }, + }, + { + .pclk = 12000000, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + } + }, + { + .pclk = 12288000, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 6, 6, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + } + }, + { + .pclk = 13000000, + .settings = { + { .freq = 2, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 1, .comp = { 7, 8, 0, 0, 0, 0 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 4, 4, 5, 5 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + } + }, + { + .pclk = 19200000, + .settings = { + { .freq = 2, .comp = { 0, 0, 0, 0, 0, 0 } }, + { .freq = 1, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 2, 2, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + } + }, +}; + +static int max98090_find_divisor(int target_freq, int pclk) +{ + int current_diff = INT_MAX; + int test_diff = INT_MAX; + int divisor_index = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(dmic_divisors); i++) { + test_diff = abs(target_freq - (pclk / dmic_divisors[i])); + if (test_diff < current_diff) { + current_diff = test_diff; + divisor_index = i; + } + } + + return divisor_index; +} + +static int max98090_find_closest_pclk(int pclk) +{ + int m1; + int m2; + int i; + + for (i = 0; i < ARRAY_SIZE(dmic_table); i++) { + if (pclk == dmic_table[i].pclk) + return i; + if (pclk < dmic_table[i].pclk) { + if (i == 0) + return i; + m1 = pclk - dmic_table[i-1].pclk; + m2 = dmic_table[i].pclk - pclk; + if (m1 < m2) + return i - 1; + else + return i; + } + } + + return -EINVAL; +} + +static int max98090_configure_dmic(struct max98090_priv *max98090, + int target_dmic_clk, int pclk, int fs) +{ + int micclk_index; + int pclk_index; + int dmic_freq; + int dmic_comp; + int i; + + pclk_index = max98090_find_closest_pclk(pclk); + if (pclk_index < 0) + return pclk_index; + + micclk_index = max98090_find_divisor(target_dmic_clk, pclk); + + for (i = 0; i < ARRAY_SIZE(comp_lrclk_rates) - 1; i++) { + if (fs <= (comp_lrclk_rates[i] + comp_lrclk_rates[i+1]) / 2) + break; + } + + dmic_freq = dmic_table[pclk_index].settings[micclk_index].freq; + dmic_comp = dmic_table[pclk_index].settings[micclk_index].comp[i]; + + regmap_update_bits(max98090->regmap, M98090_REG_DIGITAL_MIC_ENABLE, + M98090_MICCLK_MASK, + micclk_index << M98090_MICCLK_SHIFT); + + regmap_update_bits(max98090->regmap, M98090_REG_DIGITAL_MIC_CONFIG, + M98090_DMIC_COMP_MASK | M98090_DMIC_FREQ_MASK, + dmic_comp << M98090_DMIC_COMP_SHIFT | + dmic_freq << M98090_DMIC_FREQ_SHIFT); + + return 0; +} + static int max98090_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1854,7 +1984,6 @@ static int max98090_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); struct max98090_cdata *cdata; - int i, j; cdata = &max98090->dai[0]; max98090->bclk = snd_soc_params_to_bclk(params); @@ -1893,27 +2022,8 @@ static int max98090_dai_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, M98090_REG_FILTER_CONFIG, M98090_DHF_MASK, M98090_DHF_MASK); - /* Check for supported PCLK to LRCLK ratios */ - for (j = 0; j < ARRAY_SIZE(comp_pclk_rates); j++) { - if (comp_pclk_rates[j] == max98090->sysclk) { - break; - } - } - - for (i = 0; i < ARRAY_SIZE(comp_lrclk_rates) - 1; i++) { - if (max98090->lrclk <= (comp_lrclk_rates[i] + - comp_lrclk_rates[i + 1]) / 2) { - break; - } - } - - snd_soc_update_bits(codec, M98090_REG_DIGITAL_MIC_ENABLE, - M98090_MICCLK_MASK, - dmic_micclk[j] << M98090_MICCLK_SHIFT); - - snd_soc_update_bits(codec, M98090_REG_DIGITAL_MIC_CONFIG, - M98090_DMIC_COMP_MASK, - dmic_comp[j][i] << M98090_DMIC_COMP_SHIFT); + max98090_configure_dmic(max98090, max98090->dmic_freq, max98090->pclk, + max98090->lrclk); return 0; } @@ -1941,15 +2051,18 @@ static int max98090_dai_set_sysclk(struct snd_soc_dai *dai, * 0x02 (when master clk is 20MHz to 40MHz).. * 0x03 (when master clk is 40MHz to 60MHz).. */ - if ((freq >= 10000000) && (freq < 20000000)) { + if ((freq >= 10000000) && (freq <= 20000000)) { snd_soc_write(codec, M98090_REG_SYSTEM_CLOCK, M98090_PSCLK_DIV1); - } else if ((freq >= 20000000) && (freq < 40000000)) { + max98090->pclk = freq; + } else if ((freq > 20000000) && (freq <= 40000000)) { snd_soc_write(codec, M98090_REG_SYSTEM_CLOCK, M98090_PSCLK_DIV2); - } else if ((freq >= 40000000) && (freq < 60000000)) { + max98090->pclk = freq >> 1; + } else if ((freq > 40000000) && (freq <= 60000000)) { snd_soc_write(codec, M98090_REG_SYSTEM_CLOCK, M98090_PSCLK_DIV4); + max98090->pclk = freq >> 2; } else { dev_err(codec->dev, "Invalid master clock frequency\n"); return -EINVAL; @@ -2324,6 +2437,7 @@ static int max98090_probe(struct snd_soc_codec *codec) /* Initialize private data */ max98090->sysclk = (unsigned)-1; + max98090->pclk = (unsigned)-1; max98090->master = false; cdata = &max98090->dai[0]; @@ -2463,6 +2577,11 @@ static int max98090_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, max98090); max98090->pdata = i2c->dev.platform_data; + ret = of_property_read_u32(i2c->dev.of_node, "maxim,dmic-freq", + &max98090->dmic_freq); + if (ret < 0) + max98090->dmic_freq = MAX98090_DEFAULT_DMIC_FREQ; + max98090->regmap = devm_regmap_init_i2c(i2c, &max98090_regmap); if (IS_ERR(max98090->regmap)) { ret = PTR_ERR(max98090->regmap); diff --git a/sound/soc/codecs/max98090.h b/sound/soc/codecs/max98090.h index a5f6bada06d..21ff743f5af 100644 --- a/sound/soc/codecs/max98090.h +++ b/sound/soc/codecs/max98090.h @@ -12,6 +12,12 @@ #define _MAX98090_H /* + * The default operating frequency for a DMIC attached to the codec. + * This can be overridden by a device tree property. + */ +#define MAX98090_DEFAULT_DMIC_FREQ 2500000 + +/* * MAX98090 Register Definitions */ @@ -1518,8 +1524,10 @@ struct max98090_priv { struct max98090_pdata *pdata; struct clk *mclk; unsigned int sysclk; + unsigned int pclk; unsigned int bclk; unsigned int lrclk; + u32 dmic_freq; struct max98090_cdata dai[1]; int jack_state; struct delayed_work jack_work; diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index 0ee6797d508..8fba0c3db79 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -16,6 +16,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/clk.h> +#include <linux/mutex.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -57,6 +58,7 @@ struct max98095_priv { unsigned int mic2pre; struct snd_soc_jack *headphone_jack; struct snd_soc_jack *mic_jack; + struct mutex lock; }; static const struct reg_default max98095_reg_def[] = { @@ -864,7 +866,7 @@ static const struct snd_kcontrol_new max98095_right_ADC_mixer_controls[] = { static int max98095_mic_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); switch (event) { @@ -894,7 +896,7 @@ static int max98095_mic_event(struct snd_soc_dapm_widget *w, static int max98095_line_pga(struct snd_soc_dapm_widget *w, int event, u8 channel) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); u8 *state; @@ -942,7 +944,7 @@ static int max98095_pga_in2_event(struct snd_soc_dapm_widget *w, static int max98095_lineout_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct snd_soc_codec *codec = w->codec; + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -1803,7 +1805,7 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, regsave = snd_soc_read(codec, M98095_088_CFG_LEVEL); snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, 0); - mutex_lock(&codec->mutex); + mutex_lock(&max98095->lock); snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); m98095_eq_band(codec, channel, 0, coef_set->band1); m98095_eq_band(codec, channel, 1, coef_set->band2); @@ -1811,7 +1813,7 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, m98095_eq_band(codec, channel, 3, coef_set->band4); m98095_eq_band(codec, channel, 4, coef_set->band5); snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, 0); - mutex_unlock(&codec->mutex); + mutex_unlock(&max98095->lock); /* Restore the original on/off state */ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, regsave); @@ -1957,12 +1959,12 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, regsave = snd_soc_read(codec, M98095_088_CFG_LEVEL); snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, 0); - mutex_lock(&codec->mutex); + mutex_lock(&max98095->lock); snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); m98095_biquad_band(codec, channel, 0, coef_set->band1); m98095_biquad_band(codec, channel, 1, coef_set->band2); snd_soc_update_bits(codec, M98095_00F_HOST_CFG, M98095_SEG, 0); - mutex_unlock(&codec->mutex); + mutex_unlock(&max98095->lock); /* Restore the original on/off state */ snd_soc_update_bits(codec, M98095_088_CFG_LEVEL, regmask, regsave); @@ -2317,9 +2319,6 @@ static int max98095_probe(struct snd_soc_codec *codec) snd_soc_write(codec, M98095_097_PWR_SYS, M98095_PWRSV); - /* initialize registers cache to hardware default */ - max98095_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - snd_soc_write(codec, M98095_048_MIX_DAC_LR, M98095_DAI1L_TO_DACL|M98095_DAI1R_TO_DACR); @@ -2359,8 +2358,6 @@ static int max98095_remove(struct snd_soc_codec *codec) struct max98095_priv *max98095 = snd_soc_codec_get_drvdata(codec); struct i2c_client *client = to_i2c_client(codec->dev); - max98095_set_bias_level(codec, SND_SOC_BIAS_OFF); - if (max98095->headphone_jack || max98095->mic_jack) max98095_jack_detect_disable(codec); @@ -2395,6 +2392,8 @@ static int max98095_i2c_probe(struct i2c_client *i2c, if (max98095 == NULL) return -ENOMEM; + mutex_init(&max98095->lock); + max98095->regmap = devm_regmap_init_i2c(i2c, &max98095_regmap); if (IS_ERR(max98095->regmap)) { ret = PTR_ERR(max98095->regmap); diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c index 4fdf5aaa236..10f8e47ce2c 100644 --- a/sound/soc/codecs/max9850.c +++ b/sound/soc/codecs/max9850.c @@ -291,25 +291,6 @@ static struct snd_soc_dai_driver max9850_dai = { .ops = &max9850_dai_ops, }; -#ifdef CONFIG_PM -static int max9850_suspend(struct snd_soc_codec *codec) -{ - max9850_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int max9850_resume(struct snd_soc_codec *codec) -{ - max9850_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} -#else -#define max9850_suspend NULL -#define max9850_resume NULL -#endif - static int max9850_probe(struct snd_soc_codec *codec) { /* enable zero-detect */ @@ -324,9 +305,8 @@ static int max9850_probe(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_max9850 = { .probe = max9850_probe, - .suspend = max9850_suspend, - .resume = max9850_resume, .set_bias_level = max9850_set_bias_level, + .suspend_bias_off = true, .controls = max9850_controls, .num_controls = ARRAY_SIZE(max9850_controls), diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 4aa555cbcca..2cd4fe46310 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -17,6 +17,7 @@ #include <linux/i2c.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> +#include <linux/dmi.h> #include <linux/acpi.h> #include <sound/core.h> #include <sound/pcm.h> @@ -36,11 +37,13 @@ struct rt286_priv { struct regmap *regmap; + struct snd_soc_codec *codec; struct rt286_platform_data pdata; struct i2c_client *i2c; struct snd_soc_jack *jack; struct delayed_work jack_detect_work; int sys_clk; + int clk_id; struct reg_default *index_cache; }; @@ -188,7 +191,7 @@ static int rt286_hw_write(void *context, unsigned int reg, unsigned int value) u8 data[4]; int ret, i; - /*handle index registers*/ + /* handle index registers */ if (reg <= 0xff) { rt286_hw_write(client, RT286_COEF_INDEX, reg); for (i = 0; i < INDEX_CACHE_SIZE; i++) { @@ -231,7 +234,7 @@ static int rt286_hw_read(void *context, unsigned int reg, unsigned int *value) __be32 be_reg; unsigned int index, vid, buf = 0x0; - /*handle index registers*/ + /* handle index registers */ if (reg <= 0xff) { rt286_hw_write(client, RT286_COEF_INDEX, reg); reg = RT286_PROC_COEF; @@ -298,7 +301,6 @@ static int rt286_support_power_controls[] = { static int rt286_jack_detect(struct rt286_priv *rt286, bool *hp, bool *mic) { unsigned int val, buf; - int i; *hp = false; *mic = false; @@ -309,67 +311,44 @@ static int rt286_jack_detect(struct rt286_priv *rt286, bool *hp, bool *mic) if (*hp) { /* power on HV,VERF */ regmap_update_bits(rt286->regmap, - RT286_POWER_CTRL1, 0x1001, 0x0); + RT286_DC_GAIN, 0x200, 0x200); + + snd_soc_dapm_force_enable_pin(&rt286->codec->dapm, + "HV"); + snd_soc_dapm_force_enable_pin(&rt286->codec->dapm, + "VREF"); /* power LDO1 */ - regmap_update_bits(rt286->regmap, - RT286_POWER_CTRL2, 0x4, 0x4); - regmap_write(rt286->regmap, RT286_SET_MIC1, 0x24); - regmap_read(rt286->regmap, RT286_CBJ_CTRL2, &val); + snd_soc_dapm_force_enable_pin(&rt286->codec->dapm, + "LDO1"); + snd_soc_dapm_sync(&rt286->codec->dapm); - msleep(200); - i = 40; - while (((val & 0x0800) == 0) && (i > 0)) { - regmap_read(rt286->regmap, - RT286_CBJ_CTRL2, &val); - i--; - msleep(20); - } + regmap_write(rt286->regmap, RT286_SET_MIC1, 0x24); + msleep(50); - if (0x0400 == (val & 0x0700)) { - *mic = false; + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xfcc0, 0xd400); + msleep(300); + regmap_read(rt286->regmap, RT286_CBJ_CTRL2, &val); - regmap_write(rt286->regmap, - RT286_SET_MIC1, 0x20); - /* power off HV,VERF */ - regmap_update_bits(rt286->regmap, - RT286_POWER_CTRL1, 0x1001, 0x1001); - regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL3, 0xc000, 0x0000); - regmap_update_bits(rt286->regmap, - RT286_CBJ_CTRL1, 0x0030, 0x0000); - regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL2, 0xc000, 0x0000); - } else if ((0x0200 == (val & 0x0700)) || - (0x0100 == (val & 0x0700))) { + if (0x0070 == (val & 0x0070)) { *mic = true; - regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL3, 0xc000, 0x8000); - regmap_update_bits(rt286->regmap, - RT286_CBJ_CTRL1, 0x0030, 0x0020); - regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL2, 0xc000, 0x8000); } else { - *mic = false; + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xfcc0, 0xe400); + msleep(300); + regmap_read(rt286->regmap, + RT286_CBJ_CTRL2, &val); + if (0x0070 == (val & 0x0070)) + *mic = true; + else + *mic = false; } - - regmap_update_bits(rt286->regmap, - RT286_MISC_CTRL1, - 0x0060, 0x0000); - } else { - regmap_update_bits(rt286->regmap, - RT286_MISC_CTRL1, - 0x0060, 0x0020); regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL3, - 0xc000, 0x8000); - regmap_update_bits(rt286->regmap, - RT286_CBJ_CTRL1, - 0x0030, 0x0020); - regmap_update_bits(rt286->regmap, - RT286_A_BIAS_CTRL2, - 0xc000, 0x8000); + RT286_DC_GAIN, 0x200, 0x0); + } else { *mic = false; + regmap_write(rt286->regmap, RT286_SET_MIC1, 0x20); } } else { regmap_read(rt286->regmap, RT286_GET_HP_SENSE, &buf); @@ -378,6 +357,12 @@ static int rt286_jack_detect(struct rt286_priv *rt286, bool *hp, bool *mic) *mic = buf & 0x80000000; } + snd_soc_dapm_disable_pin(&rt286->codec->dapm, "HV"); + snd_soc_dapm_disable_pin(&rt286->codec->dapm, "VREF"); + if (!*hp) + snd_soc_dapm_disable_pin(&rt286->codec->dapm, "LDO1"); + snd_soc_dapm_sync(&rt286->codec->dapm); + return 0; } @@ -415,6 +400,17 @@ int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack) } EXPORT_SYMBOL_GPL(rt286_mic_detect); +static int is_mclk_mode(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(source->codec); + + if (rt286->clk_id == RT286_SCLK_S_MCLK) + return 1; + else + return 0; +} + static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); @@ -568,7 +564,84 @@ static int rt286_adc_event(struct snd_soc_dapm_widget *w, return 0; } +static int rt286_vref_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, + RT286_CBJ_CTRL1, 0x0400, 0x0000); + mdelay(50); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_ldo2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, RT286_POWER_CTRL2, 0x38, 0x08); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, RT286_POWER_CTRL2, 0x38, 0x30); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_mic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, + RT286_A_BIAS_CTRL3, 0xc000, 0x8000); + snd_soc_update_bits(codec, + RT286_A_BIAS_CTRL2, 0xc000, 0x8000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, + RT286_A_BIAS_CTRL3, 0xc000, 0x0000); + snd_soc_update_bits(codec, + RT286_A_BIAS_CTRL2, 0xc000, 0x0000); + break; + default: + return 0; + } + + return 0; +} + static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("HV", 1, RT286_POWER_CTRL1, + 12, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF", RT286_POWER_CTRL1, + 0, 1, rt286_vref_event, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT286_POWER_CTRL2, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LDO2", 2, RT286_POWER_CTRL1, + 13, 1, rt286_ldo2_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("MCLK MODE", RT286_PLL_CTRL1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1 Input Buffer", SND_SOC_NOPM, + 0, 0, rt286_mic1_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + /* Input Lines */ SND_SOC_DAPM_INPUT("DMIC1 Pin"), SND_SOC_DAPM_INPUT("DMIC2 Pin"), @@ -642,6 +715,25 @@ static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = { }; static const struct snd_soc_dapm_route rt286_dapm_routes[] = { + {"ADC 0", NULL, "MCLK MODE", is_mclk_mode}, + {"ADC 1", NULL, "MCLK MODE", is_mclk_mode}, + {"Front", NULL, "MCLK MODE", is_mclk_mode}, + {"Surround", NULL, "MCLK MODE", is_mclk_mode}, + + {"HP Power", NULL, "LDO1"}, + {"HP Power", NULL, "LDO2"}, + + {"MIC1", NULL, "LDO1"}, + {"MIC1", NULL, "LDO2"}, + {"MIC1", NULL, "HV"}, + {"MIC1", NULL, "VREF"}, + {"MIC1", NULL, "MIC1 Input Buffer"}, + + {"SPO", NULL, "LDO1"}, + {"SPO", NULL, "LDO2"}, + {"SPO", NULL, "HV"}, + {"SPO", NULL, "VREF"}, + {"DMIC1", NULL, "DMIC1 Pin"}, {"DMIC2", NULL, "DMIC2 Pin"}, {"DMIC1", NULL, "DMIC Receiver"}, @@ -880,6 +972,7 @@ static int rt286_set_dai_sysclk(struct snd_soc_dai *dai, } rt286->sys_clk = freq; + rt286->clk_id = clk_id; return 0; } @@ -915,13 +1008,18 @@ static int rt286_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_ON: mdelay(10); + snd_soc_update_bits(codec, + RT286_CBJ_CTRL1, 0x0400, 0x0400); + snd_soc_update_bits(codec, + RT286_DC_GAIN, 0x200, 0x0); + break; case SND_SOC_BIAS_STANDBY: snd_soc_write(codec, RT286_SET_AUDIO_POWER, AC_PWRST_D3); snd_soc_update_bits(codec, - RT286_DC_GAIN, 0x200, 0x0); + RT286_CBJ_CTRL1, 0x0400, 0x0000); break; default: @@ -962,6 +1060,7 @@ static int rt286_probe(struct snd_soc_codec *codec) { struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + rt286->codec = codec; codec->dapm.bias_level = SND_SOC_BIAS_OFF; if (rt286->i2c->irq) { @@ -1107,6 +1206,16 @@ static const struct acpi_device_id rt286_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, rt286_acpi_match); +static struct dmi_system_id force_combo_jack_table[] = { + { + .ident = "Intel Wilson Beach", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "Wilson Beach SDS") + } + }, + { } +}; + static int rt286_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1142,6 +1251,9 @@ static int rt286_i2c_probe(struct i2c_client *i2c, if (pdata) rt286->pdata = *pdata; + if (dmi_check_system(force_combo_jack_table)) + rt286->pdata.cbj_en = true; + regmap_write(rt286->regmap, RT286_SET_AUDIO_POWER, AC_PWRST_D3); for (i = 0; i < RT286_POWER_REG_LEN; i++) @@ -1152,7 +1264,6 @@ static int rt286_i2c_probe(struct i2c_client *i2c, if (!rt286->pdata.cbj_en) { regmap_write(rt286->regmap, RT286_CBJ_CTRL2, 0x0000); regmap_write(rt286->regmap, RT286_MIC1_DET_CTRL, 0x0816); - regmap_write(rt286->regmap, RT286_MISC_CTRL1, 0x0000); regmap_update_bits(rt286->regmap, RT286_CBJ_CTRL1, 0xf000, 0xb000); } else { @@ -1169,10 +1280,12 @@ static int rt286_i2c_probe(struct i2c_client *i2c, mdelay(10); - /*Power down LDO2*/ - regmap_update_bits(rt286->regmap, RT286_POWER_CTRL2, 0x8, 0x0); + regmap_write(rt286->regmap, RT286_MISC_CTRL1, 0x0000); + /* Power down LDO, VREF */ + regmap_update_bits(rt286->regmap, RT286_POWER_CTRL2, 0xc, 0x0); + regmap_update_bits(rt286->regmap, RT286_POWER_CTRL1, 0x1001, 0x1001); - /*Set depop parameter*/ + /* Set depop parameter */ regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL2, 0x403a, 0x401a); regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL3, 0xf777, 0x4737); regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL4, 0x00ff, 0x003f); diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index 1ba27db660a..6d7b7ca7d53 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -1612,29 +1612,6 @@ static int rt5631_probe(struct snd_soc_codec *codec) return 0; } -static int rt5631_remove(struct snd_soc_codec *codec) -{ - rt5631_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -#ifdef CONFIG_PM -static int rt5631_suspend(struct snd_soc_codec *codec) -{ - rt5631_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int rt5631_resume(struct snd_soc_codec *codec) -{ - rt5631_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define rt5631_suspend NULL -#define rt5631_resume NULL -#endif - #define RT5631_STEREO_RATES SNDRV_PCM_RATE_8000_96000 #define RT5631_FORMAT (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -1672,10 +1649,8 @@ static struct snd_soc_dai_driver rt5631_dai[] = { static struct snd_soc_codec_driver soc_codec_dev_rt5631 = { .probe = rt5631_probe, - .remove = rt5631_remove, - .suspend = rt5631_suspend, - .resume = rt5631_resume, .set_bias_level = rt5631_set_bias_level, + .suspend_bias_off = true, .controls = rt5631_snd_controls, .num_controls = ARRAY_SIZE(rt5631_snd_controls), .dapm_widgets = rt5631_dapm_widgets, @@ -1686,10 +1661,20 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5631 = { static const struct i2c_device_id rt5631_i2c_id[] = { { "rt5631", 0 }, + { "alc5631", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, rt5631_i2c_id); +#ifdef CONFIG_OF +static struct of_device_id rt5631_i2c_dt_ids[] = { + { .compatible = "realtek,rt5631"}, + { .compatible = "realtek,alc5631"}, + { } +}; +MODULE_DEVICE_TABLE(of, rt5631_i2c_dt_ids); +#endif + static const struct regmap_config rt5631_regmap_config = { .reg_bits = 8, .val_bits = 16, @@ -1734,6 +1719,7 @@ static struct i2c_driver rt5631_i2c_driver = { .driver = { .name = "rt5631", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rt5631_i2c_dt_ids), }, .probe = rt5631_i2c_probe, .remove = rt5631_i2c_remove, diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 3fb83bf0976..a7789a8726e 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -139,6 +139,7 @@ static const struct reg_default rt5645_reg[] = { { 0x76, 0x000a }, { 0x77, 0x0c00 }, { 0x78, 0x0000 }, + { 0x79, 0x0123 }, { 0x80, 0x0000 }, { 0x81, 0x0000 }, { 0x82, 0x0000 }, @@ -334,6 +335,7 @@ static bool rt5645_readable_register(struct device *dev, unsigned int reg) case RT5645_DMIC_CTRL2: case RT5645_TDM_CTRL_1: case RT5645_TDM_CTRL_2: + case RT5645_TDM_CTRL_3: case RT5645_GLB_CLK: case RT5645_PLL_CTRL1: case RT5645_PLL_CTRL2: @@ -552,6 +554,53 @@ static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, return 0; } +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + + switch (source->shift) { + case 0: + reg = RT5645_ASRC_3; + shift = 0; + break; + case 1: + reg = RT5645_ASRC_3; + shift = 4; + break; + case 3: + reg = RT5645_ASRC_2; + shift = 0; + break; + case 8: + reg = RT5645_ASRC_2; + shift = 4; + break; + case 9: + reg = RT5645_ASRC_2; + shift = 8; + break; + case 10: + reg = RT5645_ASRC_2; + shift = 12; + break; + default: + return 0; + } + + val = (snd_soc_read(source->codec, reg) >> shift) & 0xf; + switch (val) { + case 1: + case 2: + case 3: + case 4: + return 1; + default: + return 0; + } + +} + /* Digital Mixer */ static const struct snd_kcontrol_new rt5645_sto1_adc_l_mix[] = { SOC_DAPM_SINGLE("ADC1 Switch", RT5645_STO1_ADC_MIXER, @@ -1244,6 +1293,30 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5645_PWR_VOL, RT5645_PWR_MIC_DET_BIT, 0, NULL, 0), + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5645_ASRC_1, + 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5645_ASRC_1, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO ASRC", 1, RT5645_ASRC_1, + 10, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO L ASRC", 1, RT5645_ASRC_1, + 9, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO R ASRC", 1, RT5645_ASRC_1, + 8, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5645_ASRC_1, + 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5645_ASRC_1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5645_ASRC_1, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5645_ASRC_1, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO L ASRC", 1, RT5645_ASRC_1, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO R ASRC", 1, RT5645_ASRC_1, + 0, 0, NULL, 0), + /* Input Side */ /* micbias */ SND_SOC_DAPM_MICBIAS("micbias1", RT5645_PWR_ANLG2, @@ -1502,6 +1575,17 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { }; static const struct snd_soc_dapm_route rt5645_dapm_routes[] = { + { "adc stereo1 filter", NULL, "ADC STO1 ASRC", is_using_asrc }, + { "adc stereo2 filter", NULL, "ADC STO2 ASRC", is_using_asrc }, + { "adc mono left filter", NULL, "ADC MONO L ASRC", is_using_asrc }, + { "adc mono right filter", NULL, "ADC MONO R ASRC", is_using_asrc }, + { "dac mono left filter", NULL, "DAC MONO L ASRC", is_using_asrc }, + { "dac mono right filter", NULL, "DAC MONO R ASRC", is_using_asrc }, + { "dac stereo1 filter", NULL, "DAC STO ASRC", is_using_asrc }, + + { "I2S1", NULL, "I2S1 ASRC" }, + { "I2S2", NULL, "I2S2 ASRC" }, + { "IN1P", NULL, "LDO2" }, { "IN2P", NULL, "LDO2" }, @@ -1548,12 +1632,15 @@ static const struct snd_soc_dapm_route rt5645_dapm_routes[] = { { "Stereo1 DMIC Mux", "DMIC1", "DMIC1" }, { "Stereo1 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo1 DMIC Mux", NULL, "DMIC STO1 ASRC" }, { "Mono DMIC L Mux", "DMIC1", "DMIC L1" }, { "Mono DMIC L Mux", "DMIC2", "DMIC L2" }, + { "Mono DMIC L Mux", NULL, "DMIC MONO L ASRC" }, { "Mono DMIC R Mux", "DMIC1", "DMIC R1" }, { "Mono DMIC R Mux", "DMIC2", "DMIC R2" }, + { "Mono DMIC R Mux", NULL, "DMIC MONO R ASRC" }, { "Stereo1 ADC L2 Mux", "DMIC", "Stereo1 DMIC Mux" }, { "Stereo1 ADC L2 Mux", "DAC MIX", "DAC MIXL" }, @@ -2027,8 +2114,11 @@ static int rt5645_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, struct snd_soc_codec *codec = dai->codec; unsigned int val = 0; - if (rx_mask || tx_mask) + if (rx_mask || tx_mask) { val |= (1 << 14); + snd_soc_update_bits(codec, RT5645_BASS_BACK, + RT5645_G_BB_BST_MASK, RT5645_G_BB_BST_25DB); + } switch (slots) { case 4: @@ -2069,8 +2159,8 @@ static int rt5645_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { - case SND_SOC_BIAS_STANDBY: - if (SND_SOC_BIAS_OFF == codec->dapm.bias_level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == codec->dapm.bias_level) { snd_soc_update_bits(codec, RT5645_PWR_ANLG1, RT5645_PWR_VREF1 | RT5645_PWR_MB | RT5645_PWR_BG | RT5645_PWR_VREF2, @@ -2085,15 +2175,24 @@ static int rt5645_set_bias_level(struct snd_soc_codec *codec, } break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, RT5645_PWR_ANLG1, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2); + snd_soc_update_bits(codec, RT5645_PWR_ANLG1, + RT5645_PWR_FV1 | RT5645_PWR_FV2, + RT5645_PWR_FV1 | RT5645_PWR_FV2); + break; + case SND_SOC_BIAS_OFF: snd_soc_write(codec, RT5645_DEPOP_M2, 0x1100); snd_soc_write(codec, RT5645_GEN_CTRL1, 0x0128); - snd_soc_write(codec, RT5645_PWR_DIG1, 0x0000); - snd_soc_write(codec, RT5645_PWR_DIG2, 0x0000); - snd_soc_write(codec, RT5645_PWR_VOL, 0x0000); - snd_soc_write(codec, RT5645_PWR_MIXER, 0x0000); - snd_soc_write(codec, RT5645_PWR_ANLG1, 0x0000); - snd_soc_write(codec, RT5645_PWR_ANLG2, 0x0000); + snd_soc_update_bits(codec, RT5645_PWR_ANLG1, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2 | + RT5645_PWR_FV1 | RT5645_PWR_FV2, 0x0); break; default: @@ -2104,8 +2203,7 @@ static int rt5645_set_bias_level(struct snd_soc_codec *codec, return 0; } -static int rt5645_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *jack) +static int rt5645_jack_detect(struct snd_soc_codec *codec) { struct rt5645_priv *rt5645 = snd_soc_codec_get_drvdata(codec); int gpio_state, jack_type = 0; @@ -2143,34 +2241,44 @@ static int rt5645_jack_detect(struct snd_soc_codec *codec, snd_soc_dapm_disable_pin(&codec->dapm, "micbias1"); snd_soc_dapm_disable_pin(&codec->dapm, "micbias2"); - snd_soc_dapm_disable_pin(&codec->dapm, "LDO2"); + if (rt5645->pdata.jd_mode == 0) + snd_soc_dapm_disable_pin(&codec->dapm, "LDO2"); snd_soc_dapm_disable_pin(&codec->dapm, "Mic Det Power"); snd_soc_dapm_sync(&codec->dapm); } - snd_soc_jack_report(rt5645->jack, jack_type, SND_JACK_HEADSET); - + snd_soc_jack_report(rt5645->hp_jack, jack_type, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, jack_type, SND_JACK_MICROPHONE); return 0; } int rt5645_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *jack) + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack) { struct rt5645_priv *rt5645 = snd_soc_codec_get_drvdata(codec); - rt5645->jack = jack; - - rt5645_jack_detect(codec, rt5645->jack); + rt5645->hp_jack = hp_jack; + rt5645->mic_jack = mic_jack; + rt5645_jack_detect(codec); return 0; } EXPORT_SYMBOL_GPL(rt5645_set_jack_detect); +static void rt5645_jack_detect_work(struct work_struct *work) +{ + struct rt5645_priv *rt5645 = + container_of(work, struct rt5645_priv, jack_detect_work.work); + + rt5645_jack_detect(rt5645->codec); +} + static irqreturn_t rt5645_irq(int irq, void *data) { struct rt5645_priv *rt5645 = data; - rt5645_jack_detect(rt5645->codec, rt5645->jack); + queue_delayed_work(system_power_efficient_wq, + &rt5645->jack_detect_work, msecs_to_jiffies(250)); return IRQ_HANDLED; } @@ -2185,6 +2293,13 @@ static int rt5645_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, RT5645_CHARGE_PUMP, 0x0300, 0x0200); + /* for JD function */ + if (rt5645->pdata.en_jd_func) { + snd_soc_dapm_force_enable_pin(&codec->dapm, "JD Power"); + snd_soc_dapm_force_enable_pin(&codec->dapm, "LDO2"); + snd_soc_dapm_sync(&codec->dapm); + } + return 0; } @@ -2418,6 +2533,51 @@ static int rt5645_i2c_probe(struct i2c_client *i2c, } + if (rt5645->pdata.en_jd_func) { + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3, + RT5645_IRQ_CLK_GATE_CTRL | RT5645_MICINDET_MANU, + RT5645_IRQ_CLK_GATE_CTRL | RT5645_MICINDET_MANU); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1, + RT5645_CBJ_BST1_EN, RT5645_CBJ_BST1_EN); + regmap_update_bits(rt5645->regmap, RT5645_JD_CTRL3, + RT5645_JD_CBJ_EN | RT5645_JD_CBJ_POL, + RT5645_JD_CBJ_EN | RT5645_JD_CBJ_POL); + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_IRQ_CLK_INT, RT5645_IRQ_CLK_INT); + } + + if (rt5645->pdata.jd_mode) { + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_IRQ_JD_1_1_EN, RT5645_IRQ_JD_1_1_EN); + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3, + RT5645_JD_PSV_MODE, RT5645_JD_PSV_MODE); + regmap_update_bits(rt5645->regmap, RT5645_HPO_MIXER, + RT5645_IRQ_PSV_MODE, RT5645_IRQ_PSV_MODE); + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_MIC2_OVCD_EN, RT5645_MIC2_OVCD_EN); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP1_PIN_IRQ, RT5645_GP1_PIN_IRQ); + switch (rt5645->pdata.jd_mode) { + case 1: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_0); + break; + case 2: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_1); + break; + case 3: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_2); + break; + default: + break; + } + } + if (rt5645->i2c->irq) { ret = request_threaded_irq(rt5645->i2c->irq, NULL, rt5645_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING @@ -2436,6 +2596,8 @@ static int rt5645_i2c_probe(struct i2c_client *i2c, dev_err(&i2c->dev, "Fail gpio_direction hp_det_gpio\n"); } + INIT_DELAYED_WORK(&rt5645->jack_detect_work, rt5645_jack_detect_work); + return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5645, rt5645_dai, ARRAY_SIZE(rt5645_dai)); } @@ -2447,6 +2609,8 @@ static int rt5645_i2c_remove(struct i2c_client *i2c) if (i2c->irq) free_irq(i2c->irq, rt5645); + cancel_delayed_work_sync(&rt5645->jack_detect_work); + if (gpio_is_valid(rt5645->pdata.hp_det_gpio)) gpio_free(rt5645->pdata.hp_det_gpio); diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h index 50c62c5668e..a815e36a2bd 100644 --- a/sound/soc/codecs/rt5645.h +++ b/sound/soc/codecs/rt5645.h @@ -594,6 +594,7 @@ #define RT5645_M_DAC1_HM_SFT 14 #define RT5645_M_HPVOL_HM (0x1 << 13) #define RT5645_M_HPVOL_HM_SFT 13 +#define RT5645_IRQ_PSV_MODE (0x1 << 12) /* SPK Left Mixer Control (0x46) */ #define RT5645_G_RM_L_SM_L_MASK (0x3 << 14) @@ -1348,6 +1349,12 @@ #define RT5645_PWR_CLK25M_SFT 4 #define RT5645_PWR_CLK25M_PD (0x0 << 4) #define RT5645_PWR_CLK25M_PU (0x1 << 4) +#define RT5645_IRQ_CLK_MCLK (0x0 << 3) +#define RT5645_IRQ_CLK_INT (0x1 << 3) +#define RT5645_JD1_MODE_MASK (0x3 << 0) +#define RT5645_JD1_MODE_0 (0x0 << 0) +#define RT5645_JD1_MODE_1 (0x1 << 0) +#define RT5645_JD1_MODE_2 (0x2 << 0) /* VAD Control 4 (0x9d) */ #define RT5645_VAD_SEL_MASK (0x3 << 8) @@ -1636,6 +1643,7 @@ #define RT5645_OT_P_SFT 10 #define RT5645_OT_P_NOR (0x0 << 10) #define RT5645_OT_P_INV (0x1 << 10) +#define RT5645_IRQ_JD_1_1_EN (0x1 << 9) /* IRQ Control 2 (0xbe) */ #define RT5645_IRQ_MB1_OC_MASK (0x1 << 15) @@ -1853,6 +1861,7 @@ #define RT5645_M_BB_HPF_R_SFT 6 #define RT5645_G_BB_BST_MASK (0x3f) #define RT5645_G_BB_BST_SFT 0 +#define RT5645_G_BB_BST_25DB 0x14 /* MP3 Plus Control 1 (0xd0) */ #define RT5645_M_MP3_L_MASK (0x1 << 15) @@ -2116,6 +2125,10 @@ enum { #define RT5645_RXDP2_SEL_ADC (0x1 << 3) #define RT5645_RXDP2_SEL_SFT (3) +/* General Control3 (0xfc) */ +#define RT5645_JD_PSV_MODE (0x1 << 12) +#define RT5645_IRQ_CLK_GATE_CTRL (0x1 << 11) +#define RT5645_MICINDET_MANU (0x1 << 7) /* Vendor ID (0xfd) */ #define RT5645_VER_C 0x2 @@ -2167,7 +2180,9 @@ struct rt5645_priv { struct rt5645_platform_data pdata; struct regmap *regmap; struct i2c_client *i2c; - struct snd_soc_jack *jack; + struct snd_soc_jack *hp_jack; + struct snd_soc_jack *mic_jack; + struct delayed_work jack_detect_work; int sysclk; int sysclk_src; @@ -2181,6 +2196,6 @@ struct rt5645_priv { }; int rt5645_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *jack); + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack); #endif /* __RT5645_H__ */ diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index ba9d9b4d485..8a0833de166 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -16,6 +16,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/acpi.h> #include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> @@ -100,18 +101,18 @@ static const struct reg_default rt5670_reg[] = { { 0x4c, 0x5380 }, { 0x4f, 0x0073 }, { 0x52, 0x00d3 }, - { 0x53, 0xf0f0 }, + { 0x53, 0xf000 }, { 0x61, 0x0000 }, { 0x62, 0x0001 }, { 0x63, 0x00c3 }, { 0x64, 0x0000 }, - { 0x65, 0x0000 }, + { 0x65, 0x0001 }, { 0x66, 0x0000 }, { 0x6f, 0x8000 }, { 0x70, 0x8000 }, { 0x71, 0x8000 }, { 0x72, 0x8000 }, - { 0x73, 0x1110 }, + { 0x73, 0x7770 }, { 0x74, 0x0e00 }, { 0x75, 0x1505 }, { 0x76, 0x0015 }, @@ -125,21 +126,21 @@ static const struct reg_default rt5670_reg[] = { { 0x83, 0x0000 }, { 0x84, 0x0000 }, { 0x85, 0x0000 }, - { 0x86, 0x0008 }, + { 0x86, 0x0004 }, { 0x87, 0x0000 }, { 0x88, 0x0000 }, { 0x89, 0x0000 }, { 0x8a, 0x0000 }, { 0x8b, 0x0000 }, - { 0x8c, 0x0007 }, + { 0x8c, 0x0003 }, { 0x8d, 0x0000 }, { 0x8e, 0x0004 }, { 0x8f, 0x1100 }, { 0x90, 0x0646 }, { 0x91, 0x0c06 }, { 0x93, 0x0000 }, - { 0x94, 0x0000 }, - { 0x95, 0x0000 }, + { 0x94, 0x1270 }, + { 0x95, 0x1000 }, { 0x97, 0x0000 }, { 0x98, 0x0000 }, { 0x99, 0x0000 }, @@ -150,11 +151,11 @@ static const struct reg_default rt5670_reg[] = { { 0x9e, 0x0400 }, { 0xae, 0x7000 }, { 0xaf, 0x0000 }, - { 0xb0, 0x6000 }, + { 0xb0, 0x7000 }, { 0xb1, 0x0000 }, { 0xb2, 0x0000 }, { 0xb3, 0x001f }, - { 0xb4, 0x2206 }, + { 0xb4, 0x220c }, { 0xb5, 0x1f00 }, { 0xb6, 0x0000 }, { 0xb7, 0x0000 }, @@ -171,25 +172,25 @@ static const struct reg_default rt5670_reg[] = { { 0xcf, 0x1813 }, { 0xd0, 0x0690 }, { 0xd1, 0x1c17 }, - { 0xd3, 0xb320 }, + { 0xd3, 0xa220 }, { 0xd4, 0x0000 }, { 0xd6, 0x0400 }, { 0xd9, 0x0809 }, { 0xda, 0x0000 }, { 0xdb, 0x0001 }, { 0xdc, 0x0049 }, - { 0xdd, 0x0009 }, + { 0xdd, 0x0024 }, { 0xe6, 0x8000 }, { 0xe7, 0x0000 }, - { 0xec, 0xb300 }, + { 0xec, 0xa200 }, { 0xed, 0x0000 }, - { 0xee, 0xb300 }, + { 0xee, 0xa200 }, { 0xef, 0x0000 }, { 0xf8, 0x0000 }, { 0xf9, 0x0000 }, { 0xfa, 0x8010 }, { 0xfb, 0x0033 }, - { 0xfc, 0x0080 }, + { 0xfc, 0x0100 }, }; static bool rt5670_volatile_register(struct device *dev, unsigned int reg) @@ -575,6 +576,18 @@ static int is_using_asrc(struct snd_soc_dapm_widget *source, } +static int can_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + + if (rt5670->sysclk > rt5670->lrck[RT5670_AIF1] * 384) + return 1; + + return 0; +} + /* Digital Mixer */ static const struct snd_kcontrol_new rt5670_sto1_adc_l_mix[] = { SOC_DAPM_SINGLE("ADC1 Switch", RT5670_STO1_ADC_MIXER, @@ -1281,6 +1294,14 @@ static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { 9, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("DAC MONO R ASRC", 1, RT5670_ASRC_1, 8, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5670_ASRC_1, + 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO2 ASRC", 1, RT5670_ASRC_1, + 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5670_ASRC_1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5670_ASRC_1, + 4, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5670_ASRC_1, 3, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ADC STO2 ASRC", 1, RT5670_ASRC_1, @@ -1595,29 +1616,40 @@ static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { /* PDM */ SND_SOC_DAPM_SUPPLY("PDM1 Power", RT5670_PWR_DIG2, RT5670_PWR_PDM1_BIT, 0, NULL, 0), - SND_SOC_DAPM_SUPPLY("PDM2 Power", RT5670_PWR_DIG2, - RT5670_PWR_PDM2_BIT, 0, NULL, 0), SND_SOC_DAPM_MUX("PDM1 L Mux", RT5670_PDM_OUT_CTRL, RT5670_M_PDM1_L_SFT, 1, &rt5670_pdm1_l_mux), SND_SOC_DAPM_MUX("PDM1 R Mux", RT5670_PDM_OUT_CTRL, RT5670_M_PDM1_R_SFT, 1, &rt5670_pdm1_r_mux), - SND_SOC_DAPM_MUX("PDM2 L Mux", RT5670_PDM_OUT_CTRL, - RT5670_M_PDM2_L_SFT, 1, &rt5670_pdm2_l_mux), - SND_SOC_DAPM_MUX("PDM2 R Mux", RT5670_PDM_OUT_CTRL, - RT5670_M_PDM2_R_SFT, 1, &rt5670_pdm2_r_mux), /* Output Lines */ SND_SOC_DAPM_OUTPUT("HPOL"), SND_SOC_DAPM_OUTPUT("HPOR"), SND_SOC_DAPM_OUTPUT("LOUTL"), SND_SOC_DAPM_OUTPUT("LOUTR"), +}; + +static const struct snd_soc_dapm_widget rt5670_specific_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PDM2 Power", RT5670_PWR_DIG2, + RT5670_PWR_PDM2_BIT, 0, NULL, 0), + SND_SOC_DAPM_MUX("PDM2 L Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM2_L_SFT, 1, &rt5670_pdm2_l_mux), + SND_SOC_DAPM_MUX("PDM2 R Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM2_R_SFT, 1, &rt5670_pdm2_r_mux), SND_SOC_DAPM_OUTPUT("PDM1L"), SND_SOC_DAPM_OUTPUT("PDM1R"), SND_SOC_DAPM_OUTPUT("PDM2L"), SND_SOC_DAPM_OUTPUT("PDM2R"), }; +static const struct snd_soc_dapm_widget rt5672_specific_dapm_widgets[] = { + SND_SOC_DAPM_PGA("SPO Amp", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("SPOLP"), + SND_SOC_DAPM_OUTPUT("SPOLN"), + SND_SOC_DAPM_OUTPUT("SPORP"), + SND_SOC_DAPM_OUTPUT("SPORN"), +}; + static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc }, { "ADC Stereo2 Filter", NULL, "ADC STO2 ASRC", is_using_asrc }, @@ -1626,9 +1658,13 @@ static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "DAC Mono Left Filter", NULL, "DAC MONO L ASRC", is_using_asrc }, { "DAC Mono Right Filter", NULL, "DAC MONO R ASRC", is_using_asrc }, { "DAC Stereo1 Filter", NULL, "DAC STO ASRC", is_using_asrc }, + { "Stereo1 DMIC Mux", NULL, "DMIC STO1 ASRC", can_use_asrc }, + { "Stereo2 DMIC Mux", NULL, "DMIC STO2 ASRC", can_use_asrc }, + { "Mono DMIC L Mux", NULL, "DMIC MONO L ASRC", can_use_asrc }, + { "Mono DMIC R Mux", NULL, "DMIC MONO R ASRC", can_use_asrc }, - { "I2S1", NULL, "I2S1 ASRC" }, - { "I2S2", NULL, "I2S2 ASRC" }, + { "I2S1", NULL, "I2S1 ASRC", can_use_asrc}, + { "I2S2", NULL, "I2S2 ASRC", can_use_asrc}, { "DMIC1", NULL, "DMIC L1" }, { "DMIC1", NULL, "DMIC R1" }, @@ -1877,6 +1913,10 @@ static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "DAC1 MIXR", "DAC1 Switch", "DAC1 R Mux" }, { "DAC1 MIXR", NULL, "DAC Stereo1 Filter" }, + { "DAC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC Mono Left Filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC Mono Right Filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC MIX", NULL, "DAC1 MIXL" }, { "DAC MIX", NULL, "DAC1 MIXR" }, @@ -1926,14 +1966,10 @@ static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "DAC L1", NULL, "DAC L1 Power" }, { "DAC L1", NULL, "Stereo DAC MIXL" }, - { "DAC L1", NULL, "PLL1", is_sys_clk_from_pll }, { "DAC R1", NULL, "DAC R1 Power" }, { "DAC R1", NULL, "Stereo DAC MIXR" }, - { "DAC R1", NULL, "PLL1", is_sys_clk_from_pll }, { "DAC L2", NULL, "Mono DAC MIXL" }, - { "DAC L2", NULL, "PLL1", is_sys_clk_from_pll }, { "DAC R2", NULL, "Mono DAC MIXR" }, - { "DAC R2", NULL, "PLL1", is_sys_clk_from_pll }, { "OUT MIXL", "BST1 Switch", "BST1" }, { "OUT MIXL", "INL Switch", "INL VOL" }, @@ -1970,12 +2006,6 @@ static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "PDM1 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, { "PDM1 R Mux", "Mono DAC", "Mono DAC MIXR" }, { "PDM1 R Mux", NULL, "PDM1 Power" }, - { "PDM2 L Mux", "Stereo DAC", "Stereo DAC MIXL" }, - { "PDM2 L Mux", "Mono DAC", "Mono DAC MIXL" }, - { "PDM2 L Mux", NULL, "PDM2 Power" }, - { "PDM2 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, - { "PDM2 R Mux", "Mono DAC", "Mono DAC MIXR" }, - { "PDM2 R Mux", NULL, "PDM2 Power" }, { "HP Amp", NULL, "HPO MIX" }, { "HP Amp", NULL, "Mic Det Power" }, @@ -1993,13 +2023,30 @@ static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { { "LOUTR", NULL, "LOUT R Playback" }, { "LOUTL", NULL, "Improve HP Amp Drv" }, { "LOUTR", NULL, "Improve HP Amp Drv" }, +}; +static const struct snd_soc_dapm_route rt5670_specific_dapm_routes[] = { + { "PDM2 L Mux", "Stereo DAC", "Stereo DAC MIXL" }, + { "PDM2 L Mux", "Mono DAC", "Mono DAC MIXL" }, + { "PDM2 L Mux", NULL, "PDM2 Power" }, + { "PDM2 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, + { "PDM2 R Mux", "Mono DAC", "Mono DAC MIXR" }, + { "PDM2 R Mux", NULL, "PDM2 Power" }, { "PDM1L", NULL, "PDM1 L Mux" }, { "PDM1R", NULL, "PDM1 R Mux" }, { "PDM2L", NULL, "PDM2 L Mux" }, { "PDM2R", NULL, "PDM2 R Mux" }, }; +static const struct snd_soc_dapm_route rt5672_specific_dapm_routes[] = { + { "SPO Amp", NULL, "PDM1 L Mux" }, + { "SPO Amp", NULL, "PDM1 R Mux" }, + { "SPOLP", NULL, "SPO Amp" }, + { "SPOLN", NULL, "SPO Amp" }, + { "SPORP", NULL, "SPO Amp" }, + { "SPORN", NULL, "SPO Amp" }, +}; + static int rt5670_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -2287,6 +2334,8 @@ static int rt5670_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, static int rt5670_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + switch (level) { case SND_SOC_BIAS_PREPARE: if (SND_SOC_BIAS_STANDBY == codec->dapm.bias_level) { @@ -2308,16 +2357,27 @@ static int rt5670_set_bias_level(struct snd_soc_codec *codec, } break; case SND_SOC_BIAS_STANDBY: - snd_soc_write(codec, RT5670_PWR_DIG1, 0x0000); - snd_soc_write(codec, RT5670_PWR_DIG2, 0x0001); - snd_soc_write(codec, RT5670_PWR_VOL, 0x0000); - snd_soc_write(codec, RT5670_PWR_MIXER, 0x0001); - snd_soc_write(codec, RT5670_PWR_ANLG1, 0x2800); - snd_soc_write(codec, RT5670_PWR_ANLG2, 0x0004); - snd_soc_update_bits(codec, RT5670_DIG_MISC, 0x1, 0x0); + snd_soc_update_bits(codec, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, 0); snd_soc_update_bits(codec, RT5670_PWR_ANLG1, RT5670_LDO_SEL_MASK, 0x1); break; + case SND_SOC_BIAS_OFF: + if (rt5670->pdata.jd_mode) + snd_soc_update_bits(codec, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, + RT5670_PWR_MB | RT5670_PWR_BG); + else + snd_soc_update_bits(codec, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, 0); + + snd_soc_update_bits(codec, RT5670_DIG_MISC, 0x1, 0x0); + break; default: break; @@ -2331,6 +2391,29 @@ static int rt5670_probe(struct snd_soc_codec *codec) { struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec); + switch (snd_soc_read(codec, RT5670_RESET) & RT5670_ID_MASK) { + case RT5670_ID_5670: + case RT5670_ID_5671: + snd_soc_dapm_new_controls(&codec->dapm, + rt5670_specific_dapm_widgets, + ARRAY_SIZE(rt5670_specific_dapm_widgets)); + snd_soc_dapm_add_routes(&codec->dapm, + rt5670_specific_dapm_routes, + ARRAY_SIZE(rt5670_specific_dapm_routes)); + break; + case RT5670_ID_5672: + snd_soc_dapm_new_controls(&codec->dapm, + rt5672_specific_dapm_widgets, + ARRAY_SIZE(rt5672_specific_dapm_widgets)); + snd_soc_dapm_add_routes(&codec->dapm, + rt5672_specific_dapm_routes, + ARRAY_SIZE(rt5672_specific_dapm_routes)); + break; + default: + dev_err(codec->dev, + "The driver is for RT5670 RT5671 or RT5672 only\n"); + return -ENODEV; + } rt5670->codec = codec; return 0; @@ -2452,10 +2535,20 @@ static const struct regmap_config rt5670_regmap = { static const struct i2c_device_id rt5670_i2c_id[] = { { "rt5670", 0 }, + { "rt5671", 0 }, + { "rt5672", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, rt5670_i2c_id); +#ifdef CONFIG_ACPI +static struct acpi_device_id rt5670_acpi_match[] = { + { "10EC5670", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5670_acpi_match); +#endif + static int rt5670_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -2644,6 +2737,7 @@ static struct i2c_driver rt5670_i2c_driver = { .driver = { .name = "rt5670", .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(rt5670_acpi_match), }, .probe = rt5670_i2c_probe, .remove = rt5670_i2c_remove, diff --git a/sound/soc/codecs/rt5670.h b/sound/soc/codecs/rt5670.h index a0b5c855b49..d11b9c207e2 100644 --- a/sound/soc/codecs/rt5670.h +++ b/sound/soc/codecs/rt5670.h @@ -228,6 +228,12 @@ #define RT5670_R_VOL_MASK (0x3f) #define RT5670_R_VOL_SFT 0 +/* SW Reset & Device ID (0x00) */ +#define RT5670_ID_MASK (0x3 << 1) +#define RT5670_ID_5670 (0x0 << 1) +#define RT5670_ID_5672 (0x1 << 1) +#define RT5670_ID_5671 (0x2 << 1) + /* Combo Jack Control 1 (0x0a) */ #define RT5670_CBJ_BST1_MASK (0xf << 12) #define RT5670_CBJ_BST1_SFT (12) diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c new file mode 100644 index 00000000000..ef6348cb915 --- /dev/null +++ b/sound/soc/codecs/rt5677-spi.c @@ -0,0 +1,130 @@ +/* + * rt5677-spi.c -- RT5677 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/spi/spi.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_qos.h> +#include <linux/sysfs.h> +#include <linux/clk.h> +#include <linux/firmware.h> + +#include "rt5677-spi.h" + +static struct spi_device *g_spi; + +/** + * rt5677_spi_write - Write data to SPI. + * @txbuf: Data Buffer for writing. + * @len: Data length. + * + * + * Returns true for success. + */ +int rt5677_spi_write(u8 *txbuf, size_t len) +{ + int status; + + status = spi_write(g_spi, txbuf, len); + + if (status) + dev_err(&g_spi->dev, "rt5677_spi_write error %d\n", status); + + return status; +} +EXPORT_SYMBOL_GPL(rt5677_spi_write); + +/** + * rt5677_spi_burst_write - Write data to SPI by rt5677 dsp memory address. + * @addr: Start address. + * @txbuf: Data Buffer for writng. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5677_spi_burst_write(u32 addr, const struct firmware *fw) +{ + u8 spi_cmd = RT5677_SPI_CMD_BURST_WRITE; + u8 *write_buf; + unsigned int i, end, offset = 0; + + write_buf = kmalloc(RT5677_SPI_BUF_LEN + 6, GFP_KERNEL); + + if (write_buf == NULL) + return -ENOMEM; + + while (offset < fw->size) { + if (offset + RT5677_SPI_BUF_LEN <= fw->size) + end = RT5677_SPI_BUF_LEN; + else + end = fw->size % RT5677_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + for (i = 0; i < end; i += 8) { + write_buf[i + 12] = fw->data[offset + i + 0]; + write_buf[i + 11] = fw->data[offset + i + 1]; + write_buf[i + 10] = fw->data[offset + i + 2]; + write_buf[i + 9] = fw->data[offset + i + 3]; + write_buf[i + 8] = fw->data[offset + i + 4]; + write_buf[i + 7] = fw->data[offset + i + 5]; + write_buf[i + 6] = fw->data[offset + i + 6]; + write_buf[i + 5] = fw->data[offset + i + 7]; + } + + write_buf[end + 5] = spi_cmd; + + rt5677_spi_write(write_buf, end + 6); + + offset += RT5677_SPI_BUF_LEN; + } + + kfree(write_buf); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5677_spi_burst_write); + +static int rt5677_spi_probe(struct spi_device *spi) +{ + g_spi = spi; + return 0; +} + +static struct spi_driver rt5677_spi_driver = { + .driver = { + .name = "rt5677", + .owner = THIS_MODULE, + }, + .probe = rt5677_spi_probe, +}; +module_spi_driver(rt5677_spi_driver); + +MODULE_DESCRIPTION("ASoC RT5677 SPI driver"); +MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5677-spi.h b/sound/soc/codecs/rt5677-spi.h new file mode 100644 index 00000000000..ec41b2b3b2c --- /dev/null +++ b/sound/soc/codecs/rt5677-spi.h @@ -0,0 +1,21 @@ +/* + * rt5677-spi.h -- RT5677 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5677_SPI_H__ +#define __RT5677_SPI_H__ + +#define RT5677_SPI_BUF_LEN 240 +#define RT5677_SPI_CMD_BURST_WRITE 0x05 + +int rt5677_spi_write(u8 *txbuf, size_t len); +int rt5677_spi_burst_write(u32 addr, const struct firmware *fw); + +#endif /* __RT5677_SPI_H__ */ diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 16aa4d99a71..81fe1464d26 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -20,6 +20,7 @@ #include <linux/i2c.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> +#include <linux/firmware.h> #include <linux/gpio.h> #include <sound/core.h> #include <sound/pcm.h> @@ -31,6 +32,7 @@ #include "rl6231.h" #include "rt5677.h" +#include "rt5677-spi.h" #define RT5677_DEVICE_ID 0x6327 @@ -53,12 +55,13 @@ static const struct regmap_range_cfg rt5677_ranges[] = { }; static const struct reg_default init_list[] = { + {RT5677_ASRC_12, 0x0018}, {RT5677_PR_BASE + 0x3d, 0x364d}, - {RT5677_PR_BASE + 0x17, 0x4fc0}, - {RT5677_PR_BASE + 0x13, 0x0312}, - {RT5677_PR_BASE + 0x1e, 0x0000}, - {RT5677_PR_BASE + 0x12, 0x0eaa}, - {RT5677_PR_BASE + 0x14, 0x018a}, + {RT5677_PR_BASE + 0x17, 0x4fc0}, + {RT5677_PR_BASE + 0x13, 0x0312}, + {RT5677_PR_BASE + 0x1e, 0x0000}, + {RT5677_PR_BASE + 0x12, 0x0eaa}, + {RT5677_PR_BASE + 0x14, 0x018a}, }; #define RT5677_INIT_REG_LEN ARRAY_SIZE(init_list) @@ -171,7 +174,7 @@ static const struct reg_default rt5677_reg[] = { {RT5677_ASRC_9 , 0x0000}, {RT5677_ASRC_10 , 0x0000}, {RT5677_ASRC_11 , 0x0000}, - {RT5677_ASRC_12 , 0x0008}, + {RT5677_ASRC_12 , 0x0018}, {RT5677_ASRC_13 , 0x0000}, {RT5677_ASRC_14 , 0x0000}, {RT5677_ASRC_15 , 0x0000}, @@ -537,10 +540,232 @@ static bool rt5677_readable_register(struct device *dev, unsigned int reg) } } +/** + * rt5677_dsp_mode_i2c_write_addr - Write value to address on DSP mode. + * @rt5677: Private Data. + * @addr: Address index. + * @value: Address data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + unsigned int addr, unsigned int value, unsigned int opcode) +{ + struct snd_soc_codec *codec = rt5677->codec; + int ret; + + mutex_lock(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(codec->dev, "Failed to set addr msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(codec->dev, "Failed to set addr lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(codec->dev, "Failed to set data msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(codec->dev, "Failed to set data lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(codec->dev, "Failed to set op code value: %d\n", ret); + goto err; + } + +err: + mutex_unlock(&rt5677->dsp_cmd_lock); + + return ret; +} + +/** + * rt5677_dsp_mode_i2c_read_addr - Read value from address on DSP mode. + * rt5677: Private Data. + * @addr: Address index. + * @value: Address data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_read_addr( + struct rt5677_priv *rt5677, unsigned int addr, unsigned int *value) +{ + struct snd_soc_codec *codec = rt5677->codec; + int ret; + unsigned int msb, lsb; + + mutex_lock(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(codec->dev, "Failed to set addr msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(codec->dev, "Failed to set addr lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(codec->dev, "Failed to set op code value: %d\n", ret); + goto err; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +err: + mutex_unlock(&rt5677->dsp_cmd_lock); + + return ret; +} + +/** + * rt5677_dsp_mode_i2c_write - Write register on DSP mode. + * rt5677: Private Data. + * @reg: Register index. + * @value: Register data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_write(struct rt5677_priv *rt5677, + unsigned int reg, unsigned int value) +{ + return rt5677_dsp_mode_i2c_write_addr(rt5677, 0x18020000 + reg * 2, + value, 0x0001); +} + +/** + * rt5677_dsp_mode_i2c_read - Read register on DSP mode. + * @codec: SoC audio codec device. + * @reg: Register index. + * @value: Register data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_read( + struct rt5677_priv *rt5677, unsigned int reg, unsigned int *value) +{ + int ret = rt5677_dsp_mode_i2c_read_addr(rt5677, 0x18020000 + reg * 2, + value); + + *value &= 0xffff; + + return ret; +} + +static void rt5677_set_dsp_mode(struct snd_soc_codec *codec, bool on) +{ + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + + if (on) { + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x2, 0x2); + rt5677->is_dsp_mode = true; + } else { + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x2, 0x0); + rt5677->is_dsp_mode = false; + } +} + +static int rt5677_set_dsp_vad(struct snd_soc_codec *codec, bool on) +{ + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + static bool activity; + int ret; + + if (on && !activity) { + activity = true; + + regcache_cache_only(rt5677->regmap, false); + regcache_cache_bypass(rt5677->regmap, true); + + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x1, 0x1); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0f00); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_LDO1_SEL_MASK, 0x0); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_LDO1, RT5677_PWR_LDO1); + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_MCLK_SRC_MASK, RT5677_MCLK2_SRC); + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK2, + RT5677_PLL2_PR_SRC_MASK | RT5677_DSP_CLK_SRC_MASK, + RT5677_PLL2_PR_SRC_MCLK2 | RT5677_DSP_CLK_SRC_BYPASS); + regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x07ff); + regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x07fd); + rt5677_set_dsp_mode(codec, true); + + ret = request_firmware(&rt5677->fw1, RT5677_FIRMWARE1, + codec->dev); + if (ret == 0) { + rt5677_spi_burst_write(0x50000000, rt5677->fw1); + release_firmware(rt5677->fw1); + } + + ret = request_firmware(&rt5677->fw2, RT5677_FIRMWARE2, + codec->dev); + if (ret == 0) { + rt5677_spi_burst_write(0x60000000, rt5677->fw2); + release_firmware(rt5677->fw2); + } + + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x0); + + regcache_cache_bypass(rt5677->regmap, false); + regcache_cache_only(rt5677->regmap, true); + } else if (!on && activity) { + activity = false; + + regcache_cache_only(rt5677->regmap, false); + regcache_cache_bypass(rt5677->regmap, true); + + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x1); + rt5677_set_dsp_mode(codec, false); + regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x0001); + + regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec); + + regcache_cache_bypass(rt5677->regmap, false); + regcache_mark_dirty(rt5677->regmap); + regcache_sync(rt5677->regmap); + } + + return 0; +} + static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); -static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); -static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); static const DECLARE_TLV_DB_SCALE(st_vol_tlv, -4650, 150, 0); @@ -556,6 +781,31 @@ static unsigned int bst_tlv[] = { 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0), }; +static int rt5677_dsp_vad_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = rt5677->dsp_vad_en; + + return 0; +} + +static int rt5677_dsp_vad_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + + rt5677->dsp_vad_en = !!ucontrol->value.integer.value[0]; + + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) + rt5677_set_dsp_vad(codec, rt5677->dsp_vad_en); + + return 0; +} + static const struct snd_kcontrol_new rt5677_snd_controls[] = { /* OUTPUT Control */ SOC_SINGLE("OUT1 Playback Switch", RT5677_LOUT1, @@ -567,13 +817,13 @@ static const struct snd_kcontrol_new rt5677_snd_controls[] = { /* DAC Digital Volume */ SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5677_DAC1_DIG_VOL, - RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 175, 0, dac_vol_tlv), + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), SOC_DOUBLE_TLV("DAC2 Playback Volume", RT5677_DAC2_DIG_VOL, - RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 175, 0, dac_vol_tlv), + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), SOC_DOUBLE_TLV("DAC3 Playback Volume", RT5677_DAC3_DIG_VOL, - RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 175, 0, dac_vol_tlv), + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), SOC_DOUBLE_TLV("DAC4 Playback Volume", RT5677_DAC4_DIG_VOL, - RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 175, 0, dac_vol_tlv), + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), /* IN1/IN2 Control */ SOC_SINGLE_TLV("IN1 Boost", RT5677_IN1, RT5677_BST_SFT1, 8, 0, bst_tlv), @@ -592,19 +842,19 @@ static const struct snd_kcontrol_new rt5677_snd_controls[] = { RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), SOC_DOUBLE_TLV("ADC1 Capture Volume", RT5677_STO1_ADC_DIG_VOL, - RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 127, 0, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, adc_vol_tlv), SOC_DOUBLE_TLV("ADC2 Capture Volume", RT5677_STO2_ADC_DIG_VOL, - RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 127, 0, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, adc_vol_tlv), SOC_DOUBLE_TLV("ADC3 Capture Volume", RT5677_STO3_ADC_DIG_VOL, - RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 127, 0, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, adc_vol_tlv), SOC_DOUBLE_TLV("ADC4 Capture Volume", RT5677_STO4_ADC_DIG_VOL, - RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 127, 0, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, adc_vol_tlv), SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5677_MONO_ADC_DIG_VOL, - RT5677_MONO_ADC_L_VOL_SFT, RT5677_MONO_ADC_R_VOL_SFT, 127, 0, + RT5677_MONO_ADC_L_VOL_SFT, RT5677_MONO_ADC_R_VOL_SFT, 63, 0, adc_vol_tlv), /* Sidetone Control */ @@ -627,6 +877,9 @@ static const struct snd_kcontrol_new rt5677_snd_controls[] = { SOC_DOUBLE_TLV("Mono ADC Boost Volume", RT5677_ADC_BST_CTRL2, RT5677_MONO_ADC_L_BST_SFT, RT5677_MONO_ADC_R_BST_SFT, 3, 0, adc_bst_tlv), + + SOC_SINGLE_EXT("DSP VAD Switch", SND_SOC_NOPM, 0, 1, 0, + rt5677_dsp_vad_get, rt5677_dsp_vad_put), }; /** @@ -1086,7 +1339,7 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_ib45_bypass_src_mux = SOC_DAPM_ENUM("IB45 Bypass Source", rt5677_ib45_bypass_src_enum); -/* Stereo ADC Source 2 */ /* MX-27 MX26 MX25 [11:10] */ +/* Stereo ADC Source 2 */ /* MX-27 MX26 MX25 [11:10] */ static const char * const rt5677_stereo_adc2_src[] = { "DD MIX1", "DMIC", "Stereo DAC MIX" }; @@ -1171,7 +1424,7 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_sto2_adc_lr_mux = SOC_DAPM_ENUM("Stereo2 ADC LR Source", rt5677_stereo2_adc_lr_enum); -/* Stereo1 ADC Source 1 */ /* MX-27 MX26 MX25 [13:12] */ +/* Stereo1 ADC Source 1 */ /* MX-27 MX26 MX25 [13:12] */ static const char * const rt5677_stereo_adc1_src[] = { "DD MIX1", "ADC1/2", "Stereo DAC MIX" }; @@ -1443,7 +1696,7 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_pdm2_r_mux = SOC_DAPM_ENUM("PDM2 Source", rt5677_pdm2_r_enum); -/* TDM IF1/2 SLB ADC1 Data Selection */ /* MX-3C MX-41 [5:4] MX-08 [1:0]*/ +/* TDM IF1/2 SLB ADC1 Data Selection */ /* MX-3C MX-41 [5:4] MX-08 [1:0] */ static const char * const rt5677_if12_adc1_src[] = { "STO1 ADC MIX", "OB01", "VAD ADC" }; @@ -1521,7 +1774,7 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_slb_adc3_mux = SOC_DAPM_ENUM("SLB ADC3 Source", rt5677_slb_adc3_enum); -/* TDM IF1/2 SLB ADC4 Data Selection */ /* MX-3C MX-41 [11:10] MX-08 [7:6] */ +/* TDM IF1/2 SLB ADC4 Data Selection */ /* MX-3C MX-41 [11:10] MX-08 [7:6] */ static const char * const rt5677_if12_adc4_src[] = { "STO4 ADC MIX", "OB67", "OB01" }; @@ -1547,7 +1800,7 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_slb_adc4_mux = SOC_DAPM_ENUM("SLB ADC4 Source", rt5677_slb_adc4_enum); -/* Interface3/4 ADC Data Input */ /* MX-2F [3:0] MX-30 [7:4]*/ +/* Interface3/4 ADC Data Input */ /* MX-2F [3:0] MX-30 [7:4] */ static const char * const rt5677_if34_adc_src[] = { "STO1 ADC MIX", "STO2 ADC MIX", "STO3 ADC MIX", "STO4 ADC MIX", "MONO ADC MIX", "OB01", "OB23", "VAD ADC" @@ -1567,6 +1820,213 @@ static SOC_ENUM_SINGLE_DECL( static const struct snd_kcontrol_new rt5677_if4_adc_mux = SOC_DAPM_ENUM("IF4 ADC Source", rt5677_if4_adc_enum); +/* TDM IF1/2 ADC Data Selection */ /* MX-3B MX-40 [7:6][5:4][3:2][1:0] */ +static const char * const rt5677_if12_adc_swap_src[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc1_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC1_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc1_swap_mux = + SOC_DAPM_ENUM("IF1 ADC1 Swap Source", rt5677_if1_adc1_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc2_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc2_swap_mux = + SOC_DAPM_ENUM("IF1 ADC2 Swap Source", rt5677_if1_adc2_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc3_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC3_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc3_swap_mux = + SOC_DAPM_ENUM("IF1 ADC3 Swap Source", rt5677_if1_adc3_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc4_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC4_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc4_swap_mux = + SOC_DAPM_ENUM("IF1 ADC4 Swap Source", rt5677_if1_adc4_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc1_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF1_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc1_swap_mux = + SOC_DAPM_ENUM("IF1 ADC2 Swap Source", rt5677_if2_adc1_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc2_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc2_swap_mux = + SOC_DAPM_ENUM("IF2 ADC2 Swap Source", rt5677_if2_adc2_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc3_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC3_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc3_swap_mux = + SOC_DAPM_ENUM("IF2 ADC3 Swap Source", rt5677_if2_adc3_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc4_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC4_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc4_swap_mux = + SOC_DAPM_ENUM("IF2 ADC4 Swap Source", rt5677_if2_adc4_swap_enum); + +/* TDM IF1 ADC Data Selection */ /* MX-3C [2:0] */ +static const char * const rt5677_if1_adc_tdm_swap_src[] = { + "1/2/3/4", "2/1/3/4", "2/3/1/4", "4/1/2/3", "1/3/2/4", "1/4/2/3", + "3/1/2/4", "3/4/1/2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc_tdm_swap_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC_CTRL_SFT, rt5677_if1_adc_tdm_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc_tdm_swap_mux = + SOC_DAPM_ENUM("IF1 ADC TDM Swap Source", rt5677_if1_adc_tdm_swap_enum); + +/* TDM IF2 ADC Data Selection */ /* MX-41[2:0] */ +static const char * const rt5677_if2_adc_tdm_swap_src[] = { + "1/2/3/4", "2/1/3/4", "3/1/2/4", "4/1/2/3", "1/3/2/4", "1/4/2/3", + "2/3/1/4", "3/4/1/2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc_tdm_swap_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC_CTRL_SFT, rt5677_if2_adc_tdm_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc_tdm_swap_mux = + SOC_DAPM_ENUM("IF2 ADC TDM Swap Source", rt5677_if2_adc_tdm_swap_enum); + +/* TDM IF1/2 DAC Data Selection */ /* MX-3E[14:12][10:8][6:4][2:0] + MX-3F[14:12][10:8][6:4][2:0] + MX-43[14:12][10:8][6:4][2:0] + MX-44[14:12][10:8][6:4][2:0] */ +static const char * const rt5677_if12_dac_tdm_sel_src[] = { + "Slot0", "Slot1", "Slot2", "Slot3", "Slot4", "Slot5", "Slot6", "Slot7" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac0_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC0_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC0 TDM Source", rt5677_if1_dac0_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac1_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC1_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC1 TDM Source", rt5677_if1_dac1_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac2_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC2_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC2 TDM Source", rt5677_if1_dac2_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac3_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC3_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC3 TDM Source", rt5677_if1_dac3_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac4_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC4_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac4_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC4 TDM Source", rt5677_if1_dac4_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac5_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC5_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac5_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC5 TDM Source", rt5677_if1_dac5_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac6_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC6_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac6_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC6 TDM Source", rt5677_if1_dac6_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac7_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC7_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac7_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC7 TDM Source", rt5677_if1_dac7_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac0_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC0_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC0 TDM Source", rt5677_if2_dac0_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac1_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC1_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC1 TDM Source", rt5677_if2_dac1_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac2_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC2_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC2 TDM Source", rt5677_if2_dac2_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac3_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC3_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC3 TDM Source", rt5677_if2_dac3_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac4_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC4_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac4_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC4 TDM Source", rt5677_if2_dac4_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac5_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC5_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac5_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC5 TDM Source", rt5677_if2_dac5_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac6_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC6_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac6_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC6 TDM Source", rt5677_if2_dac6_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac7_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC7_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac7_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC7 TDM Source", rt5677_if2_dac7_tdm_sel_enum); + static int rt5677_bst1_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -1678,6 +2138,77 @@ static int rt5677_set_micbias1_event(struct snd_soc_dapm_widget *w, return 0; } +static int rt5677_if1_adc_tdm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + unsigned int value; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_read(rt5677->regmap, RT5677_TDM1_CTRL2, &value); + if (value & RT5677_IF1_ADC_CTRL_MASK) + regmap_update_bits(rt5677->regmap, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC_MODE_MASK, + RT5677_IF1_ADC_MODE_TDM); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_if2_adc_tdm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + unsigned int value; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_read(rt5677->regmap, RT5677_TDM2_CTRL2, &value); + if (value & RT5677_IF2_ADC_CTRL_MASK) + regmap_update_bits(rt5677->regmap, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC_MODE_MASK, + RT5677_IF2_ADC_MODE_TDM); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_vref_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (codec->dapm.bias_level != SND_SOC_BIAS_ON && + !rt5677->is_vref_slow) { + mdelay(20); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_PWR_FV1 | RT5677_PWR_FV2, + RT5677_PWR_FV1 | RT5677_PWR_FV2); + rt5677->is_vref_slow = true; + } + break; + + default: + return 0; + } + + return 0; +} + static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("PLL1", RT5677_PWR_ANLG2, RT5677_PWR_PLL1_BIT, 0, rt5677_set_pll1_event, SND_SOC_DAPM_POST_PMU), @@ -1837,10 +2368,8 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { SND_SOC_DAPM_PGA("Stereo4 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("Sto2 ADC LR MIX", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("Mono ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("IF1_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("IF1_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("IF1_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("IF1_ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), /* DSP */ SND_SOC_DAPM_MUX("IB9 Mux", SND_SOC_NOPM, 0, 0, @@ -1963,6 +2492,17 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { &rt5677_if1_adc3_mux), SND_SOC_DAPM_MUX("IF1 ADC4 Mux", SND_SOC_NOPM, 0, 0, &rt5677_if1_adc4_mux), + SND_SOC_DAPM_MUX("IF1 ADC1 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc1_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC2 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc2_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC3 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc3_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC4 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc4_swap_mux), + SND_SOC_DAPM_MUX_E("IF1 ADC TDM Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc_tdm_swap_mux, rt5677_if1_adc_tdm_event, + SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_MUX("IF2 ADC1 Mux", SND_SOC_NOPM, 0, 0, &rt5677_if2_adc1_mux), SND_SOC_DAPM_MUX("IF2 ADC2 Mux", SND_SOC_NOPM, 0, 0, @@ -1971,6 +2511,17 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { &rt5677_if2_adc3_mux), SND_SOC_DAPM_MUX("IF2 ADC4 Mux", SND_SOC_NOPM, 0, 0, &rt5677_if2_adc4_mux), + SND_SOC_DAPM_MUX("IF2 ADC1 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc1_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC2 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc2_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC3 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc3_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC4 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc4_swap_mux), + SND_SOC_DAPM_MUX_E("IF2 ADC TDM Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc_tdm_swap_mux, rt5677_if2_adc_tdm_event, + SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_MUX("IF3 ADC Mux", SND_SOC_NOPM, 0, 0, &rt5677_if3_adc_mux), SND_SOC_DAPM_MUX("IF4 ADC Mux", SND_SOC_NOPM, 0, 0, @@ -1984,6 +2535,40 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { SND_SOC_DAPM_MUX("SLB ADC4 Mux", SND_SOC_NOPM, 0, 0, &rt5677_slb_adc4_mux), + SND_SOC_DAPM_MUX("IF1 DAC0 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac3_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac4_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC5 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac5_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC6 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac6_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC7 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac7_tdm_sel_mux), + + SND_SOC_DAPM_MUX("IF2 DAC0 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac3_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac4_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC5 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac5_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC6 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac6_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC7 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac7_tdm_sel_mux), + /* Audio Interface */ SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), @@ -2022,7 +2607,7 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { rt5677_ob_7_mix, ARRAY_SIZE(rt5677_ob_7_mix)), /* Output Side */ - /* DAC mixer before sound effect */ + /* DAC mixer before sound effect */ SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, rt5677_dac_l_mix, ARRAY_SIZE(rt5677_dac_l_mix)), SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, @@ -2109,13 +2694,20 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { SND_SOC_DAPM_MUX("PDM2 R Mux", RT5677_PDM_OUT_CTRL, RT5677_M_PDM2_R_SFT, 1, &rt5677_pdm2_r_mux), - SND_SOC_DAPM_PGA_S("LOUT1 amp", 1, RT5677_PWR_ANLG1, RT5677_PWR_LO1_BIT, + SND_SOC_DAPM_PGA_S("LOUT1 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO1_BIT, 0, NULL, 0), - SND_SOC_DAPM_PGA_S("LOUT2 amp", 1, RT5677_PWR_ANLG1, RT5677_PWR_LO2_BIT, + SND_SOC_DAPM_PGA_S("LOUT2 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO2_BIT, 0, NULL, 0), - SND_SOC_DAPM_PGA_S("LOUT3 amp", 1, RT5677_PWR_ANLG1, RT5677_PWR_LO3_BIT, + SND_SOC_DAPM_PGA_S("LOUT3 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO3_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("LOUT1 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT2 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT3 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + /* Output Lines */ SND_SOC_DAPM_OUTPUT("LOUT1"), SND_SOC_DAPM_OUTPUT("LOUT2"), @@ -2124,6 +2716,8 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("PDM1R"), SND_SOC_DAPM_OUTPUT("PDM2L"), SND_SOC_DAPM_OUTPUT("PDM2R"), + + SND_SOC_DAPM_POST("vref", rt5677_vref_event), }; static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { @@ -2354,11 +2948,42 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { { "IF1 ADC4 Mux", "OB67", "OB67" }, { "IF1 ADC4 Mux", "OB01", "OB01 Bypass Mux" }, + { "IF1 ADC1 Swap Mux", "L/R", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "R/L", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "L/L", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "R/R", "IF1 ADC1 Mux" }, + + { "IF1 ADC2 Swap Mux", "L/R", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "R/L", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "L/L", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "R/R", "IF1 ADC2 Mux" }, + + { "IF1 ADC3 Swap Mux", "L/R", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "R/L", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "L/L", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "R/R", "IF1 ADC3 Mux" }, + + { "IF1 ADC4 Swap Mux", "L/R", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "R/L", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "L/L", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "R/R", "IF1 ADC4 Mux" }, + + { "IF1 ADC", NULL, "IF1 ADC1 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC2 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC3 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC4 Swap Mux" }, + + { "IF1 ADC TDM Swap Mux", "1/2/3/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "2/1/3/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "2/3/1/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "4/1/2/3", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "1/3/2/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "1/4/2/3", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "3/1/2/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "3/4/1/2", "IF1 ADC" }, + { "AIF1TX", NULL, "I2S1" }, - { "AIF1TX", NULL, "IF1 ADC1 Mux" }, - { "AIF1TX", NULL, "IF1 ADC2 Mux" }, - { "AIF1TX", NULL, "IF1 ADC3 Mux" }, - { "AIF1TX", NULL, "IF1 ADC4 Mux" }, + { "AIF1TX", NULL, "IF1 ADC TDM Swap Mux" }, { "IF2 ADC1 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, { "IF2 ADC1 Mux", "OB01", "OB01 Bypass Mux" }, @@ -2375,11 +3000,42 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { { "IF2 ADC4 Mux", "OB67", "OB67" }, { "IF2 ADC4 Mux", "OB01", "OB01 Bypass Mux" }, + { "IF2 ADC1 Swap Mux", "L/R", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "R/L", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "L/L", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "R/R", "IF2 ADC1 Mux" }, + + { "IF2 ADC2 Swap Mux", "L/R", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "R/L", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "L/L", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "R/R", "IF2 ADC2 Mux" }, + + { "IF2 ADC3 Swap Mux", "L/R", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "R/L", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "L/L", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "R/R", "IF2 ADC3 Mux" }, + + { "IF2 ADC4 Swap Mux", "L/R", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "R/L", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "L/L", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "R/R", "IF2 ADC4 Mux" }, + + { "IF2 ADC", NULL, "IF2 ADC1 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC2 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC3 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC4 Swap Mux" }, + + { "IF2 ADC TDM Swap Mux", "1/2/3/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "2/1/3/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "3/1/2/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "4/1/2/3", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "1/3/2/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "1/4/2/3", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "2/3/1/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "3/4/1/2", "IF2 ADC" }, + { "AIF2TX", NULL, "I2S2" }, - { "AIF2TX", NULL, "IF2 ADC1 Mux" }, - { "AIF2TX", NULL, "IF2 ADC2 Mux" }, - { "AIF2TX", NULL, "IF2 ADC3 Mux" }, - { "AIF2TX", NULL, "IF2 ADC4 Mux" }, + { "AIF2TX", NULL, "IF2 ADC TDM Swap Mux" }, { "IF3 ADC Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, { "IF3 ADC Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, @@ -2569,14 +3225,86 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { { "IF1 DAC6", NULL, "I2S1" }, { "IF1 DAC7", NULL, "I2S1" }, - { "IF1 DAC01", NULL, "IF1 DAC0" }, - { "IF1 DAC01", NULL, "IF1 DAC1" }, - { "IF1 DAC23", NULL, "IF1 DAC2" }, - { "IF1 DAC23", NULL, "IF1 DAC3" }, - { "IF1 DAC45", NULL, "IF1 DAC4" }, - { "IF1 DAC45", NULL, "IF1 DAC5" }, - { "IF1 DAC67", NULL, "IF1 DAC6" }, - { "IF1 DAC67", NULL, "IF1 DAC7" }, + { "IF1 DAC0 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC0 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC0 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC0 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC0 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC0 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC0 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC0 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC1 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC1 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC1 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC1 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC1 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC1 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC1 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC1 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC2 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC2 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC2 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC2 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC2 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC2 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC2 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC2 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC3 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC3 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC3 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC3 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC3 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC3 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC3 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC3 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC4 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC4 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC4 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC4 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC4 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC4 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC4 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC4 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC5 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC5 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC5 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC5 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC5 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC5 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC5 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC5 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC6 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC6 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC6 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC6 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC6 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC6 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC6 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC6 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC7 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC7 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC7 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC7 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC7 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC7 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC7 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC7 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC01", NULL, "IF1 DAC0 Mux" }, + { "IF1 DAC01", NULL, "IF1 DAC1 Mux" }, + { "IF1 DAC23", NULL, "IF1 DAC2 Mux" }, + { "IF1 DAC23", NULL, "IF1 DAC3 Mux" }, + { "IF1 DAC45", NULL, "IF1 DAC4 Mux" }, + { "IF1 DAC45", NULL, "IF1 DAC5 Mux" }, + { "IF1 DAC67", NULL, "IF1 DAC6 Mux" }, + { "IF1 DAC67", NULL, "IF1 DAC7 Mux" }, { "IF2 DAC0", NULL, "AIF2RX" }, { "IF2 DAC1", NULL, "AIF2RX" }, @@ -2595,14 +3323,86 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { { "IF2 DAC6", NULL, "I2S2" }, { "IF2 DAC7", NULL, "I2S2" }, - { "IF2 DAC01", NULL, "IF2 DAC0" }, - { "IF2 DAC01", NULL, "IF2 DAC1" }, - { "IF2 DAC23", NULL, "IF2 DAC2" }, - { "IF2 DAC23", NULL, "IF2 DAC3" }, - { "IF2 DAC45", NULL, "IF2 DAC4" }, - { "IF2 DAC45", NULL, "IF2 DAC5" }, - { "IF2 DAC67", NULL, "IF2 DAC6" }, - { "IF2 DAC67", NULL, "IF2 DAC7" }, + { "IF2 DAC0 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC0 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC0 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC0 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC0 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC0 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC0 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC0 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC1 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC1 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC1 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC1 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC1 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC1 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC1 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC1 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC2 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC2 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC2 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC2 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC2 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC2 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC2 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC2 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC3 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC3 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC3 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC3 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC3 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC3 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC3 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC3 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC4 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC4 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC4 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC4 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC4 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC4 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC4 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC4 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC5 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC5 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC5 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC5 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC5 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC5 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC5 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC5 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC6 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC6 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC6 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC6 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC6 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC6 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC6 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC6 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC7 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC7 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC7 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC7 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC7 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC7 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC7 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC7 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC01", NULL, "IF2 DAC0 Mux" }, + { "IF2 DAC01", NULL, "IF2 DAC1 Mux" }, + { "IF2 DAC23", NULL, "IF2 DAC2 Mux" }, + { "IF2 DAC23", NULL, "IF2 DAC3 Mux" }, + { "IF2 DAC45", NULL, "IF2 DAC4 Mux" }, + { "IF2 DAC45", NULL, "IF2 DAC5 Mux" }, + { "IF2 DAC67", NULL, "IF2 DAC6 Mux" }, + { "IF2 DAC67", NULL, "IF2 DAC7 Mux" }, { "IF3 DAC", NULL, "AIF3RX" }, { "IF3 DAC", NULL, "I2S3" }, @@ -2806,9 +3606,13 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { { "LOUT2 amp", NULL, "DAC 2" }, { "LOUT3 amp", NULL, "DAC 3" }, - { "LOUT1", NULL, "LOUT1 amp" }, - { "LOUT2", NULL, "LOUT2 amp" }, - { "LOUT3", NULL, "LOUT3 amp" }, + { "LOUT1 vref", NULL, "LOUT1 amp" }, + { "LOUT2 vref", NULL, "LOUT2 amp" }, + { "LOUT3 vref", NULL, "LOUT3 amp" }, + + { "LOUT1", NULL, "LOUT1 vref" }, + { "LOUT2", NULL, "LOUT2 vref" }, + { "LOUT3", NULL, "LOUT3 vref" }, { "PDM1L", NULL, "PDM1 L Mux" }, { "PDM1R", NULL, "PDM1 R Mux" }, @@ -2837,7 +3641,8 @@ static int rt5677_hw_params(struct snd_pcm_substream *substream, rt5677->lrck[dai->id] = params_rate(params); pre_div = rl6231_get_clk_info(rt5677->sysclk, rt5677->lrck[dai->id]); if (pre_div < 0) { - dev_err(codec->dev, "Unsupported clock setting\n"); + dev_err(codec->dev, "Unsupported clock setting: sysclk=%dHz lrck=%dHz\n", + rt5677->sysclk, rt5677->lrck[dai->id]); return -EINVAL; } frame_size = snd_soc_params_to_frame_size(params); @@ -3181,6 +3986,8 @@ static int rt5677_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + rt5677_set_dsp_vad(codec, false); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, RT5677_LDO1_SEL_MASK | RT5677_LDO2_SEL_MASK, 0x0055); @@ -3188,14 +3995,12 @@ static int rt5677_set_bias_level(struct snd_soc_codec *codec, RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0f00); regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_PWR_FV1 | RT5677_PWR_FV2 | RT5677_PWR_VREF1 | RT5677_PWR_MB | RT5677_PWR_BG | RT5677_PWR_VREF2, RT5677_PWR_VREF1 | RT5677_PWR_MB | RT5677_PWR_BG | RT5677_PWR_VREF2); - mdelay(20); - regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, - RT5677_PWR_FV1 | RT5677_PWR_FV2, - RT5677_PWR_FV1 | RT5677_PWR_FV2); + rt5677->is_vref_slow = false; regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, RT5677_PWR_CORE, RT5677_PWR_CORE); regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, @@ -3214,6 +4019,9 @@ static int rt5677_set_bias_level(struct snd_soc_codec *codec, regmap_write(rt5677->regmap, RT5677_PWR_ANLG2, 0x0000); regmap_update_bits(rt5677->regmap, RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0000); + + if (rt5677->dsp_vad_en) + rt5677_set_dsp_vad(codec, true); break; default: @@ -3309,6 +4117,78 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) return 0; } +/** Configures the gpio as + * 0 - floating + * 1 - pull down + * 2 - pull up + */ +static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, + int value) +{ + int shift; + + switch (offset) { + case RT5677_GPIO1 ... RT5677_GPIO2: + shift = 2 * (1 - offset); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_DIG_IN_PIN_ST_CTRL2, + 0x3 << shift, + (value & 0x3) << shift); + break; + + case RT5677_GPIO3 ... RT5677_GPIO6: + shift = 2 * (9 - offset); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_DIG_IN_PIN_ST_CTRL3, + 0x3 << shift, + (value & 0x3) << shift); + break; + + default: + break; + } +} + +static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct rt5677_priv *rt5677 = gpio_to_rt5677(chip); + struct regmap_irq_chip_data *data = rt5677->irq_data; + int irq; + + if (offset >= RT5677_GPIO1 && offset <= RT5677_GPIO3) { + if ((rt5677->pdata.jd1_gpio == 1 && offset == RT5677_GPIO1) || + (rt5677->pdata.jd1_gpio == 2 && + offset == RT5677_GPIO2) || + (rt5677->pdata.jd1_gpio == 3 && + offset == RT5677_GPIO3)) { + irq = RT5677_IRQ_JD1; + } else { + return -ENXIO; + } + } + + if (offset >= RT5677_GPIO4 && offset <= RT5677_GPIO6) { + if ((rt5677->pdata.jd2_gpio == 1 && offset == RT5677_GPIO4) || + (rt5677->pdata.jd2_gpio == 2 && + offset == RT5677_GPIO5) || + (rt5677->pdata.jd2_gpio == 3 && + offset == RT5677_GPIO6)) { + irq = RT5677_IRQ_JD2; + } else if ((rt5677->pdata.jd3_gpio == 1 && + offset == RT5677_GPIO4) || + (rt5677->pdata.jd3_gpio == 2 && + offset == RT5677_GPIO5) || + (rt5677->pdata.jd3_gpio == 3 && + offset == RT5677_GPIO6)) { + irq = RT5677_IRQ_JD3; + } else { + return -ENXIO; + } + } + + return regmap_irq_get_virq(data, irq); +} + static struct gpio_chip rt5677_template_chip = { .label = "rt5677", .owner = THIS_MODULE, @@ -3316,6 +4196,7 @@ static struct gpio_chip rt5677_template_chip = { .set = rt5677_gpio_set, .direction_input = rt5677_gpio_direction_in, .get = rt5677_gpio_get, + .to_irq = rt5677_to_irq, .can_sleep = 1, }; @@ -3341,6 +4222,11 @@ static void rt5677_free_gpio(struct i2c_client *i2c) gpiochip_remove(&rt5677->gpio_chip); } #else +static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, + int value) +{ +} + static void rt5677_init_gpio(struct i2c_client *i2c) { } @@ -3353,6 +4239,7 @@ static void rt5677_free_gpio(struct i2c_client *i2c) static int rt5677_probe(struct snd_soc_codec *codec) { struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); + int i; rt5677->codec = codec; @@ -3371,6 +4258,37 @@ static int rt5677_probe(struct snd_soc_codec *codec) regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020); regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00); + for (i = 0; i < RT5677_GPIO_NUM; i++) + rt5677_gpio_config(rt5677, i, rt5677->pdata.gpio_config[i]); + + if (rt5677->irq_data) { + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, 0x8000, + 0x8000); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x0018, + 0x0008); + + if (rt5677->pdata.jd1_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD1_MASK, + rt5677->pdata.jd1_gpio << + RT5677_SEL_GPIO_JD1_SFT); + + if (rt5677->pdata.jd2_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD2_MASK, + rt5677->pdata.jd2_gpio << + RT5677_SEL_GPIO_JD2_SFT); + + if (rt5677->pdata.jd3_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD3_MASK, + rt5677->pdata.jd3_gpio << + RT5677_SEL_GPIO_JD3_SFT); + } + + mutex_init(&rt5677->dsp_cmd_lock); + mutex_init(&rt5677->dsp_pri_lock); + return 0; } @@ -3390,8 +4308,11 @@ static int rt5677_suspend(struct snd_soc_codec *codec) { struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec); - regcache_cache_only(rt5677->regmap, true); - regcache_mark_dirty(rt5677->regmap); + if (!rt5677->dsp_vad_en) { + regcache_cache_only(rt5677->regmap, true); + regcache_mark_dirty(rt5677->regmap); + } + if (gpio_is_valid(rt5677->pow_ldo2)) gpio_set_value_cansleep(rt5677->pow_ldo2, 0); @@ -3406,8 +4327,11 @@ static int rt5677_resume(struct snd_soc_codec *codec) gpio_set_value_cansleep(rt5677->pow_ldo2, 1); msleep(10); } - regcache_cache_only(rt5677->regmap, false); - regcache_sync(rt5677->regmap); + + if (!rt5677->dsp_vad_en) { + regcache_cache_only(rt5677->regmap, false); + regcache_sync(rt5677->regmap); + } return 0; } @@ -3416,6 +4340,51 @@ static int rt5677_resume(struct snd_soc_codec *codec) #define rt5677_resume NULL #endif +static int rt5677_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + struct rt5677_priv *rt5677 = i2c_get_clientdata(client); + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { + mutex_lock(&rt5677->dsp_pri_lock); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, + reg & 0xff); + rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); + mutex_unlock(&rt5677->dsp_pri_lock); + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } + } else { + regmap_read(rt5677->regmap_physical, reg, val); + } + + return 0; +} + +static int rt5677_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + struct rt5677_priv *rt5677 = i2c_get_clientdata(client); + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { + mutex_lock(&rt5677->dsp_pri_lock); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, + reg & 0xff); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, + val); + mutex_unlock(&rt5677->dsp_pri_lock); + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } + } else { + regmap_write(rt5677->regmap_physical, reg, val); + } + + return 0; +} + #define RT5677_STEREO_RATES SNDRV_PCM_RATE_8000_96000 #define RT5677_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) @@ -3541,6 +4510,20 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5677 = { .num_dapm_routes = ARRAY_SIZE(rt5677_dapm_routes), }; +static const struct regmap_config rt5677_regmap_physical = { + .name = "physical", + .reg_bits = 8, + .val_bits = 16, + + .max_register = RT5677_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5677_ranges) * + RT5677_PR_SPACING), + .readable_reg = rt5677_readable_register, + + .cache_type = REGCACHE_NONE, + .ranges = rt5677_ranges, + .num_ranges = ARRAY_SIZE(rt5677_ranges), +}; + static const struct regmap_config rt5677_regmap = { .reg_bits = 8, .val_bits = 16, @@ -3550,6 +4533,8 @@ static const struct regmap_config rt5677_regmap = { .volatile_reg = rt5677_volatile_register, .readable_reg = rt5677_readable_register, + .reg_read = rt5677_read, + .reg_write = rt5677_write, .cache_type = REGCACHE_RBTREE, .reg_defaults = rt5677_reg, @@ -3590,9 +4575,77 @@ static int rt5677_parse_dt(struct rt5677_priv *rt5677, struct device_node *np) (rt5677->pow_ldo2 != -ENOENT)) return rt5677->pow_ldo2; + of_property_read_u8_array(np, "realtek,gpio-config", + rt5677->pdata.gpio_config, RT5677_GPIO_NUM); + + of_property_read_u32(np, "realtek,jd1-gpio", &rt5677->pdata.jd1_gpio); + of_property_read_u32(np, "realtek,jd2-gpio", &rt5677->pdata.jd2_gpio); + of_property_read_u32(np, "realtek,jd3-gpio", &rt5677->pdata.jd3_gpio); + + return 0; +} + +static struct regmap_irq rt5677_irqs[] = { + [RT5677_IRQ_JD1] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD1, + }, + [RT5677_IRQ_JD2] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD2, + }, + [RT5677_IRQ_JD3] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD3, + }, +}; + +static struct regmap_irq_chip rt5677_irq_chip = { + .name = "rt5677", + .irqs = rt5677_irqs, + .num_irqs = ARRAY_SIZE(rt5677_irqs), + + .num_regs = 1, + .status_base = RT5677_IRQ_CTRL1, + .mask_base = RT5677_IRQ_CTRL1, + .mask_invert = 1, +}; + +static int rt5677_init_irq(struct i2c_client *i2c) +{ + int ret; + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + + if (!rt5677->pdata.jd1_gpio && + !rt5677->pdata.jd2_gpio && + !rt5677->pdata.jd3_gpio) + return 0; + + if (!i2c->irq) { + dev_err(&i2c->dev, "No interrupt specified\n"); + return -EINVAL; + } + + ret = regmap_add_irq_chip(rt5677->regmap, i2c->irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, + &rt5677_irq_chip, &rt5677->irq_data); + + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register IRQ chip: %d\n", ret); + return ret; + } + return 0; } +static void rt5677_free_irq(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + + if (rt5677->irq_data) + regmap_del_irq_chip(i2c->irq, rt5677->irq_data); +} + static int rt5677_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -3638,7 +4691,16 @@ static int rt5677_i2c_probe(struct i2c_client *i2c, msleep(10); } - rt5677->regmap = devm_regmap_init_i2c(i2c, &rt5677_regmap); + rt5677->regmap_physical = devm_regmap_init_i2c(i2c, + &rt5677_regmap_physical); + if (IS_ERR(rt5677->regmap_physical)) { + ret = PTR_ERR(rt5677->regmap_physical); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + rt5677->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt5677_regmap); if (IS_ERR(rt5677->regmap)) { ret = PTR_ERR(rt5677->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", @@ -3690,6 +4752,7 @@ static int rt5677_i2c_probe(struct i2c_client *i2c, } rt5677_init_gpio(i2c); + rt5677_init_irq(i2c); return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5677, rt5677_dai, ARRAY_SIZE(rt5677_dai)); @@ -3698,6 +4761,7 @@ static int rt5677_i2c_probe(struct i2c_client *i2c, static int rt5677_i2c_remove(struct i2c_client *i2c) { snd_soc_unregister_codec(&i2c->dev); + rt5677_free_irq(i2c); rt5677_free_gpio(i2c); return 0; diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index d4eb6d5e674..c0a625f290c 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -13,6 +13,7 @@ #define __RT5677_H__ #include <sound/rt5677.h> +#include <linux/gpio/driver.h> /* Info */ #define RT5677_RESET 0x00 @@ -305,10 +306,10 @@ #define RT5677_R_MUTE_SFT 7 #define RT5677_VOL_R_MUTE (0x1 << 6) #define RT5677_VOL_R_SFT 6 -#define RT5677_L_VOL_MASK (0x3f << 8) -#define RT5677_L_VOL_SFT 8 -#define RT5677_R_VOL_MASK (0x3f) -#define RT5677_R_VOL_SFT 0 +#define RT5677_L_VOL_MASK (0x7f << 9) +#define RT5677_L_VOL_SFT 9 +#define RT5677_R_VOL_MASK (0x7f << 1) +#define RT5677_R_VOL_SFT 1 /* LOUT1 Control (0x01) */ #define RT5677_LOUT1_L_MUTE (0x1 << 15) @@ -446,16 +447,16 @@ #define RT5677_SEL_DAC2_R_SRC_SFT 0 /* Stereo1 ADC Digital Volume Control (0x1c) */ -#define RT5677_STO1_ADC_L_VOL_MASK (0x7f << 8) -#define RT5677_STO1_ADC_L_VOL_SFT 8 -#define RT5677_STO1_ADC_R_VOL_MASK (0x7f) -#define RT5677_STO1_ADC_R_VOL_SFT 0 +#define RT5677_STO1_ADC_L_VOL_MASK (0x3f << 9) +#define RT5677_STO1_ADC_L_VOL_SFT 9 +#define RT5677_STO1_ADC_R_VOL_MASK (0x3f << 1) +#define RT5677_STO1_ADC_R_VOL_SFT 1 /* Mono ADC Digital Volume Control (0x1d) */ -#define RT5677_MONO_ADC_L_VOL_MASK (0x7f << 8) -#define RT5677_MONO_ADC_L_VOL_SFT 8 -#define RT5677_MONO_ADC_R_VOL_MASK (0x7f) -#define RT5677_MONO_ADC_R_VOL_SFT 0 +#define RT5677_MONO_ADC_L_VOL_MASK (0x3f << 9) +#define RT5677_MONO_ADC_L_VOL_SFT 9 +#define RT5677_MONO_ADC_R_VOL_MASK (0x3f << 1) +#define RT5677_MONO_ADC_R_VOL_SFT 1 /* Stereo 1/2 ADC Boost Gain Control (0x1e) */ #define RT5677_STO1_ADC_L_BST_MASK (0x3 << 14) @@ -798,7 +799,21 @@ #define RT5677_PDM2_I2C_EXE (0x1 << 1) #define RT5677_PDM2_I2C_BUSY (0x1 << 0) -/* MX3C TDM1 control 1 (0x3c) */ +/* TDM1 control 1 (0x3b) */ +#define RT5677_IF1_ADC_MODE_MASK (0x1 << 12) +#define RT5677_IF1_ADC_MODE_SFT 12 +#define RT5677_IF1_ADC_MODE_I2S (0x0 << 12) +#define RT5677_IF1_ADC_MODE_TDM (0x1 << 12) +#define RT5677_IF1_ADC1_SWAP_MASK (0x3 << 6) +#define RT5677_IF1_ADC1_SWAP_SFT 6 +#define RT5677_IF1_ADC2_SWAP_MASK (0x3 << 4) +#define RT5677_IF1_ADC2_SWAP_SFT 4 +#define RT5677_IF1_ADC3_SWAP_MASK (0x3 << 2) +#define RT5677_IF1_ADC3_SWAP_SFT 2 +#define RT5677_IF1_ADC4_SWAP_MASK (0x3 << 0) +#define RT5677_IF1_ADC4_SWAP_SFT 0 + +/* TDM1 control 2 (0x3c) */ #define RT5677_IF1_ADC4_MASK (0x3 << 10) #define RT5677_IF1_ADC4_SFT 10 #define RT5677_IF1_ADC3_MASK (0x3 << 8) @@ -807,8 +822,44 @@ #define RT5677_IF1_ADC2_SFT 6 #define RT5677_IF1_ADC1_MASK (0x3 << 4) #define RT5677_IF1_ADC1_SFT 4 - -/* MX41 TDM2 control 1 (0x41) */ +#define RT5677_IF1_ADC_CTRL_MASK (0x7 << 0) +#define RT5677_IF1_ADC_CTRL_SFT 0 + +/* TDM1 control 4 (0x3e) */ +#define RT5677_IF1_DAC0_MASK (0x7 << 12) +#define RT5677_IF1_DAC0_SFT 12 +#define RT5677_IF1_DAC1_MASK (0x7 << 8) +#define RT5677_IF1_DAC1_SFT 8 +#define RT5677_IF1_DAC2_MASK (0x7 << 4) +#define RT5677_IF1_DAC2_SFT 4 +#define RT5677_IF1_DAC3_MASK (0x7 << 0) +#define RT5677_IF1_DAC3_SFT 0 + +/* TDM1 control 5 (0x3f) */ +#define RT5677_IF1_DAC4_MASK (0x7 << 12) +#define RT5677_IF1_DAC4_SFT 12 +#define RT5677_IF1_DAC5_MASK (0x7 << 8) +#define RT5677_IF1_DAC5_SFT 8 +#define RT5677_IF1_DAC6_MASK (0x7 << 4) +#define RT5677_IF1_DAC6_SFT 4 +#define RT5677_IF1_DAC7_MASK (0x7 << 0) +#define RT5677_IF1_DAC7_SFT 0 + +/* TDM2 control 1 (0x40) */ +#define RT5677_IF2_ADC_MODE_MASK (0x1 << 12) +#define RT5677_IF2_ADC_MODE_SFT 12 +#define RT5677_IF2_ADC_MODE_I2S (0x0 << 12) +#define RT5677_IF2_ADC_MODE_TDM (0x1 << 12) +#define RT5677_IF2_ADC1_SWAP_MASK (0x3 << 6) +#define RT5677_IF2_ADC1_SWAP_SFT 6 +#define RT5677_IF2_ADC2_SWAP_MASK (0x3 << 4) +#define RT5677_IF2_ADC2_SWAP_SFT 4 +#define RT5677_IF2_ADC3_SWAP_MASK (0x3 << 2) +#define RT5677_IF2_ADC3_SWAP_SFT 2 +#define RT5677_IF2_ADC4_SWAP_MASK (0x3 << 0) +#define RT5677_IF2_ADC4_SWAP_SFT 0 + +/* TDM2 control 2 (0x41) */ #define RT5677_IF2_ADC4_MASK (0x3 << 10) #define RT5677_IF2_ADC4_SFT 10 #define RT5677_IF2_ADC3_MASK (0x3 << 8) @@ -817,6 +868,28 @@ #define RT5677_IF2_ADC2_SFT 6 #define RT5677_IF2_ADC1_MASK (0x3 << 4) #define RT5677_IF2_ADC1_SFT 4 +#define RT5677_IF2_ADC_CTRL_MASK (0x7 << 0) +#define RT5677_IF2_ADC_CTRL_SFT 0 + +/* TDM2 control 4 (0x43) */ +#define RT5677_IF2_DAC0_MASK (0x7 << 12) +#define RT5677_IF2_DAC0_SFT 12 +#define RT5677_IF2_DAC1_MASK (0x7 << 8) +#define RT5677_IF2_DAC1_SFT 8 +#define RT5677_IF2_DAC2_MASK (0x7 << 4) +#define RT5677_IF2_DAC2_SFT 4 +#define RT5677_IF2_DAC3_MASK (0x7 << 0) +#define RT5677_IF2_DAC3_SFT 0 + +/* TDM2 control 5 (0x44) */ +#define RT5677_IF2_DAC4_MASK (0x7 << 12) +#define RT5677_IF2_DAC4_SFT 12 +#define RT5677_IF2_DAC5_MASK (0x7 << 8) +#define RT5677_IF2_DAC5_SFT 8 +#define RT5677_IF2_DAC6_MASK (0x7 << 4) +#define RT5677_IF2_DAC6_SFT 4 +#define RT5677_IF2_DAC7_MASK (0x7 << 0) +#define RT5677_IF2_DAC7_SFT 0 /* Digital Microphone Control 1 (0x50) */ #define RT5677_DMIC_1_EN_MASK (0x1 << 15) @@ -1367,6 +1440,48 @@ #define RT5677_SEL_SRC_IB01 (0x1 << 0) #define RT5677_SEL_SRC_IB01_SFT 0 +/* Jack Detect Control 1 (0xb5) */ +#define RT5677_SEL_GPIO_JD1_MASK (0x3 << 14) +#define RT5677_SEL_GPIO_JD1_SFT 14 +#define RT5677_SEL_GPIO_JD2_MASK (0x3 << 12) +#define RT5677_SEL_GPIO_JD2_SFT 12 +#define RT5677_SEL_GPIO_JD3_MASK (0x3 << 10) +#define RT5677_SEL_GPIO_JD3_SFT 10 + +/* IRQ Control 1 (0xbd) */ +#define RT5677_STA_GPIO_JD1 (0x1 << 15) +#define RT5677_STA_GPIO_JD1_SFT 15 +#define RT5677_EN_IRQ_GPIO_JD1 (0x1 << 14) +#define RT5677_EN_IRQ_GPIO_JD1_SFT 14 +#define RT5677_EN_GPIO_JD1_STICKY (0x1 << 13) +#define RT5677_EN_GPIO_JD1_STICKY_SFT 13 +#define RT5677_INV_GPIO_JD1 (0x1 << 12) +#define RT5677_INV_GPIO_JD1_SFT 12 +#define RT5677_STA_GPIO_JD2 (0x1 << 11) +#define RT5677_STA_GPIO_JD2_SFT 11 +#define RT5677_EN_IRQ_GPIO_JD2 (0x1 << 10) +#define RT5677_EN_IRQ_GPIO_JD2_SFT 10 +#define RT5677_EN_GPIO_JD2_STICKY (0x1 << 9) +#define RT5677_EN_GPIO_JD2_STICKY_SFT 9 +#define RT5677_INV_GPIO_JD2 (0x1 << 8) +#define RT5677_INV_GPIO_JD2_SFT 8 +#define RT5677_STA_MICBIAS1_OVCD (0x1 << 7) +#define RT5677_STA_MICBIAS1_OVCD_SFT 7 +#define RT5677_EN_IRQ_MICBIAS1_OVCD (0x1 << 6) +#define RT5677_EN_IRQ_MICBIAS1_OVCD_SFT 6 +#define RT5677_EN_MICBIAS1_OVCD_STICKY (0x1 << 5) +#define RT5677_EN_MICBIAS1_OVCD_STICKY_SFT 5 +#define RT5677_INV_MICBIAS1_OVCD (0x1 << 4) +#define RT5677_INV_MICBIAS1_OVCD_SFT 4 +#define RT5677_STA_GPIO_JD3 (0x1 << 3) +#define RT5677_STA_GPIO_JD3_SFT 3 +#define RT5677_EN_IRQ_GPIO_JD3 (0x1 << 2) +#define RT5677_EN_IRQ_GPIO_JD3_SFT 2 +#define RT5677_EN_GPIO_JD3_STICKY (0x1 << 1) +#define RT5677_EN_GPIO_JD3_STICKY_SFT 1 +#define RT5677_INV_GPIO_JD3 (0x1 << 0) +#define RT5677_INV_GPIO_JD3_SFT 0 + /* GPIO status (0xbf) */ #define RT5677_GPIO6_STATUS_MASK (0x1 << 5) #define RT5677_GPIO6_STATUS_SFT 5 @@ -1506,6 +1621,9 @@ #define RT5677_GPIO5_FUNC_GPIO (0x0 << 9) #define RT5677_GPIO5_FUNC_DMIC (0x1 << 9) +#define RT5677_FIRMWARE1 "rt5677_dsp_fw1.bin" +#define RT5677_FIRMWARE2 "rt5677_dsp_fw2.bin" + /* System Clock Source */ enum { RT5677_SCLK_S_MCLK, @@ -1541,10 +1659,18 @@ enum { RT5677_GPIO_NUM, }; +enum { + RT5677_IRQ_JD1, + RT5677_IRQ_JD2, + RT5677_IRQ_JD3, +}; + struct rt5677_priv { struct snd_soc_codec *codec; struct rt5677_platform_data pdata; - struct regmap *regmap; + struct regmap *regmap, *regmap_physical; + const struct firmware *fw1, *fw2; + struct mutex dsp_cmd_lock, dsp_pri_lock; int sysclk; int sysclk_src; @@ -1558,6 +1684,10 @@ struct rt5677_priv { #ifdef CONFIG_GPIOLIB struct gpio_chip gpio_chip; #endif + bool dsp_vad_en; + struct regmap_irq_chip_data *irq_data; + bool is_dsp_mode; + bool is_vref_slow; }; #endif /* __RT5677_H__ */ diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 6bb77d76561..29cf7ce610f 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -16,6 +16,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/clk.h> +#include <linux/log2.h> #include <linux/regmap.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> @@ -121,6 +122,13 @@ struct ldo_regulator { bool enabled; }; +enum sgtl5000_micbias_resistor { + SGTL5000_MICBIAS_OFF = 0, + SGTL5000_MICBIAS_2K = 2, + SGTL5000_MICBIAS_4K = 4, + SGTL5000_MICBIAS_8K = 8, +}; + /* sgtl5000 private structure in codec */ struct sgtl5000_priv { int sysclk; /* sysclk rate */ @@ -131,6 +139,8 @@ struct sgtl5000_priv { struct regmap *regmap; struct clk *mclk; int revision; + u8 micbias_resistor; + u8 micbias_voltage; }; /* @@ -145,12 +155,14 @@ struct sgtl5000_priv { static int mic_bias_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(w->codec); + switch (event) { case SND_SOC_DAPM_POST_PMU: - /* change mic bias resistor to 4Kohm */ + /* change mic bias resistor */ snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL, - SGTL5000_BIAS_R_MASK, - SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT); + SGTL5000_BIAS_R_MASK, + sgtl5000->micbias_resistor << SGTL5000_BIAS_R_SHIFT); break; case SND_SOC_DAPM_PRE_PMD: @@ -530,16 +542,16 @@ static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai, /* * set clock according to i2s frame clock, - * sgtl5000 provide 2 clock sources. - * 1. sys_mclk. sample freq can only configure to + * sgtl5000 provides 2 clock sources: + * 1. sys_mclk: sample freq can only be configured to * 1/256, 1/384, 1/512 of sys_mclk. - * 2. pll. can derive any audio clocks. + * 2. pll: can derive any audio clocks. * * clock setting rules: - * 1. in slave mode, only sys_mclk can use. - * 2. as constraint by sys_mclk, sample freq should - * set to 32k, 44.1k and above. - * 3. using sys_mclk prefer to pll to save power. + * 1. in slave mode, only sys_mclk can be used + * 2. as constraint by sys_mclk, sample freq should be set to 32 kHz, 44.1 kHz + * and above. + * 3. usage of sys_mclk is preferred over pll to save power. */ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) { @@ -549,8 +561,8 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) /* * sample freq should be divided by frame clock, - * if frame clock lower than 44.1khz, sample feq should set to - * 32khz or 44.1khz. + * if frame clock is lower than 44.1 kHz, sample freq should be set to + * 32 kHz or 44.1 kHz. */ switch (frame_rate) { case 8000: @@ -603,9 +615,10 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) /* * calculate the divider of mclk/sample_freq, - * factor of freq =96k can only be 256, since mclk in range (12m,27m) + * factor of freq = 96 kHz can only be 256, since mclk is in the range + * of 8 MHz - 27 MHz */ - switch (sgtl5000->sysclk / sys_fs) { + switch (sgtl5000->sysclk / frame_rate) { case 256: clk_ctl |= SGTL5000_MCLK_FREQ_256FS << SGTL5000_MCLK_FREQ_SHIFT; @@ -619,7 +632,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) SGTL5000_MCLK_FREQ_SHIFT; break; default: - /* if mclk not satisify the divider, use pll */ + /* if mclk does not satisfy the divider, use pll */ if (sgtl5000->master) { clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT; @@ -628,7 +641,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) "PLL not supported in slave mode\n"); dev_err(codec->dev, "%d ratio is not supported. " "SYS_MCLK needs to be 256, 384 or 512 * fs\n", - sgtl5000->sysclk / sys_fs); + sgtl5000->sysclk / frame_rate); return -EINVAL; } } @@ -795,7 +808,7 @@ static int ldo_regulator_enable(struct regulator_dev *dev) SGTL5000_LINEREG_D_POWERUP, SGTL5000_LINEREG_D_POWERUP); - /* when internal ldo enabled, simple digital power can be disabled */ + /* when internal ldo is enabled, simple digital power can be disabled */ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, SGTL5000_LINREG_SIMPLE_POWERUP, 0); @@ -1079,7 +1092,7 @@ static bool sgtl5000_readable(struct device *dev, unsigned int reg) /* * sgtl5000 has 3 internal power supplies: * 1. VAG, normally set to vdda/2 - * 2. chargepump, set to different value + * 2. charge pump, set to different value * according to voltage of vdda and vddio * 3. line out VAG, normally set to vddio/2 * @@ -1299,8 +1312,7 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) /* enable small pop, introduce 400ms delay in turning off */ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, - SGTL5000_SMALL_POP, - SGTL5000_SMALL_POP); + SGTL5000_SMALL_POP, 1); /* disable short cut detector */ snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0); @@ -1326,8 +1338,13 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) SGTL5000_HP_ZCD_EN | SGTL5000_ADC_ZCD_EN); - snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 2); + snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, + sgtl5000->micbias_resistor << SGTL5000_BIAS_R_SHIFT); + snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, + sgtl5000->micbias_voltage << SGTL5000_BIAS_R_SHIFT); /* * disable DAP * TODO: @@ -1417,10 +1434,10 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, { struct sgtl5000_priv *sgtl5000; int ret, reg, rev; - unsigned int mclk; + struct device_node *np = client->dev.of_node; + u32 value; - sgtl5000 = devm_kzalloc(&client->dev, sizeof(struct sgtl5000_priv), - GFP_KERNEL); + sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL); if (!sgtl5000) return -ENOMEM; @@ -1441,14 +1458,6 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, return ret; } - /* SGTL5000 SYS_MCLK should be between 8 and 27 MHz */ - mclk = clk_get_rate(sgtl5000->mclk); - if (mclk < 8000000 || mclk > 27000000) { - dev_err(&client->dev, "Invalid SYS_CLK frequency: %u.%03uMHz\n", - mclk / 1000000, mclk / 1000 % 1000); - return -EINVAL; - } - ret = clk_prepare_enable(sgtl5000->mclk); if (ret) return ret; @@ -1470,6 +1479,47 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev); sgtl5000->revision = rev; + if (np) { + if (!of_property_read_u32(np, + "micbias-resistor-k-ohms", &value)) { + switch (value) { + case SGTL5000_MICBIAS_OFF: + sgtl5000->micbias_resistor = 0; + break; + case SGTL5000_MICBIAS_2K: + sgtl5000->micbias_resistor = 1; + break; + case SGTL5000_MICBIAS_4K: + sgtl5000->micbias_resistor = 2; + break; + case SGTL5000_MICBIAS_8K: + sgtl5000->micbias_resistor = 3; + break; + default: + sgtl5000->micbias_resistor = 2; + dev_err(&client->dev, + "Unsuitable MicBias resistor\n"); + } + } else { + /* default is 4Kohms */ + sgtl5000->micbias_resistor = 2; + } + if (!of_property_read_u32(np, + "micbias-voltage-m-volts", &value)) { + /* 1250mV => 0 */ + /* steps of 250mV */ + if ((value >= 1250) && (value <= 3000)) + sgtl5000->micbias_voltage = (value / 250) - 5; + else { + sgtl5000->micbias_voltage = 0; + dev_err(&client->dev, + "Unsuitable MicBias resistor\n"); + } + } else { + sgtl5000->micbias_voltage = 0; + } + } + i2c_set_clientdata(client, sgtl5000); /* Ensure sgtl5000 will start with sane register values */ diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h index 2f8c88931f6..bd7a344bf8c 100644 --- a/sound/soc/codecs/sgtl5000.h +++ b/sound/soc/codecs/sgtl5000.h @@ -275,7 +275,7 @@ #define SGTL5000_BIAS_CTRL_MASK 0x000e #define SGTL5000_BIAS_CTRL_SHIFT 1 #define SGTL5000_BIAS_CTRL_WIDTH 3 -#define SGTL5000_SMALL_POP 0x0001 +#define SGTL5000_SMALL_POP 0 /* * SGTL5000_CHIP_MIC_CTRL diff --git a/sound/soc/codecs/sigmadsp-i2c.c b/sound/soc/codecs/sigmadsp-i2c.c index 246081aae8c..21ca3a5e9f6 100644 --- a/sound/soc/codecs/sigmadsp-i2c.c +++ b/sound/soc/codecs/sigmadsp-i2c.c @@ -6,29 +6,88 @@ * Licensed under the GPL-2 or later. */ -#include <linux/i2c.h> #include <linux/export.h> +#include <linux/i2c.h> #include <linux/module.h> +#include <linux/slab.h> +#include <asm/unaligned.h> #include "sigmadsp.h" -static int sigma_action_write_i2c(void *control_data, - const struct sigma_action *sa, size_t len) +static int sigmadsp_write_i2c(void *control_data, + unsigned int addr, const uint8_t data[], size_t len) +{ + uint8_t *buf; + int ret; + + buf = kzalloc(2 + len, GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + + put_unaligned_be16(addr, buf); + memcpy(buf + 2, data, len); + + ret = i2c_master_send(control_data, buf, len + 2); + + kfree(buf); + + return ret; +} + +static int sigmadsp_read_i2c(void *control_data, + unsigned int addr, uint8_t data[], size_t len) { - return i2c_master_send(control_data, (const unsigned char *)&sa->addr, - len); + struct i2c_client *client = control_data; + struct i2c_msg msgs[2]; + uint8_t buf[2]; + int ret; + + put_unaligned_be16(addr, buf); + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(buf); + msgs[0].buf = buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = len; + msgs[1].buf = data; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + else if (ret != ARRAY_SIZE(msgs)) + return -EIO; + return 0; } -int process_sigma_firmware(struct i2c_client *client, const char *name) +/** + * devm_sigmadsp_init_i2c() - Initialize SigmaDSP instance + * @client: The parent I2C device + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init_i2c(struct i2c_client *client, + const struct sigmadsp_ops *ops, const char *firmware_name) { - struct sigma_firmware ssfw; + struct sigmadsp *sigmadsp; + + sigmadsp = devm_sigmadsp_init(&client->dev, ops, firmware_name); + if (IS_ERR(sigmadsp)) + return sigmadsp; - ssfw.control_data = client; - ssfw.write = sigma_action_write_i2c; + sigmadsp->control_data = client; + sigmadsp->write = sigmadsp_write_i2c; + sigmadsp->read = sigmadsp_read_i2c; - return _process_sigma_firmware(&client->dev, &ssfw, name); + return sigmadsp; } -EXPORT_SYMBOL(process_sigma_firmware); +EXPORT_SYMBOL_GPL(devm_sigmadsp_init_i2c); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_DESCRIPTION("SigmaDSP I2C firmware loader"); diff --git a/sound/soc/codecs/sigmadsp-regmap.c b/sound/soc/codecs/sigmadsp-regmap.c index f78ed8d2cfb..912861be5b8 100644 --- a/sound/soc/codecs/sigmadsp-regmap.c +++ b/sound/soc/codecs/sigmadsp-regmap.c @@ -12,24 +12,48 @@ #include "sigmadsp.h" -static int sigma_action_write_regmap(void *control_data, - const struct sigma_action *sa, size_t len) +static int sigmadsp_write_regmap(void *control_data, + unsigned int addr, const uint8_t data[], size_t len) { - return regmap_raw_write(control_data, be16_to_cpu(sa->addr), - sa->payload, len - 2); + return regmap_raw_write(control_data, addr, + data, len); } -int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap, - const char *name) +static int sigmadsp_read_regmap(void *control_data, + unsigned int addr, uint8_t data[], size_t len) { - struct sigma_firmware ssfw; + return regmap_raw_read(control_data, addr, + data, len); +} + +/** + * devm_sigmadsp_init_i2c() - Initialize SigmaDSP instance + * @dev: The parent device + * @regmap: Regmap instance to use + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init_regmap(struct device *dev, + struct regmap *regmap, const struct sigmadsp_ops *ops, + const char *firmware_name) +{ + struct sigmadsp *sigmadsp; + + sigmadsp = devm_sigmadsp_init(dev, ops, firmware_name); + if (IS_ERR(sigmadsp)) + return sigmadsp; - ssfw.control_data = regmap; - ssfw.write = sigma_action_write_regmap; + sigmadsp->control_data = regmap; + sigmadsp->write = sigmadsp_write_regmap; + sigmadsp->read = sigmadsp_read_regmap; - return _process_sigma_firmware(dev, &ssfw, name); + return sigmadsp; } -EXPORT_SYMBOL(process_sigma_firmware_regmap); +EXPORT_SYMBOL_GPL(devm_sigmadsp_init_regmap); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_DESCRIPTION("SigmaDSP regmap firmware loader"); diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c index f2de7e049bc..d53680ac78e 100644 --- a/sound/soc/codecs/sigmadsp.c +++ b/sound/soc/codecs/sigmadsp.c @@ -1,23 +1,74 @@ /* * Load Analog Devices SigmaStudio firmware files * - * Copyright 2009-2011 Analog Devices Inc. + * Copyright 2009-2014 Analog Devices Inc. * * Licensed under the GPL-2 or later. */ #include <linux/crc32.h> -#include <linux/delay.h> #include <linux/firmware.h> #include <linux/kernel.h> #include <linux/i2c.h> #include <linux/regmap.h> #include <linux/module.h> +#include <linux/slab.h> + +#include <sound/control.h> +#include <sound/soc.h> #include "sigmadsp.h" #define SIGMA_MAGIC "ADISIGM" +#define SIGMA_FW_CHUNK_TYPE_DATA 0 +#define SIGMA_FW_CHUNK_TYPE_CONTROL 1 +#define SIGMA_FW_CHUNK_TYPE_SAMPLERATES 2 + +struct sigmadsp_control { + struct list_head head; + uint32_t samplerates; + unsigned int addr; + unsigned int num_bytes; + const char *name; + struct snd_kcontrol *kcontrol; + bool cached; + uint8_t cache[]; +}; + +struct sigmadsp_data { + struct list_head head; + uint32_t samplerates; + unsigned int addr; + unsigned int length; + uint8_t data[]; +}; + +struct sigma_fw_chunk { + __le32 length; + __le32 tag; + __le32 samplerates; +} __packed; + +struct sigma_fw_chunk_data { + struct sigma_fw_chunk chunk; + __le16 addr; + uint8_t data[]; +} __packed; + +struct sigma_fw_chunk_control { + struct sigma_fw_chunk chunk; + __le16 type; + __le16 addr; + __le16 num_bytes; + const char name[]; +} __packed; + +struct sigma_fw_chunk_samplerate { + struct sigma_fw_chunk chunk; + __le32 samplerates[]; +} __packed; + struct sigma_firmware_header { unsigned char magic[7]; u8 version; @@ -28,12 +79,286 @@ enum { SIGMA_ACTION_WRITEXBYTES = 0, SIGMA_ACTION_WRITESINGLE, SIGMA_ACTION_WRITESAFELOAD, - SIGMA_ACTION_DELAY, - SIGMA_ACTION_PLLWAIT, - SIGMA_ACTION_NOOP, SIGMA_ACTION_END, }; +struct sigma_action { + u8 instr; + u8 len_hi; + __le16 len; + __be16 addr; + unsigned char payload[]; +} __packed; + +static int sigmadsp_write(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t data[], size_t len) +{ + return sigmadsp->write(sigmadsp->control_data, addr, data, len); +} + +static int sigmadsp_read(struct sigmadsp *sigmadsp, unsigned int addr, + uint8_t data[], size_t len) +{ + return sigmadsp->read(sigmadsp->control_data, addr, data, len); +} + +static int sigmadsp_ctrl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + + info->type = SNDRV_CTL_ELEM_TYPE_BYTES; + info->count = ctrl->num_bytes; + + return 0; +} + +static int sigmadsp_ctrl_write(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, void *data) +{ + /* safeload loads up to 20 bytes in a atomic operation */ + if (ctrl->num_bytes > 4 && ctrl->num_bytes <= 20 && sigmadsp->ops && + sigmadsp->ops->safeload) + return sigmadsp->ops->safeload(sigmadsp, ctrl->addr, data, + ctrl->num_bytes); + else + return sigmadsp_write(sigmadsp, ctrl->addr, data, + ctrl->num_bytes); +} + +static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + uint8_t *data; + int ret = 0; + + mutex_lock(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + + if (!(kcontrol->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) + ret = sigmadsp_ctrl_write(sigmadsp, ctrl, data); + + if (ret == 0) { + memcpy(ctrl->cache, data, ctrl->num_bytes); + ctrl->cached = true; + } + + mutex_unlock(&sigmadsp->lock); + + return ret; +} + +static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + + mutex_lock(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, + ctrl->num_bytes); + } + + if (ret == 0) { + ctrl->cached = true; + memcpy(ucontrol->value.bytes.data, ctrl->cache, + ctrl->num_bytes); + } + + mutex_unlock(&sigmadsp->lock); + + return ret; +} + +static void sigmadsp_control_free(struct snd_kcontrol *kcontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + + ctrl->kcontrol = NULL; +} + +static bool sigma_fw_validate_control_name(const char *name, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + /* Normal ASCII characters are valid */ + if (name[i] < ' ' || name[i] > '~') + return false; + } + + return true; +} + +static int sigma_fw_load_control(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_control *ctrl_chunk; + struct sigmadsp_control *ctrl; + unsigned int num_bytes; + size_t name_len; + char *name; + int ret; + + if (length <= sizeof(*ctrl_chunk)) + return -EINVAL; + + ctrl_chunk = (const struct sigma_fw_chunk_control *)chunk; + + name_len = length - sizeof(*ctrl_chunk); + if (name_len >= SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + name_len = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1; + + /* Make sure there are no non-displayable characaters in the string */ + if (!sigma_fw_validate_control_name(ctrl_chunk->name, name_len)) + return -EINVAL; + + num_bytes = le16_to_cpu(ctrl_chunk->num_bytes); + ctrl = kzalloc(sizeof(*ctrl) + num_bytes, GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + name = kzalloc(name_len + 1, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto err_free_ctrl; + } + memcpy(name, ctrl_chunk->name, name_len); + name[name_len] = '\0'; + ctrl->name = name; + + ctrl->addr = le16_to_cpu(ctrl_chunk->addr); + ctrl->num_bytes = num_bytes; + ctrl->samplerates = le32_to_cpu(chunk->samplerates); + + list_add_tail(&ctrl->head, &sigmadsp->ctrl_list); + + return 0; + +err_free_ctrl: + kfree(ctrl); + + return ret; +} + +static int sigma_fw_load_data(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_data *data_chunk; + struct sigmadsp_data *data; + + if (length <= sizeof(*data_chunk)) + return -EINVAL; + + data_chunk = (struct sigma_fw_chunk_data *)chunk; + + length -= sizeof(*data_chunk); + + data = kzalloc(sizeof(*data) + length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = le16_to_cpu(data_chunk->addr); + data->length = length; + data->samplerates = le32_to_cpu(chunk->samplerates); + memcpy(data->data, data_chunk->data, length); + list_add_tail(&data->head, &sigmadsp->data_list); + + return 0; +} + +static int sigma_fw_load_samplerates(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_samplerate *rate_chunk; + unsigned int num_rates; + unsigned int *rates; + unsigned int i; + + rate_chunk = (const struct sigma_fw_chunk_samplerate *)chunk; + + num_rates = (length - sizeof(*rate_chunk)) / sizeof(__le32); + + if (num_rates > 32 || num_rates == 0) + return -EINVAL; + + /* We only allow one samplerates block per file */ + if (sigmadsp->rate_constraints.count) + return -EINVAL; + + rates = kcalloc(num_rates, sizeof(*rates), GFP_KERNEL); + if (!rates) + return -ENOMEM; + + for (i = 0; i < num_rates; i++) + rates[i] = le32_to_cpu(rate_chunk->samplerates[i]); + + sigmadsp->rate_constraints.count = num_rates; + sigmadsp->rate_constraints.list = rates; + + return 0; +} + +static int sigmadsp_fw_load_v2(struct sigmadsp *sigmadsp, + const struct firmware *fw) +{ + struct sigma_fw_chunk *chunk; + unsigned int length, pos; + int ret; + + /* + * Make sure that there is at least one chunk to avoid integer + * underflows later on. Empty firmware is still valid though. + */ + if (fw->size < sizeof(*chunk) + sizeof(struct sigma_firmware_header)) + return 0; + + pos = sizeof(struct sigma_firmware_header); + + while (pos < fw->size - sizeof(*chunk)) { + chunk = (struct sigma_fw_chunk *)(fw->data + pos); + + length = le32_to_cpu(chunk->length); + + if (length > fw->size - pos || length < sizeof(*chunk)) + return -EINVAL; + + switch (le32_to_cpu(chunk->tag)) { + case SIGMA_FW_CHUNK_TYPE_DATA: + ret = sigma_fw_load_data(sigmadsp, chunk, length); + break; + case SIGMA_FW_CHUNK_TYPE_CONTROL: + ret = sigma_fw_load_control(sigmadsp, chunk, length); + break; + case SIGMA_FW_CHUNK_TYPE_SAMPLERATES: + ret = sigma_fw_load_samplerates(sigmadsp, chunk, length); + break; + default: + dev_warn(sigmadsp->dev, "Unknown chunk type: %d\n", + chunk->tag); + ret = 0; + break; + } + + if (ret) + return ret; + + /* + * This can not overflow since if length is larger than the + * maximum firmware size (0x4000000) we'll error out earilier. + */ + pos += ALIGN(length, sizeof(__le32)); + } + + return 0; +} + static inline u32 sigma_action_len(struct sigma_action *sa) { return (sa->len_hi << 16) | le16_to_cpu(sa->len); @@ -62,11 +387,11 @@ static size_t sigma_action_size(struct sigma_action *sa) * Returns a negative error value in case of an error, 0 if processing of * the firmware should be stopped after this action, 1 otherwise. */ -static int -process_sigma_action(struct sigma_firmware *ssfw, struct sigma_action *sa) +static int process_sigma_action(struct sigmadsp *sigmadsp, + struct sigma_action *sa) { size_t len = sigma_action_len(sa); - int ret; + struct sigmadsp_data *data; pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__, sa->instr, sa->addr, len); @@ -75,13 +400,17 @@ process_sigma_action(struct sigma_firmware *ssfw, struct sigma_action *sa) case SIGMA_ACTION_WRITEXBYTES: case SIGMA_ACTION_WRITESINGLE: case SIGMA_ACTION_WRITESAFELOAD: - ret = ssfw->write(ssfw->control_data, sa, len); - if (ret < 0) + if (len < 3) return -EINVAL; - break; - case SIGMA_ACTION_DELAY: - udelay(len); - len = 0; + + data = kzalloc(sizeof(*data) + len - 2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = be16_to_cpu(sa->addr); + data->length = len - 2; + memcpy(data->data, sa->payload, data->length); + list_add_tail(&data->head, &sigmadsp->data_list); break; case SIGMA_ACTION_END: return 0; @@ -92,22 +421,24 @@ process_sigma_action(struct sigma_firmware *ssfw, struct sigma_action *sa) return 1; } -static int -process_sigma_actions(struct sigma_firmware *ssfw) +static int sigmadsp_fw_load_v1(struct sigmadsp *sigmadsp, + const struct firmware *fw) { struct sigma_action *sa; - size_t size; + size_t size, pos; int ret; - while (ssfw->pos + sizeof(*sa) <= ssfw->fw->size) { - sa = (struct sigma_action *)(ssfw->fw->data + ssfw->pos); + pos = sizeof(struct sigma_firmware_header); + + while (pos + sizeof(*sa) <= fw->size) { + sa = (struct sigma_action *)(fw->data + pos); size = sigma_action_size(sa); - ssfw->pos += size; - if (ssfw->pos > ssfw->fw->size || size == 0) + pos += size; + if (pos > fw->size || size == 0) break; - ret = process_sigma_action(ssfw, sa); + ret = process_sigma_action(sigmadsp, sa); pr_debug("%s: action returned %i\n", __func__, ret); @@ -115,29 +446,47 @@ process_sigma_actions(struct sigma_firmware *ssfw) return ret; } - if (ssfw->pos != ssfw->fw->size) + if (pos != fw->size) return -EINVAL; return 0; } -int _process_sigma_firmware(struct device *dev, - struct sigma_firmware *ssfw, const char *name) +static void sigmadsp_firmware_release(struct sigmadsp *sigmadsp) { - int ret; - struct sigma_firmware_header *ssfw_head; + struct sigmadsp_control *ctrl, *_ctrl; + struct sigmadsp_data *data, *_data; + + list_for_each_entry_safe(ctrl, _ctrl, &sigmadsp->ctrl_list, head) { + kfree(ctrl->name); + kfree(ctrl); + } + + list_for_each_entry_safe(data, _data, &sigmadsp->data_list, head) + kfree(data); + + INIT_LIST_HEAD(&sigmadsp->ctrl_list); + INIT_LIST_HEAD(&sigmadsp->data_list); +} + +static void devm_sigmadsp_release(struct device *dev, void *res) +{ + sigmadsp_firmware_release((struct sigmadsp *)res); +} + +static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) +{ + const struct sigma_firmware_header *ssfw_head; const struct firmware *fw; + int ret; u32 crc; - pr_debug("%s: loading firmware %s\n", __func__, name); - /* first load the blob */ - ret = request_firmware(&fw, name, dev); + ret = request_firmware(&fw, name, sigmadsp->dev); if (ret) { pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); - return ret; + goto done; } - ssfw->fw = fw; /* then verify the header */ ret = -EINVAL; @@ -149,13 +498,13 @@ int _process_sigma_firmware(struct device *dev, * overflows later in the loading process. */ if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { - dev_err(dev, "Failed to load firmware: Invalid size\n"); + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); goto done; } ssfw_head = (void *)fw->data; if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { - dev_err(dev, "Failed to load firmware: Invalid magic\n"); + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); goto done; } @@ -163,23 +512,303 @@ int _process_sigma_firmware(struct device *dev, fw->size - sizeof(*ssfw_head)); pr_debug("%s: crc=%x\n", __func__, crc); if (crc != le32_to_cpu(ssfw_head->crc)) { - dev_err(dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", le32_to_cpu(ssfw_head->crc), crc); goto done; } - ssfw->pos = sizeof(*ssfw_head); + switch (ssfw_head->version) { + case 1: + ret = sigmadsp_fw_load_v1(sigmadsp, fw); + break; + case 2: + ret = sigmadsp_fw_load_v2(sigmadsp, fw); + break; + default: + dev_err(sigmadsp->dev, + "Failed to load firmware: Invalid version %d. Supported firmware versions: 1, 2\n", + ssfw_head->version); + ret = -EINVAL; + break; + } - /* finally process all of the actions */ - ret = process_sigma_actions(ssfw); + if (ret) + sigmadsp_firmware_release(sigmadsp); - done: +done: release_firmware(fw); - pr_debug("%s: loaded %s\n", __func__, name); + return ret; +} + +static int sigmadsp_init(struct sigmadsp *sigmadsp, struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name) +{ + sigmadsp->ops = ops; + sigmadsp->dev = dev; + + INIT_LIST_HEAD(&sigmadsp->ctrl_list); + INIT_LIST_HEAD(&sigmadsp->data_list); + mutex_init(&sigmadsp->lock); + + return sigmadsp_firmware_load(sigmadsp, firmware_name); +} + +/** + * devm_sigmadsp_init() - Initialize SigmaDSP instance + * @dev: The parent device + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init(struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name) +{ + struct sigmadsp *sigmadsp; + int ret; + + sigmadsp = devres_alloc(devm_sigmadsp_release, sizeof(*sigmadsp), + GFP_KERNEL); + if (!sigmadsp) + return ERR_PTR(-ENOMEM); + + ret = sigmadsp_init(sigmadsp, dev, ops, firmware_name); + if (ret) { + devres_free(sigmadsp); + return ERR_PTR(ret); + } + + devres_add(dev, sigmadsp); + + return sigmadsp; +} +EXPORT_SYMBOL_GPL(devm_sigmadsp_init); + +static int sigmadsp_rate_to_index(struct sigmadsp *sigmadsp, unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < sigmadsp->rate_constraints.count; i++) { + if (sigmadsp->rate_constraints.list[i] == rate) + return i; + } + + return -EINVAL; +} + +static unsigned int sigmadsp_get_samplerate_mask(struct sigmadsp *sigmadsp, + unsigned int samplerate) +{ + int samplerate_index; + + if (samplerate == 0) + return 0; + + if (sigmadsp->rate_constraints.count) { + samplerate_index = sigmadsp_rate_to_index(sigmadsp, samplerate); + if (samplerate_index < 0) + return 0; + + return BIT(samplerate_index); + } else { + return ~0; + } +} + +static bool sigmadsp_samplerate_valid(unsigned int supported, + unsigned int requested) +{ + /* All samplerates are supported */ + if (!supported) + return true; + + return supported & requested; +} + +static int sigmadsp_alloc_control(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, unsigned int samplerate_mask) +{ + struct snd_kcontrol_new template; + struct snd_kcontrol *kcontrol; + + memset(&template, 0, sizeof(template)); + template.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + template.name = ctrl->name; + template.info = sigmadsp_ctrl_info; + template.get = sigmadsp_ctrl_get; + template.put = sigmadsp_ctrl_put; + template.private_value = (unsigned long)ctrl; + template.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (!sigmadsp_samplerate_valid(ctrl->samplerates, samplerate_mask)) + template.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + + kcontrol = snd_ctl_new1(&template, sigmadsp); + if (!kcontrol) + return -ENOMEM; + + kcontrol->private_free = sigmadsp_control_free; + ctrl->kcontrol = kcontrol; + + return snd_ctl_add(sigmadsp->component->card->snd_card, kcontrol); +} + +static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, unsigned int samplerate_mask) +{ + struct snd_card *card = sigmadsp->component->card->snd_card; + struct snd_kcontrol_volatile *vd; + struct snd_ctl_elem_id id; + bool active; + bool changed = false; + + active = sigmadsp_samplerate_valid(ctrl->samplerates, samplerate_mask); + + down_write(&card->controls_rwsem); + if (!ctrl->kcontrol) { + up_write(&card->controls_rwsem); + return; + } + + id = ctrl->kcontrol->id; + vd = &ctrl->kcontrol->vd[0]; + if (active == (bool)(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) { + vd->access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + changed = true; + } + up_write(&card->controls_rwsem); + + if (active && changed) { + mutex_lock(&sigmadsp->lock); + if (ctrl->cached) + sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); + mutex_unlock(&sigmadsp->lock); + } + + if (changed) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &id); +} + +/** + * sigmadsp_attach() - Attach a sigmadsp instance to a ASoC component + * @sigmadsp: The sigmadsp instance to attach + * @component: The component to attach to + * + * Typically called in the components probe callback. + * + * Note, once this function has been called the firmware must not be released + * until after the ALSA snd_card that the component belongs to has been + * disconnected, even if sigmadsp_attach() returns an error. + */ +int sigmadsp_attach(struct sigmadsp *sigmadsp, + struct snd_soc_component *component) +{ + struct sigmadsp_control *ctrl; + unsigned int samplerate_mask; + int ret; + + sigmadsp->component = component; + + samplerate_mask = sigmadsp_get_samplerate_mask(sigmadsp, + sigmadsp->current_samplerate); + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) { + ret = sigmadsp_alloc_control(sigmadsp, ctrl, samplerate_mask); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(sigmadsp_attach); + +/** + * sigmadsp_setup() - Setup the DSP for the specified samplerate + * @sigmadsp: The sigmadsp instance to configure + * @samplerate: The samplerate the DSP should be configured for + * + * Loads the appropriate firmware program and parameter memory (if not already + * loaded) and enables the controls for the specified samplerate. Any control + * parameter changes that have been made previously will be restored. + * + * Returns 0 on success, a negative error code otherwise. + */ +int sigmadsp_setup(struct sigmadsp *sigmadsp, unsigned int samplerate) +{ + struct sigmadsp_control *ctrl; + unsigned int samplerate_mask; + struct sigmadsp_data *data; + int ret; + + if (sigmadsp->current_samplerate == samplerate) + return 0; + + samplerate_mask = sigmadsp_get_samplerate_mask(sigmadsp, samplerate); + if (samplerate_mask == 0) + return -EINVAL; + + list_for_each_entry(data, &sigmadsp->data_list, head) { + if (!sigmadsp_samplerate_valid(data->samplerates, + samplerate_mask)) + continue; + ret = sigmadsp_write(sigmadsp, data->addr, data->data, + data->length); + if (ret) + goto err; + } + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) + sigmadsp_activate_ctrl(sigmadsp, ctrl, samplerate_mask); + + sigmadsp->current_samplerate = samplerate; + + return 0; +err: + sigmadsp_reset(sigmadsp); return ret; } -EXPORT_SYMBOL_GPL(_process_sigma_firmware); +EXPORT_SYMBOL_GPL(sigmadsp_setup); + +/** + * sigmadsp_reset() - Notify the sigmadsp instance that the DSP has been reset + * @sigmadsp: The sigmadsp instance to reset + * + * Should be called whenever the DSP has been reset and parameter and program + * memory need to be re-loaded. + */ +void sigmadsp_reset(struct sigmadsp *sigmadsp) +{ + struct sigmadsp_control *ctrl; + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) + sigmadsp_activate_ctrl(sigmadsp, ctrl, false); + + sigmadsp->current_samplerate = 0; +} +EXPORT_SYMBOL_GPL(sigmadsp_reset); + +/** + * sigmadsp_restrict_params() - Applies DSP firmware specific constraints + * @sigmadsp: The sigmadsp instance + * @substream: The substream to restrict + * + * Applies samplerate constraints that may be required by the firmware Should + * typically be called from the CODEC/component drivers startup callback. + * + * Returns 0 on success, a negative error code otherwise. + */ +int sigmadsp_restrict_params(struct sigmadsp *sigmadsp, + struct snd_pcm_substream *substream) +{ + if (sigmadsp->rate_constraints.count == 0) + return 0; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &sigmadsp->rate_constraints); +} +EXPORT_SYMBOL_GPL(sigmadsp_restrict_params); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp.h b/sound/soc/codecs/sigmadsp.h index c47cd23e982..614475cbb82 100644 --- a/sound/soc/codecs/sigmadsp.h +++ b/sound/soc/codecs/sigmadsp.h @@ -11,31 +11,56 @@ #include <linux/device.h> #include <linux/regmap.h> +#include <linux/list.h> -struct sigma_action { - u8 instr; - u8 len_hi; - __le16 len; - __be16 addr; - unsigned char payload[]; -} __packed; +#include <sound/pcm.h> -struct sigma_firmware { - const struct firmware *fw; - size_t pos; +struct sigmadsp; +struct snd_soc_component; +struct snd_pcm_substream; + +struct sigmadsp_ops { + int (*safeload)(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t *data, size_t len); +}; + +struct sigmadsp { + const struct sigmadsp_ops *ops; + + struct list_head ctrl_list; + struct list_head data_list; + + struct snd_pcm_hw_constraint_list rate_constraints; + + unsigned int current_samplerate; + struct snd_soc_component *component; + struct device *dev; + + struct mutex lock; void *control_data; - int (*write)(void *control_data, const struct sigma_action *sa, - size_t len); + int (*write)(void *, unsigned int, const uint8_t *, size_t); + int (*read)(void *, unsigned int, uint8_t *, size_t); }; -int _process_sigma_firmware(struct device *dev, - struct sigma_firmware *ssfw, const char *name); +struct sigmadsp *devm_sigmadsp_init(struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name); +void sigmadsp_reset(struct sigmadsp *sigmadsp); + +int sigmadsp_restrict_params(struct sigmadsp *sigmadsp, + struct snd_pcm_substream *substream); struct i2c_client; -extern int process_sigma_firmware(struct i2c_client *client, const char *name); -extern int process_sigma_firmware_regmap(struct device *dev, - struct regmap *regmap, const char *name); +struct sigmadsp *devm_sigmadsp_init_regmap(struct device *dev, + struct regmap *regmap, const struct sigmadsp_ops *ops, + const char *firmware_name); +struct sigmadsp *devm_sigmadsp_init_i2c(struct i2c_client *client, + const struct sigmadsp_ops *ops, const char *firmware_name); + +int sigmadsp_attach(struct sigmadsp *sigmadsp, + struct snd_soc_component *component); +int sigmadsp_setup(struct sigmadsp *sigmadsp, unsigned int rate); +void sigmadsp_reset(struct sigmadsp *sigmadsp); #endif diff --git a/sound/soc/codecs/sirf-audio-codec.c b/sound/soc/codecs/sirf-audio-codec.c index 06ba4923fd5..07eea20e664 100644 --- a/sound/soc/codecs/sirf-audio-codec.c +++ b/sound/soc/codecs/sirf-audio-codec.c @@ -120,7 +120,8 @@ static int atlas6_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w, { #define ATLAS6_CODEC_ENABLE_BITS (1 << 29) #define ATLAS6_CODEC_RESET_BITS (1 << 28) - struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(w->codec->dev); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct sirf_audio_codec *sirf_audio_codec = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_PRE_PMU: enable_and_reset_codec(sirf_audio_codec->regmap, @@ -142,7 +143,8 @@ static int prima2_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w, { #define PRIMA2_CODEC_ENABLE_BITS (1 << 27) #define PRIMA2_CODEC_RESET_BITS (1 << 26) - struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(w->codec->dev); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct sirf_audio_codec *sirf_audio_codec = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMU: enable_and_reset_codec(sirf_audio_codec->regmap, diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c index cf8fa40662f..31d97cd5e59 100644 --- a/sound/soc/codecs/sn95031.c +++ b/sound/soc/codecs/sn95031.c @@ -867,25 +867,16 @@ static int sn95031_codec_probe(struct snd_soc_codec *codec) snd_soc_write(codec, SN95031_SSR2, 0x10); snd_soc_write(codec, SN95031_SSR3, 0x40); - snd_soc_add_codec_controls(codec, sn95031_snd_controls, - ARRAY_SIZE(sn95031_snd_controls)); - - return 0; -} - -static int sn95031_codec_remove(struct snd_soc_codec *codec) -{ - pr_debug("codec_remove called\n"); - sn95031_set_vaud_bias(codec, SND_SOC_BIAS_OFF); - return 0; } static struct snd_soc_codec_driver sn95031_codec = { .probe = sn95031_codec_probe, - .remove = sn95031_codec_remove, .set_bias_level = sn95031_set_vaud_bias, .idle_bias_off = true, + + .controls = sn95031_snd_controls, + .num_controls = ARRAY_SIZE(sn95031_snd_controls), .dapm_widgets = sn95031_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(sn95031_dapm_widgets), .dapm_routes = sn95031_audio_map, diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c index 4b5c17f8507..a984485108c 100644 --- a/sound/soc/codecs/ssm4567.c +++ b/sound/soc/codecs/ssm4567.c @@ -69,6 +69,22 @@ #define SSM4567_DAC_FS_64000_96000 0x3 #define SSM4567_DAC_FS_128000_192000 0x4 +/* SAI_CTRL_1 */ +#define SSM4567_SAI_CTRL_1_BCLK BIT(6) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_MASK (0x3 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_32 (0x0 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_48 (0x1 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_64 (0x2 << 4) +#define SSM4567_SAI_CTRL_1_FSYNC BIT(3) +#define SSM4567_SAI_CTRL_1_LJ BIT(2) +#define SSM4567_SAI_CTRL_1_TDM BIT(1) +#define SSM4567_SAI_CTRL_1_PDM BIT(0) + +/* SAI_CTRL_2 */ +#define SSM4567_SAI_CTRL_2_AUTO_SLOT BIT(3) +#define SSM4567_SAI_CTRL_2_TDM_SLOT_MASK 0x7 +#define SSM4567_SAI_CTRL_2_TDM_SLOT(x) (x) + struct ssm4567 { struct regmap *regmap; }; @@ -145,15 +161,24 @@ static const struct snd_kcontrol_new ssm4567_snd_controls[] = { SOC_SINGLE_TLV("Master Playback Volume", SSM4567_REG_DAC_VOLUME, 0, 0xff, 1, ssm4567_vol_tlv), SOC_SINGLE("DAC Low Power Mode Switch", SSM4567_REG_DAC_CTRL, 4, 1, 0), + SOC_SINGLE("DAC High Pass Filter Switch", SSM4567_REG_DAC_CTRL, + 5, 1, 0), }; +static const struct snd_kcontrol_new ssm4567_amplifier_boost_control = + SOC_DAPM_SINGLE("Switch", SSM4567_REG_POWER_CTRL, 1, 1, 1); + static const struct snd_soc_dapm_widget ssm4567_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM4567_REG_POWER_CTRL, 2, 1), + SND_SOC_DAPM_SWITCH("Amplifier Boost", SSM4567_REG_POWER_CTRL, 3, 1, + &ssm4567_amplifier_boost_control), SND_SOC_DAPM_OUTPUT("OUT"), }; static const struct snd_soc_dapm_route ssm4567_routes[] = { + { "OUT", NULL, "Amplifier Boost" }, + { "Amplifier Boost", "Switch", "DAC" }, { "OUT", NULL, "DAC" }, }; @@ -192,6 +217,107 @@ static int ssm4567_mute(struct snd_soc_dai *dai, int mute) SSM4567_DAC_MUTE, val); } +static int ssm4567_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ssm4567 *ssm4567 = snd_soc_dai_get_drvdata(dai); + unsigned int blcks; + int slot; + int ret; + + if (tx_mask == 0) + return -EINVAL; + + if (rx_mask && rx_mask != tx_mask) + return -EINVAL; + + slot = __ffs(tx_mask); + if (tx_mask != BIT(slot)) + return -EINVAL; + + switch (width) { + case 32: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_32; + break; + case 48: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_48; + break; + case 64: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_64; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(ssm4567->regmap, SSM4567_REG_SAI_CTRL_2, + SSM4567_SAI_CTRL_2_AUTO_SLOT | SSM4567_SAI_CTRL_2_TDM_SLOT_MASK, + SSM4567_SAI_CTRL_2_TDM_SLOT(slot)); + if (ret) + return ret; + + return regmap_update_bits(ssm4567->regmap, SSM4567_REG_SAI_CTRL_1, + SSM4567_SAI_CTRL_1_TDM_BLCKS_MASK, blcks); +} + +static int ssm4567_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct ssm4567 *ssm4567 = snd_soc_dai_get_drvdata(dai); + unsigned int ctrl1 = 0; + bool invert_fclk; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_fclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1 |= SSM4567_SAI_CTRL_1_BCLK; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl1 |= SSM4567_SAI_CTRL_1_FSYNC; + invert_fclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl1 |= SSM4567_SAI_CTRL_1_BCLK; + invert_fclk = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1 |= SSM4567_SAI_CTRL_1_LJ; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1 |= SSM4567_SAI_CTRL_1_TDM; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1 |= SSM4567_SAI_CTRL_1_TDM | SSM4567_SAI_CTRL_1_LJ; + break; + case SND_SOC_DAIFMT_PDM: + ctrl1 |= SSM4567_SAI_CTRL_1_PDM; + break; + default: + return -EINVAL; + } + + if (invert_fclk) + ctrl1 |= SSM4567_SAI_CTRL_1_FSYNC; + + return regmap_write(ssm4567->regmap, SSM4567_REG_SAI_CTRL_1, ctrl1); +} + static int ssm4567_set_power(struct ssm4567 *ssm4567, bool enable) { int ret = 0; @@ -246,6 +372,8 @@ static int ssm4567_set_bias_level(struct snd_soc_codec *codec, static const struct snd_soc_dai_ops ssm4567_dai_ops = { .hw_params = ssm4567_hw_params, .digital_mute = ssm4567_mute, + .set_fmt = ssm4567_set_dai_fmt, + .set_tdm_slot = ssm4567_set_tdm_slot, }; static struct snd_soc_dai_driver ssm4567_dai = { diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index 48740855566..7e18200dd6a 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -833,23 +833,6 @@ static struct snd_soc_dai_driver sta32x_dai = { .ops = &sta32x_dai_ops, }; -#ifdef CONFIG_PM -static int sta32x_suspend(struct snd_soc_codec *codec) -{ - sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int sta32x_resume(struct snd_soc_codec *codec) -{ - sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define sta32x_suspend NULL -#define sta32x_resume NULL -#endif - static int sta32x_probe(struct snd_soc_codec *codec) { struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); @@ -936,7 +919,6 @@ static int sta32x_remove(struct snd_soc_codec *codec) struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); sta32x_watchdog_stop(sta32x); - sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF); regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); return 0; @@ -955,9 +937,8 @@ static bool sta32x_reg_is_volatile(struct device *dev, unsigned int reg) static const struct snd_soc_codec_driver sta32x_codec = { .probe = sta32x_probe, .remove = sta32x_remove, - .suspend = sta32x_suspend, - .resume = sta32x_resume, .set_bias_level = sta32x_set_bias_level, + .suspend_bias_off = true, .controls = sta32x_snd_controls, .num_controls = ARRAY_SIZE(sta32x_snd_controls), .dapm_widgets = sta32x_dapm_widgets, diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index cc97dd52aa9..bda2ee18769 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -912,23 +912,6 @@ static struct snd_soc_dai_driver sta350_dai = { .ops = &sta350_dai_ops, }; -#ifdef CONFIG_PM -static int sta350_suspend(struct snd_soc_codec *codec) -{ - sta350_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int sta350_resume(struct snd_soc_codec *codec) -{ - sta350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define sta350_suspend NULL -#define sta350_resume NULL -#endif - static int sta350_probe(struct snd_soc_codec *codec) { struct sta350_priv *sta350 = snd_soc_codec_get_drvdata(codec); @@ -1065,7 +1048,6 @@ static int sta350_remove(struct snd_soc_codec *codec) { struct sta350_priv *sta350 = snd_soc_codec_get_drvdata(codec); - sta350_set_bias_level(codec, SND_SOC_BIAS_OFF); regulator_bulk_disable(ARRAY_SIZE(sta350->supplies), sta350->supplies); return 0; @@ -1074,9 +1056,8 @@ static int sta350_remove(struct snd_soc_codec *codec) static const struct snd_soc_codec_driver sta350_codec = { .probe = sta350_probe, .remove = sta350_remove, - .suspend = sta350_suspend, - .resume = sta350_resume, .set_bias_level = sta350_set_bias_level, + .suspend_bias_off = true, .controls = sta350_snd_controls, .num_controls = ARRAY_SIZE(sta350_snd_controls), .dapm_widgets = sta350_dapm_widgets, diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index 89c748dd3d6..b0f436d1012 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -319,41 +319,10 @@ static struct snd_soc_dai_driver sta529_dai = { .ops = &sta529_dai_ops, }; -static int sta529_probe(struct snd_soc_codec *codec) -{ - sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -/* power down chip */ -static int sta529_remove(struct snd_soc_codec *codec) -{ - sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int sta529_suspend(struct snd_soc_codec *codec) -{ - sta529_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int sta529_resume(struct snd_soc_codec *codec) -{ - sta529_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - static const struct snd_soc_codec_driver sta529_codec_driver = { - .probe = sta529_probe, - .remove = sta529_remove, .set_bias_level = sta529_set_bias_level, - .suspend = sta529_suspend, - .resume = sta529_resume, + .suspend_bias_off = true, + .controls = sta529_snd_controls, .num_controls = ARRAY_SIZE(sta529_snd_controls), }; diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index 53b810d23fe..dbff0c89be4 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -139,18 +139,19 @@ static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; if (reg > AC97_STAC_PAGE0) { stac9766_ac97_write(codec, AC97_INT_PAGING, 0); - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(ac97, reg, val); stac9766_ac97_write(codec, AC97_INT_PAGING, 1); return 0; } if (reg / 2 >= ARRAY_SIZE(stac9766_reg)) return -EIO; - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(ac97, reg, val); cache[reg / 2] = val; return 0; } @@ -158,11 +159,12 @@ static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); u16 val = 0, *cache = codec->reg_cache; if (reg > AC97_STAC_PAGE0) { stac9766_ac97_write(codec, AC97_INT_PAGING, 0); - val = soc_ac97_ops->read(codec->ac97, reg - AC97_STAC_PAGE0); + val = soc_ac97_ops->read(ac97, reg - AC97_STAC_PAGE0); stac9766_ac97_write(codec, AC97_INT_PAGING, 1); return val; } @@ -173,7 +175,7 @@ static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2) { - val = soc_ac97_ops->read(codec->ac97, reg); + val = soc_ac97_ops->read(ac97, reg); return val; } return cache[reg / 2]; @@ -240,45 +242,41 @@ static int stac9766_set_bias_level(struct snd_soc_codec *codec, static int stac9766_reset(struct snd_soc_codec *codec, int try_warm) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + if (try_warm && soc_ac97_ops->warm_reset) { - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(ac97); if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) return 1; } - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(ac97); if (soc_ac97_ops->warm_reset) - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(ac97); if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) return -EIO; return 0; } -static int stac9766_codec_suspend(struct snd_soc_codec *codec) -{ - stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static int stac9766_codec_resume(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); u16 id, reset; reset = 0; /* give the codec an AC97 warm reset to start the link */ reset: if (reset > 5) { - printk(KERN_ERR "stac9766 failed to resume"); + dev_err(codec->dev, "Failed to resume\n"); return -EIO; } - codec->ac97->bus->ops->warm_reset(codec->ac97); - id = soc_ac97_ops->read(codec->ac97, AC97_VENDOR_ID2); + ac97->bus->ops->warm_reset(ac97); + id = soc_ac97_ops->read(ac97, AC97_VENDOR_ID2); if (id != 0x4c13) { stac9766_reset(codec, 0); reset++; goto reset; } - stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } @@ -294,7 +292,6 @@ static const struct snd_soc_dai_ops stac9766_dai_ops_digital = { static struct snd_soc_dai_driver stac9766_dai[] = { { .name = "stac9766-hifi-analog", - .ac97_control = 1, /* stream cababilities */ .playback = { @@ -316,7 +313,6 @@ static struct snd_soc_dai_driver stac9766_dai[] = { }, { .name = "stac9766-hifi-IEC958", - .ac97_control = 1, /* stream cababilities */ .playback = { @@ -334,46 +330,48 @@ static struct snd_soc_dai_driver stac9766_dai[] = { static int stac9766_codec_probe(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97; int ret = 0; - ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0); - if (ret < 0) - goto codec_err; + ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(ac97)) + return PTR_ERR(ac97); + + snd_soc_codec_set_drvdata(codec, ac97); /* do a cold reset for the controller and then try * a warm reset followed by an optional cold reset for codec */ stac9766_reset(codec, 0); ret = stac9766_reset(codec, 1); if (ret < 0) { - printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); + dev_err(codec->dev, "Failed to reset: AC97 link error\n"); goto codec_err; } - stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - snd_soc_add_codec_controls(codec, stac9766_snd_ac97_controls, - ARRAY_SIZE(stac9766_snd_ac97_controls)); - return 0; codec_err: - snd_soc_free_ac97_codec(codec); + snd_soc_free_ac97_codec(ac97); return ret; } static int stac9766_codec_remove(struct snd_soc_codec *codec) { - snd_soc_free_ac97_codec(codec); + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + snd_soc_free_ac97_codec(ac97); return 0; } static struct snd_soc_codec_driver soc_codec_dev_stac9766 = { + .controls = stac9766_snd_ac97_controls, + .num_controls = ARRAY_SIZE(stac9766_snd_ac97_controls), .write = stac9766_ac97_write, .read = stac9766_ac97_read, .set_bias_level = stac9766_set_bias_level, + .suspend_bias_off = true, .probe = stac9766_codec_probe, .remove = stac9766_codec_remove, - .suspend = stac9766_codec_suspend, .resume = stac9766_codec_resume, .reg_cache_size = ARRAY_SIZE(stac9766_reg), .reg_word_size = sizeof(u16), diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index f039dc82597..b505212019e 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -345,7 +345,6 @@ static const struct reg_default tas2552_init_regs[] = { static int tas2552_codec_probe(struct snd_soc_codec *codec) { struct tas2552_data *tas2552 = snd_soc_codec_get_drvdata(codec); - struct snd_soc_dapm_context *dapm = &codec->dapm; int ret; tas2552->codec = codec; @@ -390,11 +389,6 @@ static int tas2552_codec_probe(struct snd_soc_codec *codec) snd_soc_write(codec, TAS2552_CFG_2, TAS2552_BOOST_EN | TAS2552_APT_EN | TAS2552_LIM_EN); - snd_soc_dapm_new_controls(dapm, tas2552_dapm_widgets, - ARRAY_SIZE(tas2552_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, tas2552_audio_map, - ARRAY_SIZE(tas2552_audio_map)); - return 0; patch_fail: @@ -462,6 +456,10 @@ static struct snd_soc_codec_driver soc_codec_dev_tas2552 = { .resume = tas2552_resume, .controls = tas2552_snd_controls, .num_controls = ARRAY_SIZE(tas2552_snd_controls), + .dapm_widgets = tas2552_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas2552_dapm_widgets), + .dapm_routes = tas2552_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas2552_audio_map), }; static const struct regmap_config tas2552_regmap_config = { diff --git a/sound/soc/codecs/tfa9879.c b/sound/soc/codecs/tfa9879.c new file mode 100644 index 00000000000..16f1b71edb5 --- /dev/null +++ b/sound/soc/codecs/tfa9879.c @@ -0,0 +1,328 @@ +/* + * tfa9879.c -- driver for NXP Semiconductors TFA9879 + * + * Copyright (C) 2014 Axentia Technologies AB + * Author: Peter Rosin <peda@axentia.se> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> + +#include "tfa9879.h" + +struct tfa9879_priv { + struct regmap *regmap; + int lsb_justified; +}; + +static int tfa9879_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tfa9879_priv *tfa9879 = snd_soc_codec_get_drvdata(codec); + int fs; + int i2s_set = 0; + + switch (params_rate(params)) { + case 8000: + fs = TFA9879_I2S_FS_8000; + break; + case 11025: + fs = TFA9879_I2S_FS_11025; + break; + case 12000: + fs = TFA9879_I2S_FS_12000; + break; + case 16000: + fs = TFA9879_I2S_FS_16000; + break; + case 22050: + fs = TFA9879_I2S_FS_22050; + break; + case 24000: + fs = TFA9879_I2S_FS_24000; + break; + case 32000: + fs = TFA9879_I2S_FS_32000; + break; + case 44100: + fs = TFA9879_I2S_FS_44100; + break; + case 48000: + fs = TFA9879_I2S_FS_48000; + break; + case 64000: + fs = TFA9879_I2S_FS_64000; + break; + case 88200: + fs = TFA9879_I2S_FS_88200; + break; + case 96000: + fs = TFA9879_I2S_FS_96000; + break; + default: + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + i2s_set = TFA9879_I2S_SET_LSB_J_16; + break; + case 24: + i2s_set = TFA9879_I2S_SET_LSB_J_24; + break; + default: + return -EINVAL; + } + + if (tfa9879->lsb_justified) + snd_soc_update_bits(codec, TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_SET_MASK, + i2s_set << TFA9879_I2S_SET_SHIFT); + + snd_soc_update_bits(codec, TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_FS_MASK, + fs << TFA9879_I2S_FS_SHIFT); + return 0; +} + +static int tfa9879_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + snd_soc_update_bits(codec, TFA9879_MISC_CONTROL, + TFA9879_S_MUTE_MASK, + !!mute << TFA9879_S_MUTE_SHIFT); + + return 0; +} + +static int tfa9879_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct tfa9879_priv *tfa9879 = snd_soc_codec_get_drvdata(codec); + int i2s_set; + int sck_pol; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sck_pol = TFA9879_SCK_POL_NORMAL; + break; + case SND_SOC_DAIFMT_IB_NF: + sck_pol = TFA9879_SCK_POL_INVERSE; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + tfa9879->lsb_justified = 0; + i2s_set = TFA9879_I2S_SET_I2S_24; + break; + case SND_SOC_DAIFMT_LEFT_J: + tfa9879->lsb_justified = 0; + i2s_set = TFA9879_I2S_SET_MSB_J_24; + break; + case SND_SOC_DAIFMT_RIGHT_J: + tfa9879->lsb_justified = 1; + i2s_set = TFA9879_I2S_SET_LSB_J_24; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, TFA9879_SERIAL_INTERFACE_1, + TFA9879_SCK_POL_MASK, + sck_pol << TFA9879_SCK_POL_SHIFT); + snd_soc_update_bits(codec, TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_SET_MASK, + i2s_set << TFA9879_I2S_SET_SHIFT); + return 0; +} + +static struct reg_default tfa9879_regs[] = { + { TFA9879_DEVICE_CONTROL, 0x0000 }, /* 0x00 */ + { TFA9879_SERIAL_INTERFACE_1, 0x0a18 }, /* 0x01 */ + { TFA9879_PCM_IOM2_FORMAT_1, 0x0007 }, /* 0x02 */ + { TFA9879_SERIAL_INTERFACE_2, 0x0a18 }, /* 0x03 */ + { TFA9879_PCM_IOM2_FORMAT_2, 0x0007 }, /* 0x04 */ + { TFA9879_EQUALIZER_A1, 0x59dd }, /* 0x05 */ + { TFA9879_EQUALIZER_A2, 0xc63e }, /* 0x06 */ + { TFA9879_EQUALIZER_B1, 0x651a }, /* 0x07 */ + { TFA9879_EQUALIZER_B2, 0xe53e }, /* 0x08 */ + { TFA9879_EQUALIZER_C1, 0x4616 }, /* 0x09 */ + { TFA9879_EQUALIZER_C2, 0xd33e }, /* 0x0a */ + { TFA9879_EQUALIZER_D1, 0x4df3 }, /* 0x0b */ + { TFA9879_EQUALIZER_D2, 0xea3e }, /* 0x0c */ + { TFA9879_EQUALIZER_E1, 0x5ee0 }, /* 0x0d */ + { TFA9879_EQUALIZER_E2, 0xf93e }, /* 0x0e */ + { TFA9879_BYPASS_CONTROL, 0x0093 }, /* 0x0f */ + { TFA9879_DYNAMIC_RANGE_COMPR, 0x92ba }, /* 0x10 */ + { TFA9879_BASS_TREBLE, 0x12a5 }, /* 0x11 */ + { TFA9879_HIGH_PASS_FILTER, 0x0004 }, /* 0x12 */ + { TFA9879_VOLUME_CONTROL, 0x10bd }, /* 0x13 */ + { TFA9879_MISC_CONTROL, 0x0000 }, /* 0x14 */ +}; + +static bool tfa9879_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == TFA9879_MISC_STATUS; +} + +static const DECLARE_TLV_DB_SCALE(volume_tlv, -7050, 50, 1); +static const DECLARE_TLV_DB_SCALE(tb_gain_tlv, -1800, 200, 0); +static const char * const tb_freq_text[] = { + "Low", "Mid", "High" +}; +static const struct soc_enum treble_freq_enum = + SOC_ENUM_SINGLE(TFA9879_BASS_TREBLE, TFA9879_F_TRBLE_SHIFT, + ARRAY_SIZE(tb_freq_text), tb_freq_text); +static const struct soc_enum bass_freq_enum = + SOC_ENUM_SINGLE(TFA9879_BASS_TREBLE, TFA9879_F_BASS_SHIFT, + ARRAY_SIZE(tb_freq_text), tb_freq_text); + +static const struct snd_kcontrol_new tfa9879_controls[] = { + SOC_SINGLE_TLV("PCM Playback Volume", TFA9879_VOLUME_CONTROL, + TFA9879_VOL_SHIFT, 0xbd, 1, volume_tlv), + SOC_SINGLE_TLV("Treble Volume", TFA9879_BASS_TREBLE, + TFA9879_G_TRBLE_SHIFT, 18, 0, tb_gain_tlv), + SOC_SINGLE_TLV("Bass Volume", TFA9879_BASS_TREBLE, + TFA9879_G_BASS_SHIFT, 18, 0, tb_gain_tlv), + SOC_ENUM("Treble Corner Freq", treble_freq_enum), + SOC_ENUM("Bass Corner Freq", bass_freq_enum), +}; + +static const struct snd_soc_dapm_widget tfa9879_dapm_widgets[] = { +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DAC", NULL, TFA9879_DEVICE_CONTROL, TFA9879_OPMODE_SHIFT, 0), +SND_SOC_DAPM_OUTPUT("LINEOUT"), +SND_SOC_DAPM_SUPPLY("POWER", TFA9879_DEVICE_CONTROL, TFA9879_POWERUP_SHIFT, 0, + NULL, 0), +}; + +static const struct snd_soc_dapm_route tfa9879_dapm_routes[] = { + { "DAC", NULL, "AIFINL" }, + { "DAC", NULL, "AIFINR" }, + + { "LINEOUT", NULL, "DAC" }, + + { "DAC", NULL, "POWER" }, +}; + +static const struct snd_soc_codec_driver tfa9879_codec = { + .controls = tfa9879_controls, + .num_controls = ARRAY_SIZE(tfa9879_controls), + + .dapm_widgets = tfa9879_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tfa9879_dapm_widgets), + .dapm_routes = tfa9879_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tfa9879_dapm_routes), +}; + +static const struct regmap_config tfa9879_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .volatile_reg = tfa9879_volatile_reg, + .max_register = TFA9879_MISC_STATUS, + .reg_defaults = tfa9879_regs, + .num_reg_defaults = ARRAY_SIZE(tfa9879_regs), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct snd_soc_dai_ops tfa9879_dai_ops = { + .hw_params = tfa9879_hw_params, + .digital_mute = tfa9879_digital_mute, + .set_fmt = tfa9879_set_fmt, +}; + +#define TFA9879_RATES SNDRV_PCM_RATE_8000_96000 + +#define TFA9879_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver tfa9879_dai = { + .name = "tfa9879-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TFA9879_RATES, + .formats = TFA9879_FORMATS, }, + .ops = &tfa9879_dai_ops, +}; + +static int tfa9879_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tfa9879_priv *tfa9879; + int i; + + tfa9879 = devm_kzalloc(&i2c->dev, sizeof(*tfa9879), GFP_KERNEL); + if (IS_ERR(tfa9879)) + return PTR_ERR(tfa9879); + + i2c_set_clientdata(i2c, tfa9879); + + tfa9879->regmap = devm_regmap_init_i2c(i2c, &tfa9879_regmap); + if (IS_ERR(tfa9879->regmap)) + return PTR_ERR(tfa9879->regmap); + + /* Ensure the device is in reset state */ + for (i = 0; i < ARRAY_SIZE(tfa9879_regs); i++) + regmap_write(tfa9879->regmap, + tfa9879_regs[i].reg, tfa9879_regs[i].def); + + return snd_soc_register_codec(&i2c->dev, &tfa9879_codec, + &tfa9879_dai, 1); +} + +static int tfa9879_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + + return 0; +} + +static const struct i2c_device_id tfa9879_i2c_id[] = { + { "tfa9879", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tfa9879_i2c_id); + +static struct i2c_driver tfa9879_i2c_driver = { + .driver = { + .name = "tfa9879", + .owner = THIS_MODULE, + }, + .probe = tfa9879_i2c_probe, + .remove = tfa9879_i2c_remove, + .id_table = tfa9879_i2c_id, +}; + +module_i2c_driver(tfa9879_i2c_driver); + +MODULE_DESCRIPTION("ASoC NXP Semiconductors TFA9879 driver"); +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tfa9879.h b/sound/soc/codecs/tfa9879.h new file mode 100644 index 00000000000..3408c90c462 --- /dev/null +++ b/sound/soc/codecs/tfa9879.h @@ -0,0 +1,202 @@ +/* + * tfa9879.h -- driver for NXP Semiconductors TFA9879 + * + * Copyright (C) 2014 Axentia Technologies AB + * Author: Peter Rosin <peda@axentia.se> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _TFA9879_H +#define _TFA9879_H + +#define TFA9879_DEVICE_CONTROL 0x00 +#define TFA9879_SERIAL_INTERFACE_1 0x01 +#define TFA9879_PCM_IOM2_FORMAT_1 0x02 +#define TFA9879_SERIAL_INTERFACE_2 0x03 +#define TFA9879_PCM_IOM2_FORMAT_2 0x04 +#define TFA9879_EQUALIZER_A1 0x05 +#define TFA9879_EQUALIZER_A2 0x06 +#define TFA9879_EQUALIZER_B1 0x07 +#define TFA9879_EQUALIZER_B2 0x08 +#define TFA9879_EQUALIZER_C1 0x09 +#define TFA9879_EQUALIZER_C2 0x0a +#define TFA9879_EQUALIZER_D1 0x0b +#define TFA9879_EQUALIZER_D2 0x0c +#define TFA9879_EQUALIZER_E1 0x0d +#define TFA9879_EQUALIZER_E2 0x0e +#define TFA9879_BYPASS_CONTROL 0x0f +#define TFA9879_DYNAMIC_RANGE_COMPR 0x10 +#define TFA9879_BASS_TREBLE 0x11 +#define TFA9879_HIGH_PASS_FILTER 0x12 +#define TFA9879_VOLUME_CONTROL 0x13 +#define TFA9879_MISC_CONTROL 0x14 +#define TFA9879_MISC_STATUS 0x15 + +/* TFA9879_DEVICE_CONTROL */ +#define TFA9879_INPUT_SEL_MASK 0x0010 +#define TFA9879_INPUT_SEL_SHIFT 4 +#define TFA9879_OPMODE_MASK 0x0008 +#define TFA9879_OPMODE_SHIFT 3 +#define TFA9879_RESET_MASK 0x0002 +#define TFA9879_RESET_SHIFT 1 +#define TFA9879_POWERUP_MASK 0x0001 +#define TFA9879_POWERUP_SHIFT 0 + +/* TFA9879_SERIAL_INTERFACE */ +#define TFA9879_MONO_SEL_MASK 0x0c00 +#define TFA9879_MONO_SEL_SHIFT 10 +#define TFA9879_MONO_SEL_LEFT 0 +#define TFA9879_MONO_SEL_RIGHT 1 +#define TFA9879_MONO_SEL_BOTH 2 +#define TFA9879_I2S_FS_MASK 0x03c0 +#define TFA9879_I2S_FS_SHIFT 6 +#define TFA9879_I2S_FS_8000 0 +#define TFA9879_I2S_FS_11025 1 +#define TFA9879_I2S_FS_12000 2 +#define TFA9879_I2S_FS_16000 3 +#define TFA9879_I2S_FS_22050 4 +#define TFA9879_I2S_FS_24000 5 +#define TFA9879_I2S_FS_32000 6 +#define TFA9879_I2S_FS_44100 7 +#define TFA9879_I2S_FS_48000 8 +#define TFA9879_I2S_FS_64000 9 +#define TFA9879_I2S_FS_88200 10 +#define TFA9879_I2S_FS_96000 11 +#define TFA9879_I2S_SET_MASK 0x0038 +#define TFA9879_I2S_SET_SHIFT 3 +#define TFA9879_I2S_SET_MSB_J_24 2 +#define TFA9879_I2S_SET_I2S_24 3 +#define TFA9879_I2S_SET_LSB_J_16 4 +#define TFA9879_I2S_SET_LSB_J_18 5 +#define TFA9879_I2S_SET_LSB_J_20 6 +#define TFA9879_I2S_SET_LSB_J_24 7 +#define TFA9879_SCK_POL_MASK 0x0004 +#define TFA9879_SCK_POL_SHIFT 2 +#define TFA9879_SCK_POL_NORMAL 0 +#define TFA9879_SCK_POL_INVERSE 1 +#define TFA9879_I_MODE_MASK 0x0003 +#define TFA9879_I_MODE_SHIFT 0 +#define TFA9879_I_MODE_I2S 0 +#define TFA9879_I_MODE_PCM_IOM2_SHORT 1 +#define TFA9879_I_MODE_PCM_IOM2_LONG 2 + +/* TFA9879_PCM_IOM2_FORMAT */ +#define TFA9879_PCM_FS_MASK 0x0800 +#define TFA9879_PCM_FS_SHIFT 11 +#define TFA9879_A_LAW_MASK 0x0400 +#define TFA9879_A_LAW_SHIFT 10 +#define TFA9879_PCM_COMP_MASK 0x0200 +#define TFA9879_PCM_COMP_SHIFT 9 +#define TFA9879_PCM_DL_MASK 0x0100 +#define TFA9879_PCM_DL_SHIFT 8 +#define TFA9879_D1_SLOT_MASK 0x00f0 +#define TFA9879_D1_SLOT_SHIFT 4 +#define TFA9879_D2_SLOT_MASK 0x000f +#define TFA9879_D2_SLOT_SHIFT 0 + +/* TFA9879_EQUALIZER_X1 */ +#define TFA9879_T1_MASK 0x8000 +#define TFA9879_T1_SHIFT 15 +#define TFA9879_K1M_MASK 0x7ff0 +#define TFA9879_K1M_SHIFT 4 +#define TFA9879_K1E_MASK 0x000f +#define TFA9879_K1E_SHIFT 0 + +/* TFA9879_EQUALIZER_X2 */ +#define TFA9879_T2_MASK 0x8000 +#define TFA9879_T2_SHIFT 15 +#define TFA9879_K2M_MASK 0x7800 +#define TFA9879_K2M_SHIFT 11 +#define TFA9879_K2E_MASK 0x0700 +#define TFA9879_K2E_SHIFT 8 +#define TFA9879_K0_MASK 0x00fe +#define TFA9879_K0_SHIFT 1 +#define TFA9879_S_MASK 0x0001 +#define TFA9879_S_SHIFT 0 + +/* TFA9879_BYPASS_CONTROL */ +#define TFA9879_L_OCP_MASK 0x00c0 +#define TFA9879_L_OCP_SHIFT 6 +#define TFA9879_L_OTP_MASK 0x0030 +#define TFA9879_L_OTP_SHIFT 4 +#define TFA9879_CLIPCTRL_MASK 0x0008 +#define TFA9879_CLIPCTRL_SHIFT 3 +#define TFA9879_HPF_BP_MASK 0x0004 +#define TFA9879_HPF_BP_SHIFT 2 +#define TFA9879_DRC_BP_MASK 0x0002 +#define TFA9879_DRC_BP_SHIFT 1 +#define TFA9879_EQ_BP_MASK 0x0001 +#define TFA9879_EQ_BP_SHIFT 0 + +/* TFA9879_DYNAMIC_RANGE_COMPR */ +#define TFA9879_AT_LVL_MASK 0xf000 +#define TFA9879_AT_LVL_SHIFT 12 +#define TFA9879_AT_RATE_MASK 0x0f00 +#define TFA9879_AT_RATE_SHIFT 8 +#define TFA9879_RL_LVL_MASK 0x00f0 +#define TFA9879_RL_LVL_SHIFT 4 +#define TFA9879_RL_RATE_MASK 0x000f +#define TFA9879_RL_RATE_SHIFT 0 + +/* TFA9879_BASS_TREBLE */ +#define TFA9879_G_TRBLE_MASK 0x3e00 +#define TFA9879_G_TRBLE_SHIFT 9 +#define TFA9879_F_TRBLE_MASK 0x0180 +#define TFA9879_F_TRBLE_SHIFT 7 +#define TFA9879_G_BASS_MASK 0x007c +#define TFA9879_G_BASS_SHIFT 2 +#define TFA9879_F_BASS_MASK 0x0003 +#define TFA9879_F_BASS_SHIFT 0 + +/* TFA9879_HIGH_PASS_FILTER */ +#define TFA9879_HP_CTRL_MASK 0x00ff +#define TFA9879_HP_CTRL_SHIFT 0 + +/* TFA9879_VOLUME_CONTROL */ +#define TFA9879_ZR_CRSS_MASK 0x1000 +#define TFA9879_ZR_CRSS_SHIFT 12 +#define TFA9879_VOL_MASK 0x00ff +#define TFA9879_VOL_SHIFT 0 + +/* TFA9879_MISC_CONTROL */ +#define TFA9879_DE_PHAS_MASK 0x0c00 +#define TFA9879_DE_PHAS_SHIFT 10 +#define TFA9879_H_MUTE_MASK 0x0200 +#define TFA9879_H_MUTE_SHIFT 9 +#define TFA9879_S_MUTE_MASK 0x0100 +#define TFA9879_S_MUTE_SHIFT 8 +#define TFA9879_P_LIM_MASK 0x00ff +#define TFA9879_P_LIM_SHIFT 0 + +/* TFA9879_MISC_STATUS */ +#define TFA9879_PS_MASK 0x4000 +#define TFA9879_PS_SHIFT 14 +#define TFA9879_PORA_MASK 0x2000 +#define TFA9879_PORA_SHIFT 13 +#define TFA9879_AMP_MASK 0x0600 +#define TFA9879_AMP_SHIFT 9 +#define TFA9879_IBP_2_MASK 0x0100 +#define TFA9879_IBP_2_SHIFT 8 +#define TFA9879_OFP_2_MASK 0x0080 +#define TFA9879_OFP_2_SHIFT 7 +#define TFA9879_UFP_2_MASK 0x0040 +#define TFA9879_UFP_2_SHIFT 6 +#define TFA9879_IBP_1_MASK 0x0020 +#define TFA9879_IBP_1_SHIFT 5 +#define TFA9879_OFP_1_MASK 0x0010 +#define TFA9879_OFP_1_SHIFT 4 +#define TFA9879_UFP_1_MASK 0x0008 +#define TFA9879_UFP_1_SHIFT 3 +#define TFA9879_OCPOKA_MASK 0x0004 +#define TFA9879_OCPOKA_SHIFT 2 +#define TFA9879_OCPOKB_MASK 0x0002 +#define TFA9879_OCPOKB_SHIFT 1 +#define TFA9879_OTPOK_MASK 0x0001 +#define TFA9879_OTPOK_SHIFT 0 + +#endif diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index d67167920c2..cc17e7e5126 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -540,19 +540,11 @@ static struct snd_soc_dai_driver tlv320aic23_dai = { .ops = &tlv320aic23_dai_ops, }; -static int tlv320aic23_suspend(struct snd_soc_codec *codec) -{ - tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static int tlv320aic23_resume(struct snd_soc_codec *codec) { struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); regcache_mark_dirty(aic23->regmap); regcache_sync(aic23->regmap); - tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } @@ -562,9 +554,6 @@ static int tlv320aic23_codec_probe(struct snd_soc_codec *codec) /* Reset codec */ snd_soc_write(codec, TLV320AIC23_RESET, 0); - /* power on device */ - tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - snd_soc_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); /* Unmute input */ @@ -589,18 +578,12 @@ static int tlv320aic23_codec_probe(struct snd_soc_codec *codec) return 0; } -static int tlv320aic23_remove(struct snd_soc_codec *codec) -{ - tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_tlv320aic23 = { .probe = tlv320aic23_codec_probe, - .remove = tlv320aic23_remove, - .suspend = tlv320aic23_suspend, .resume = tlv320aic23_resume, .set_bias_level = tlv320aic23_set_bias_level, + .suspend_bias_off = true, + .controls = tlv320aic23_snd_controls, .num_controls = ARRAY_SIZE(tlv320aic23_snd_controls), .dapm_widgets = tlv320aic23_dapm_widgets, diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 145fe5b253d..dc3223d6eca 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -911,12 +911,13 @@ static int aic31xx_set_dai_sysclk(struct snd_soc_dai *codec_dai, } aic31xx->p_div = i; - for (i = 0; aic31xx_divs[i].mclk_p != freq/aic31xx->p_div; i++) { - if (i == ARRAY_SIZE(aic31xx_divs)) { - dev_err(aic31xx->dev, "%s: Unsupported frequency %d\n", - __func__, freq); - return -EINVAL; - } + for (i = 0; i < ARRAY_SIZE(aic31xx_divs) && + aic31xx_divs[i].mclk_p != freq/aic31xx->p_div; i++) + ; + if (i == ARRAY_SIZE(aic31xx_divs)) { + dev_err(aic31xx->dev, "%s: Unsupported frequency %d\n", + __func__, freq); + return -EINVAL; } /* set clock on MCLK, BCLK, or GPIO1 as PLL input */ @@ -1056,18 +1057,6 @@ static int aic31xx_set_bias_level(struct snd_soc_codec *codec, return 0; } -static int aic31xx_suspend(struct snd_soc_codec *codec) -{ - aic31xx_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int aic31xx_resume(struct snd_soc_codec *codec) -{ - aic31xx_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int aic31xx_codec_probe(struct snd_soc_codec *codec) { int ret = 0; @@ -1110,8 +1099,6 @@ static int aic31xx_codec_remove(struct snd_soc_codec *codec) { struct aic31xx_priv *aic31xx = snd_soc_codec_get_drvdata(codec); int i; - /* power down chip */ - aic31xx_set_bias_level(codec, SND_SOC_BIAS_OFF); for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) regulator_unregister_notifier(aic31xx->supplies[i].consumer, @@ -1123,9 +1110,9 @@ static int aic31xx_codec_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_driver_aic31xx = { .probe = aic31xx_codec_probe, .remove = aic31xx_codec_remove, - .suspend = aic31xx_suspend, - .resume = aic31xx_resume, .set_bias_level = aic31xx_set_bias_level, + .suspend_bias_off = true, + .controls = aic31xx_snd_controls, .num_controls = ARRAY_SIZE(aic31xx_snd_controls), .dapm_widgets = aic31xx_dapm_widgets, diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 6ea662db241..015467ed606 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -597,18 +597,6 @@ static struct snd_soc_dai_driver aic32x4_dai = { .symmetric_rates = 1, }; -static int aic32x4_suspend(struct snd_soc_codec *codec) -{ - aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int aic32x4_resume(struct snd_soc_codec *codec) -{ - aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int aic32x4_probe(struct snd_soc_codec *codec) { struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec); @@ -654,8 +642,6 @@ static int aic32x4_probe(struct snd_soc_codec *codec) snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_CM1R_10K); - aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* * Workaround: for an unknown reason, the ADC needs to be powered up * and down for the first capture to work properly. It seems related to @@ -669,18 +655,10 @@ static int aic32x4_probe(struct snd_soc_codec *codec) return 0; } -static int aic32x4_remove(struct snd_soc_codec *codec) -{ - aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = { .probe = aic32x4_probe, - .remove = aic32x4_remove, - .suspend = aic32x4_suspend, - .resume = aic32x4_resume, .set_bias_level = aic32x4_set_bias_level, + .suspend_bias_off = true, .controls = aic32x4_snd_controls, .num_controls = ARRAY_SIZE(aic32x4_snd_controls), diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index f7c2a575a89..b7ebce054b4 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -78,6 +78,8 @@ struct aic3x_priv { struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES]; struct aic3x_setup_data *setup; unsigned int sysclk; + unsigned int dai_fmt; + unsigned int tdm_delay; struct list_head list; int master; int gpio_reset; @@ -214,61 +216,78 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, 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[] = - { "differential of HPLOUT", "constant VCM", "single-ended" }; -static const char *aic3x_right_hpcom_mux[] = - { "differential of HPROUT", "constant VCM", "single-ended", - "differential of HPLCOM", "external feedback" }; -static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; -static const char *aic3x_adc_hpf[] = - { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; - -#define LDAC_ENUM 0 -#define RDAC_ENUM 1 -#define LHPCOM_ENUM 2 -#define RHPCOM_ENUM 3 -#define LINE1L_2_L_ENUM 4 -#define LINE1L_2_R_ENUM 5 -#define LINE1R_2_L_ENUM 6 -#define LINE1R_2_R_ENUM 7 -#define LINE2L_ENUM 8 -#define LINE2R_ENUM 9 -#define ADC_HPF_ENUM 10 - -static const struct soc_enum aic3x_enum[] = { - SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), - SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux), - SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux), - SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux), - SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_SINGLE(LINE1L_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_SINGLE(LINE1R_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), - SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf), -}; - -static const char *aic3x_agc_level[] = - { "-5.5dB", "-8dB", "-10dB", "-12dB", "-14dB", "-17dB", "-20dB", "-24dB" }; -static const struct soc_enum aic3x_agc_level_enum[] = { - SOC_ENUM_SINGLE(LAGC_CTRL_A, 4, 8, aic3x_agc_level), - SOC_ENUM_SINGLE(RAGC_CTRL_A, 4, 8, aic3x_agc_level), -}; - -static const char *aic3x_agc_attack[] = { "8ms", "11ms", "16ms", "20ms" }; -static const struct soc_enum aic3x_agc_attack_enum[] = { - SOC_ENUM_SINGLE(LAGC_CTRL_A, 2, 4, aic3x_agc_attack), - SOC_ENUM_SINGLE(RAGC_CTRL_A, 2, 4, aic3x_agc_attack), -}; - -static const char *aic3x_agc_decay[] = { "100ms", "200ms", "400ms", "500ms" }; -static const struct soc_enum aic3x_agc_decay_enum[] = { - SOC_ENUM_SINGLE(LAGC_CTRL_A, 0, 4, aic3x_agc_decay), - SOC_ENUM_SINGLE(RAGC_CTRL_A, 0, 4, aic3x_agc_decay), -}; +static const char * const aic3x_left_dac_mux[] = { + "DAC_L1", "DAC_L3", "DAC_L2" }; +static SOC_ENUM_SINGLE_DECL(aic3x_left_dac_enum, DAC_LINE_MUX, 6, + aic3x_left_dac_mux); + +static const char * const aic3x_right_dac_mux[] = { + "DAC_R1", "DAC_R3", "DAC_R2" }; +static SOC_ENUM_SINGLE_DECL(aic3x_right_dac_enum, DAC_LINE_MUX, 4, + aic3x_right_dac_mux); + +static const char * const aic3x_left_hpcom_mux[] = { + "differential of HPLOUT", "constant VCM", "single-ended" }; +static SOC_ENUM_SINGLE_DECL(aic3x_left_hpcom_enum, HPLCOM_CFG, 4, + aic3x_left_hpcom_mux); + +static const char * const aic3x_right_hpcom_mux[] = { + "differential of HPROUT", "constant VCM", "single-ended", + "differential of HPLCOM", "external feedback" }; +static SOC_ENUM_SINGLE_DECL(aic3x_right_hpcom_enum, HPRCOM_CFG, 3, + aic3x_right_hpcom_mux); + +static const char * const aic3x_linein_mode_mux[] = { + "single-ended", "differential" }; +static SOC_ENUM_SINGLE_DECL(aic3x_line1l_2_l_enum, LINE1L_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1l_2_r_enum, LINE1L_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1r_2_l_enum, LINE1R_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1r_2_r_enum, LINE1R_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line2l_2_ldac_enum, LINE2L_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line2r_2_rdac_enum, LINE2R_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); + +static const char * const aic3x_adc_hpf[] = { + "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; +static SOC_ENUM_DOUBLE_DECL(aic3x_adc_hpf_enum, AIC3X_CODEC_DFILT_CTRL, 6, 4, + aic3x_adc_hpf); + +static const char * const aic3x_agc_level[] = { + "-5.5dB", "-8dB", "-10dB", "-12dB", + "-14dB", "-17dB", "-20dB", "-24dB" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_level_enum, LAGC_CTRL_A, 4, + aic3x_agc_level); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_level_enum, RAGC_CTRL_A, 4, + aic3x_agc_level); + +static const char * const aic3x_agc_attack[] = { + "8ms", "11ms", "16ms", "20ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_attack_enum, LAGC_CTRL_A, 2, + aic3x_agc_attack); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_attack_enum, RAGC_CTRL_A, 2, + aic3x_agc_attack); + +static const char * const aic3x_agc_decay[] = { + "100ms", "200ms", "400ms", "500ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_decay_enum, LAGC_CTRL_A, 0, + aic3x_agc_decay); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_decay_enum, RAGC_CTRL_A, 0, + aic3x_agc_decay); + +static const char * const aic3x_poweron_time[] = { + "0us", "10us", "100us", "1ms", "10ms", "50ms", + "100ms", "200ms", "400ms", "800ms", "2s", "4s" }; +static SOC_ENUM_SINGLE_DECL(aic3x_poweron_time_enum, HPOUT_POP_REDUCTION, 4, + aic3x_poweron_time); + +static const char * const aic3x_rampup_step[] = { "0ms", "1ms", "2ms", "4ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_rampup_step_enum, HPOUT_POP_REDUCTION, 2, + aic3x_rampup_step); /* * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps @@ -383,12 +402,12 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { * adjust PGA to max value when ADC is on and will never go back. */ SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0), - SOC_ENUM("Left AGC Target level", aic3x_agc_level_enum[0]), - SOC_ENUM("Right AGC Target level", aic3x_agc_level_enum[1]), - SOC_ENUM("Left AGC Attack time", aic3x_agc_attack_enum[0]), - SOC_ENUM("Right AGC Attack time", aic3x_agc_attack_enum[1]), - SOC_ENUM("Left AGC Decay time", aic3x_agc_decay_enum[0]), - SOC_ENUM("Right AGC Decay time", aic3x_agc_decay_enum[1]), + SOC_ENUM("Left AGC Target level", aic3x_lagc_level_enum), + SOC_ENUM("Right AGC Target level", aic3x_ragc_level_enum), + SOC_ENUM("Left AGC Attack time", aic3x_lagc_attack_enum), + SOC_ENUM("Right AGC Attack time", aic3x_ragc_attack_enum), + SOC_ENUM("Left AGC Decay time", aic3x_lagc_decay_enum), + SOC_ENUM("Right AGC Decay time", aic3x_ragc_decay_enum), /* De-emphasis */ SOC_DOUBLE("De-emphasis Switch", AIC3X_CODEC_DFILT_CTRL, 2, 0, 0x01, 0), @@ -398,7 +417,11 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { 0, 119, 0, adc_tlv), SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), - SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), + SOC_ENUM("ADC HPF Cut-off", aic3x_adc_hpf_enum), + + /* Pop reduction */ + SOC_ENUM("Output Driver Power-On time", aic3x_poweron_time_enum), + SOC_ENUM("Output Driver Ramp-up step", aic3x_rampup_step_enum), }; static const struct snd_kcontrol_new aic3x_mono_controls[] = { @@ -425,19 +448,19 @@ static const struct snd_kcontrol_new aic3x_classd_amp_gain_ctrl = /* Left DAC Mux */ static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_left_dac_enum); /* Right DAC Mux */ static const struct snd_kcontrol_new aic3x_right_dac_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_right_dac_enum); /* Left HPCOM Mux */ static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_left_hpcom_enum); /* Right HPCOM Mux */ static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_right_hpcom_enum); /* Left Line Mixer */ static const struct snd_kcontrol_new aic3x_left_line_mixer_controls[] = { @@ -529,23 +552,23 @@ static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = { /* Left Line1 Mux */ static const struct snd_kcontrol_new aic3x_left_line1l_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_L_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line1l_2_l_enum); static const struct snd_kcontrol_new aic3x_right_line1l_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_R_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line1l_2_r_enum); /* Right Line1 Mux */ static const struct snd_kcontrol_new aic3x_right_line1r_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_R_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line1r_2_r_enum); static const struct snd_kcontrol_new aic3x_left_line1r_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_L_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line1r_2_l_enum); /* Left Line2 Mux */ static const struct snd_kcontrol_new aic3x_left_line2_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line2l_2_ldac_enum); /* Right Line2 Mux */ static const struct snd_kcontrol_new aic3x_right_line2_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]); +SOC_DAPM_ENUM("Route", aic3x_line2r_2_rdac_enum); static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { /* Left DAC to Left Outputs */ @@ -1009,6 +1032,25 @@ found: return 0; } +static int aic3x_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + int delay = 0; + + /* TDM slot selection only valid in DSP_A/_B mode */ + if (aic3x->dai_fmt == SND_SOC_DAIFMT_DSP_A) + delay += (aic3x->tdm_delay + 1); + else if (aic3x->dai_fmt == SND_SOC_DAIFMT_DSP_B) + delay += aic3x->tdm_delay; + + /* Configure data delay */ + snd_soc_write(codec, AIC3X_ASD_INTF_CTRLC, aic3x->tdm_delay); + + return 0; +} + static int aic3x_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; @@ -1048,7 +1090,6 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); u8 iface_areg, iface_breg; - int delay = 0; iface_areg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f; iface_breg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f; @@ -1076,7 +1117,6 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): break; case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): - delay = 1; case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): iface_breg |= (0x01 << 6); break; @@ -1090,10 +1130,45 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } + aic3x->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + /* set iface */ snd_soc_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg); snd_soc_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg); - snd_soc_write(codec, AIC3X_ASD_INTF_CTRLC, delay); + + return 0; +} + +static int aic3x_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + unsigned int lsb; + + if (tx_mask != rx_mask) { + dev_err(codec->dev, "tx and rx masks must be symmetric\n"); + return -EINVAL; + } + + if (unlikely(!tx_mask)) { + dev_err(codec->dev, "tx and rx masks need to be non 0\n"); + return -EINVAL; + } + + /* TDM based on DSP mode requires slots to be adjacent */ + lsb = __ffs(tx_mask); + if ((lsb + 1) != __fls(tx_mask)) { + dev_err(codec->dev, "Invalid mask, slots must be adjacent\n"); + return -EINVAL; + } + + aic3x->tdm_delay = lsb * slot_width; + + /* DOUT in high-impedance on inactive bit clocks */ + snd_soc_update_bits(codec, AIC3X_ASD_INTF_CTRLA, + DOUT_TRISTATE, DOUT_TRISTATE); return 0; } @@ -1212,9 +1287,11 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, static const struct snd_soc_dai_ops aic3x_dai_ops = { .hw_params = aic3x_hw_params, + .prepare = aic3x_prepare, .digital_mute = aic3x_mute, .set_sysclk = aic3x_set_dai_sysclk, .set_fmt = aic3x_set_dai_fmt, + .set_tdm_slot = aic3x_set_dai_tdm_slot, }; static struct snd_soc_dai_driver aic3x_dai = { @@ -1414,7 +1491,6 @@ static int aic3x_remove(struct snd_soc_codec *codec) struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); int i; - aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); list_del(&aic3x->list); for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) regulator_unregister_notifier(aic3x->supplies[i].consumer, diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index e521ac3ddde..89fa692df20 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -169,6 +169,7 @@ /* Audio serial data interface control register A bits */ #define BIT_CLK_MASTER 0x80 #define WORD_CLK_MASTER 0x40 +#define DOUT_TRISTATE 0x20 /* Codec Datapath setup register 7 */ #define FSREF_44100 (1 << 7) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index e21ed934bdb..0fe2ced5b09 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1436,8 +1436,6 @@ static int dac33_soc_remove(struct snd_soc_codec *codec) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - dac33_set_bias_level(codec, SND_SOC_BIAS_OFF); - if (dac33->irq >= 0) { free_irq(dac33->irq, dac33->codec); destroy_workqueue(dac33->dac33_wq); diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c new file mode 100644 index 00000000000..1d1205702d2 --- /dev/null +++ b/sound/soc/codecs/ts3a227e.c @@ -0,0 +1,314 @@ +/* + * TS3A227E Autonomous Audio Accessory Detection and Configuration Switch + * + * Copyright (C) 2014 Google, 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. + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/soc.h> + +struct ts3a227e { + struct regmap *regmap; + struct snd_soc_jack *jack; + bool plugged; + bool mic_present; + unsigned int buttons_held; +}; + +/* Button values to be reported on the jack */ +static const int ts3a227e_buttons[] = { + SND_JACK_BTN_0, + SND_JACK_BTN_1, + SND_JACK_BTN_2, + SND_JACK_BTN_3, +}; + +#define TS3A227E_NUM_BUTTONS 4 +#define TS3A227E_JACK_MASK (SND_JACK_HEADPHONE | \ + SND_JACK_MICROPHONE | \ + SND_JACK_BTN_0 | \ + SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | \ + SND_JACK_BTN_3) + +/* TS3A227E registers */ +#define TS3A227E_REG_DEVICE_ID 0x00 +#define TS3A227E_REG_INTERRUPT 0x01 +#define TS3A227E_REG_KP_INTERRUPT 0x02 +#define TS3A227E_REG_INTERRUPT_DISABLE 0x03 +#define TS3A227E_REG_SETTING_1 0x04 +#define TS3A227E_REG_SETTING_2 0x05 +#define TS3A227E_REG_SETTING_3 0x06 +#define TS3A227E_REG_SWITCH_CONTROL_1 0x07 +#define TS3A227E_REG_SWITCH_CONTROL_2 0x08 +#define TS3A227E_REG_SWITCH_STATUS_1 0x09 +#define TS3A227E_REG_SWITCH_STATUS_2 0x0a +#define TS3A227E_REG_ACCESSORY_STATUS 0x0b +#define TS3A227E_REG_ADC_OUTPUT 0x0c +#define TS3A227E_REG_KP_THRESHOLD_1 0x0d +#define TS3A227E_REG_KP_THRESHOLD_2 0x0e +#define TS3A227E_REG_KP_THRESHOLD_3 0x0f + +/* TS3A227E_REG_INTERRUPT 0x01 */ +#define INS_REM_EVENT 0x01 +#define DETECTION_COMPLETE_EVENT 0x02 + +/* TS3A227E_REG_KP_INTERRUPT 0x02 */ +#define PRESS_MASK(idx) (0x01 << (2 * (idx))) +#define RELEASE_MASK(idx) (0x02 << (2 * (idx))) + +/* TS3A227E_REG_INTERRUPT_DISABLE 0x03 */ +#define INS_REM_INT_DISABLE 0x01 +#define DETECTION_COMPLETE_INT_DISABLE 0x02 +#define ADC_COMPLETE_INT_DISABLE 0x04 +#define INTB_DISABLE 0x08 + +/* TS3A227E_REG_SETTING_2 0x05 */ +#define KP_ENABLE 0x04 + +/* TS3A227E_REG_ACCESSORY_STATUS 0x0b */ +#define TYPE_3_POLE 0x01 +#define TYPE_4_POLE_OMTP 0x02 +#define TYPE_4_POLE_STANDARD 0x04 +#define JACK_INSERTED 0x08 +#define EITHER_MIC_MASK (TYPE_4_POLE_OMTP | TYPE_4_POLE_STANDARD) + +static const struct reg_default ts3a227e_reg_defaults[] = { + { TS3A227E_REG_DEVICE_ID, 0x10 }, + { TS3A227E_REG_INTERRUPT, 0x00 }, + { TS3A227E_REG_KP_INTERRUPT, 0x00 }, + { TS3A227E_REG_INTERRUPT_DISABLE, 0x08 }, + { TS3A227E_REG_SETTING_1, 0x23 }, + { TS3A227E_REG_SETTING_2, 0x00 }, + { TS3A227E_REG_SETTING_3, 0x0e }, + { TS3A227E_REG_SWITCH_CONTROL_1, 0x00 }, + { TS3A227E_REG_SWITCH_CONTROL_2, 0x00 }, + { TS3A227E_REG_SWITCH_STATUS_1, 0x0c }, + { TS3A227E_REG_SWITCH_STATUS_2, 0x00 }, + { TS3A227E_REG_ACCESSORY_STATUS, 0x00 }, + { TS3A227E_REG_ADC_OUTPUT, 0x00 }, + { TS3A227E_REG_KP_THRESHOLD_1, 0x20 }, + { TS3A227E_REG_KP_THRESHOLD_2, 0x40 }, + { TS3A227E_REG_KP_THRESHOLD_3, 0x68 }, +}; + +static bool ts3a227e_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_DEVICE_ID ... TS3A227E_REG_KP_THRESHOLD_3: + return true; + default: + return false; + } +} + +static bool ts3a227e_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_INTERRUPT_DISABLE ... TS3A227E_REG_SWITCH_CONTROL_2: + case TS3A227E_REG_KP_THRESHOLD_1 ... TS3A227E_REG_KP_THRESHOLD_3: + return true; + default: + return false; + } +} + +static bool ts3a227e_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_INTERRUPT ... TS3A227E_REG_INTERRUPT_DISABLE: + case TS3A227E_REG_SETTING_2: + case TS3A227E_REG_SWITCH_STATUS_1 ... TS3A227E_REG_ADC_OUTPUT: + return true; + default: + return false; + } +} + +static void ts3a227e_jack_report(struct ts3a227e *ts3a227e) +{ + unsigned int i; + int report = 0; + + if (!ts3a227e->jack) + return; + + if (ts3a227e->plugged) + report = SND_JACK_HEADPHONE; + if (ts3a227e->mic_present) + report |= SND_JACK_MICROPHONE; + for (i = 0; i < TS3A227E_NUM_BUTTONS; i++) { + if (ts3a227e->buttons_held & (1 << i)) + report |= ts3a227e_buttons[i]; + } + snd_soc_jack_report(ts3a227e->jack, report, TS3A227E_JACK_MASK); +} + +static void ts3a227e_new_jack_state(struct ts3a227e *ts3a227e, unsigned acc_reg) +{ + bool plugged, mic_present; + + plugged = !!(acc_reg & JACK_INSERTED); + mic_present = plugged && !!(acc_reg & EITHER_MIC_MASK); + + ts3a227e->plugged = plugged; + + if (mic_present != ts3a227e->mic_present) { + ts3a227e->mic_present = mic_present; + ts3a227e->buttons_held = 0; + if (mic_present) { + /* Enable key press detection. */ + regmap_update_bits(ts3a227e->regmap, + TS3A227E_REG_SETTING_2, + KP_ENABLE, KP_ENABLE); + } + } +} + +static irqreturn_t ts3a227e_interrupt(int irq, void *data) +{ + struct ts3a227e *ts3a227e = (struct ts3a227e *)data; + struct regmap *regmap = ts3a227e->regmap; + unsigned int int_reg, kp_int_reg, acc_reg, i; + + /* Check for plug/unplug. */ + regmap_read(regmap, TS3A227E_REG_INTERRUPT, &int_reg); + if (int_reg & (DETECTION_COMPLETE_EVENT | INS_REM_EVENT)) { + regmap_read(regmap, TS3A227E_REG_ACCESSORY_STATUS, &acc_reg); + ts3a227e_new_jack_state(ts3a227e, acc_reg); + } + + /* Report any key events. */ + regmap_read(regmap, TS3A227E_REG_KP_INTERRUPT, &kp_int_reg); + for (i = 0; i < TS3A227E_NUM_BUTTONS; i++) { + if (kp_int_reg & PRESS_MASK(i)) + ts3a227e->buttons_held |= (1 << i); + if (kp_int_reg & RELEASE_MASK(i)) + ts3a227e->buttons_held &= ~(1 << i); + } + + ts3a227e_jack_report(ts3a227e); + + return IRQ_HANDLED; +} + +/** + * ts3a227e_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events 0-3 will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int ts3a227e_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct ts3a227e *ts3a227e = snd_soc_component_get_drvdata(component); + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ts3a227e->jack = jack; + ts3a227e_jack_report(ts3a227e); + + return 0; +} +EXPORT_SYMBOL_GPL(ts3a227e_enable_jack_detect); + +static struct snd_soc_component_driver ts3a227e_soc_driver; + +static const struct regmap_config ts3a227e_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = TS3A227E_REG_KP_THRESHOLD_3, + .readable_reg = ts3a227e_readable_reg, + .writeable_reg = ts3a227e_writeable_reg, + .volatile_reg = ts3a227e_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ts3a227e_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ts3a227e_reg_defaults), +}; + +static int ts3a227e_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ts3a227e *ts3a227e; + struct device *dev = &i2c->dev; + int ret; + + ts3a227e = devm_kzalloc(&i2c->dev, sizeof(*ts3a227e), GFP_KERNEL); + if (ts3a227e == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ts3a227e); + + ts3a227e->regmap = devm_regmap_init_i2c(i2c, &ts3a227e_regmap_config); + if (IS_ERR(ts3a227e->regmap)) + return PTR_ERR(ts3a227e->regmap); + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, ts3a227e_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "TS3A227E", ts3a227e); + if (ret) { + dev_err(dev, "Cannot request irq %d (%d)\n", i2c->irq, ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, &ts3a227e_soc_driver, + NULL, 0); + if (ret) + return ret; + + /* Enable interrupts except for ADC complete. */ + regmap_update_bits(ts3a227e->regmap, TS3A227E_REG_INTERRUPT_DISABLE, + INTB_DISABLE | ADC_COMPLETE_INT_DISABLE, + ADC_COMPLETE_INT_DISABLE); + + return 0; +} + +static const struct i2c_device_id ts3a227e_i2c_ids[] = { + { "ts3a227e", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ts3a227e_i2c_ids); + +static const struct of_device_id ts3a227e_of_match[] = { + { .compatible = "ti,ts3a227e", }, + { } +}; +MODULE_DEVICE_TABLE(of, ts3a227e_of_match); + +static struct i2c_driver ts3a227e_driver = { + .driver = { + .name = "ts3a227e", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ts3a227e_of_match), + }, + .probe = ts3a227e_i2c_probe, + .id_table = ts3a227e_i2c_ids, +}; +module_i2c_driver(ts3a227e_driver); + +MODULE_DESCRIPTION("ASoC ts3a227e driver"); +MODULE_AUTHOR("Dylan Reid <dgreid@chromium.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ts3a227e.h b/sound/soc/codecs/ts3a227e.h new file mode 100644 index 00000000000..e2acf9c5beb --- /dev/null +++ b/sound/soc/codecs/ts3a227e.h @@ -0,0 +1,17 @@ +/* + * TS3A227E Autonous Audio Accessory Detection and Configureation Switch + * + * Copyright (C) 2014 Google, 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. + */ + +#ifndef _TS3A227E_H +#define _TS3A227E_H + +int ts3a227e_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + +#endif diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b6b0cb39959..27f3b21effb 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2177,8 +2177,6 @@ static int twl4030_soc_remove(struct snd_soc_codec *codec) struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); struct twl4030_codec_data *pdata = twl4030->pdata; - twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); - if (pdata && pdata->hs_extmute && gpio_is_valid(pdata->hs_extmute_gpio)) gpio_free(pdata->hs_extmute_gpio); diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 0f6067f04e2..5ff2b1e4638 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1095,25 +1095,6 @@ static struct snd_soc_dai_driver twl6040_dai[] = { }, }; -#ifdef CONFIG_PM -static int twl6040_suspend(struct snd_soc_codec *codec) -{ - twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int twl6040_resume(struct snd_soc_codec *codec) -{ - twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} -#else -#define twl6040_suspend NULL -#define twl6040_resume NULL -#endif - static int twl6040_probe(struct snd_soc_codec *codec) { struct twl6040_data *priv; @@ -1160,7 +1141,6 @@ static int twl6040_remove(struct snd_soc_codec *codec) struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); free_irq(priv->plug_irq, codec); - twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1168,11 +1148,10 @@ static int twl6040_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_twl6040 = { .probe = twl6040_probe, .remove = twl6040_remove, - .suspend = twl6040_suspend, - .resume = twl6040_resume, .read = twl6040_read, .write = twl6040_write, .set_bias_level = twl6040_set_bias_level, + .suspend_bias_off = true, .ignore_pmdown_time = true, .controls = twl6040_snd_controls, diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 32b2f78aa62..4056260a502 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -518,11 +518,6 @@ static int uda134x_soc_probe(struct snd_soc_codec *codec) uda134x_reset(codec); - if (pd->is_powered_on_standby) - uda134x_set_bias_level(codec, SND_SOC_BIAS_ON); - else - uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (pd->model == UDA134X_UDA1341) { widgets = uda1341_dapm_widgets; num_widgets = ARRAY_SIZE(uda1341_dapm_widgets); @@ -574,44 +569,21 @@ static int uda134x_soc_remove(struct snd_soc_codec *codec) { struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); - uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); - kfree(uda134x); return 0; } -#if defined(CONFIG_PM) -static int uda134x_soc_suspend(struct snd_soc_codec *codec) -{ - uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int uda134x_soc_resume(struct snd_soc_codec *codec) -{ - uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - uda134x_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; -} -#else -#define uda134x_soc_suspend NULL -#define uda134x_soc_resume NULL -#endif /* CONFIG_PM */ - static struct snd_soc_codec_driver soc_codec_dev_uda134x = { .probe = uda134x_soc_probe, .remove = uda134x_soc_remove, - .suspend = uda134x_soc_suspend, - .resume = uda134x_soc_resume, .reg_cache_size = sizeof(uda134x_reg), .reg_word_size = sizeof(u8), .reg_cache_default = uda134x_reg, .reg_cache_step = 1, .read = uda134x_read_reg_cache, - .write = uda134x_write, .set_bias_level = uda134x_set_bias_level, + .suspend_bias_off = true, + .dapm_widgets = uda134x_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(uda134x_dapm_widgets), .dapm_routes = uda134x_dapm_routes, diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index e62e70781ec..dc7778b6dd7 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -693,18 +693,6 @@ static struct snd_soc_dai_driver uda1380_dai[] = { }, }; -static int uda1380_suspend(struct snd_soc_codec *codec) -{ - uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int uda1380_resume(struct snd_soc_codec *codec) -{ - uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int uda1380_probe(struct snd_soc_codec *codec) { struct uda1380_platform_data *pdata =codec->dev->platform_data; @@ -739,8 +727,6 @@ static int uda1380_probe(struct snd_soc_codec *codec) INIT_WORK(&uda1380->work, uda1380_flush_work); - /* power on device */ - uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set clock input */ switch (pdata->dac_clk) { case UDA1380_DAC_CLK_SYSCLK: @@ -766,8 +752,6 @@ static int uda1380_remove(struct snd_soc_codec *codec) { struct uda1380_platform_data *pdata =codec->dev->platform_data; - uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); - gpio_free(pdata->gpio_reset); gpio_free(pdata->gpio_power); @@ -777,11 +761,11 @@ static int uda1380_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_uda1380 = { .probe = uda1380_probe, .remove = uda1380_remove, - .suspend = uda1380_suspend, - .resume = uda1380_resume, .read = uda1380_read_reg_cache, .write = uda1380_write, .set_bias_level = uda1380_set_bias_level, + .suspend_bias_off = true, + .reg_cache_size = ARRAY_SIZE(uda1380_reg), .reg_word_size = sizeof(u16), .reg_cache_default = uda1380_reg, diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c index f3d4e88d0b7..00aea4100bb 100644 --- a/sound/soc/codecs/wl1273.c +++ b/sound/soc/codecs/wl1273.c @@ -452,7 +452,6 @@ static int wl1273_probe(struct snd_soc_codec *codec) { struct wl1273_core **core = codec->dev->platform_data; struct wl1273_priv *wl1273; - int r; dev_dbg(codec->dev, "%s.\n", __func__); @@ -470,12 +469,7 @@ static int wl1273_probe(struct snd_soc_codec *codec) snd_soc_codec_set_drvdata(codec, wl1273); - r = snd_soc_add_codec_controls(codec, wl1273_controls, - ARRAY_SIZE(wl1273_controls)); - if (r) - kfree(wl1273); - - return r; + return 0; } static int wl1273_remove(struct snd_soc_codec *codec) @@ -492,6 +486,8 @@ static struct snd_soc_codec_driver soc_codec_dev_wl1273 = { .probe = wl1273_probe, .remove = wl1273_remove, + .controls = wl1273_controls, + .num_controls = ARRAY_SIZE(wl1273_controls), .dapm_widgets = wl1273_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wl1273_dapm_widgets), .dapm_routes = wl1273_dapm_routes, diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index f6023496252..d78fb8dffc8 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -619,10 +619,10 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, struct arizona *arizona = dev_get_drvdata(codec->dev->parent); uint16_t data; - mutex_lock(&codec->mutex); + mutex_lock(&arizona->dac_comp_lock); data = cpu_to_be16(arizona->dac_comp_coeff); memcpy(ucontrol->value.bytes.data, &data, sizeof(data)); - mutex_unlock(&codec->mutex); + mutex_unlock(&arizona->dac_comp_lock); return 0; } @@ -633,11 +633,11 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct arizona *arizona = dev_get_drvdata(codec->dev->parent); - mutex_lock(&codec->mutex); + mutex_lock(&arizona->dac_comp_lock); memcpy(&arizona->dac_comp_coeff, ucontrol->value.bytes.data, sizeof(arizona->dac_comp_coeff)); arizona->dac_comp_coeff = be16_to_cpu(arizona->dac_comp_coeff); - mutex_unlock(&codec->mutex); + mutex_unlock(&arizona->dac_comp_lock); return 0; } @@ -648,9 +648,9 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct arizona *arizona = dev_get_drvdata(codec->dev->parent); - mutex_lock(&codec->mutex); + mutex_lock(&arizona->dac_comp_lock); ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; - mutex_unlock(&codec->mutex); + mutex_unlock(&arizona->dac_comp_lock); return 0; } @@ -661,9 +661,9 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); struct arizona *arizona = dev_get_drvdata(codec->dev->parent); - mutex_lock(&codec->mutex); + mutex_lock(&arizona->dac_comp_lock); arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; - mutex_unlock(&codec->mutex); + mutex_unlock(&arizona->dac_comp_lock); return 0; } @@ -1900,6 +1900,8 @@ static int wm5102_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, wm5102); + mutex_init(&arizona->dac_comp_lock); + wm5102->core.arizona = arizona; wm5102->core.num_inputs = 6; diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 628ec774cf2..87f664b9cc7 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1242,19 +1242,6 @@ static int wm8350_set_bias_level(struct snd_soc_codec *codec, return 0; } -static int wm8350_suspend(struct snd_soc_codec *codec) -{ - wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8350_resume(struct snd_soc_codec *codec) -{ - wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - static void wm8350_hp_work(struct wm8350_data *priv, struct wm8350_jack_data *jack, u16 mask) @@ -1565,9 +1552,6 @@ static int wm8350_codec_probe(struct snd_soc_codec *codec) wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD, wm8350_mic_handler, 0, "Microphone detect", priv); - - wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; } @@ -1596,8 +1580,6 @@ static int wm8350_codec_remove(struct snd_soc_codec *codec) * wait for its completion */ flush_delayed_work(&codec->dapm.delayed_work); - wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF); - wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); return 0; @@ -1613,10 +1595,9 @@ static struct regmap *wm8350_get_regmap(struct device *dev) static struct snd_soc_codec_driver soc_codec_dev_wm8350 = { .probe = wm8350_codec_probe, .remove = wm8350_codec_remove, - .suspend = wm8350_suspend, - .resume = wm8350_resume, .get_regmap = wm8350_get_regmap, .set_bias_level = wm8350_set_bias_level, + .suspend_bias_off = true, .controls = wm8350_snd_controls, .num_controls = ARRAY_SIZE(wm8350_snd_controls), diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 72471bef2e9..385894f6e26 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -58,12 +58,10 @@ static struct regulator_bulk_data power[] = { /* codec private data */ struct wm8400_priv { - struct snd_soc_codec *codec; struct wm8400 *wm8400; u16 fake_register; unsigned int sysclk; unsigned int pcmclk; - struct work_struct work; int fll_in, fll_out; }; @@ -1278,30 +1276,6 @@ static struct snd_soc_dai_driver wm8400_dai = { .ops = &wm8400_dai_ops, }; -static int wm8400_suspend(struct snd_soc_codec *codec) -{ - wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int wm8400_resume(struct snd_soc_codec *codec) -{ - wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static void wm8400_probe_deferred(struct work_struct *work) -{ - struct wm8400_priv *priv = container_of(work, struct wm8400_priv, - work); - struct snd_soc_codec *codec = priv->codec; - - /* charge output caps */ - wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); -} - static int wm8400_codec_probe(struct snd_soc_codec *codec) { struct wm8400 *wm8400 = dev_get_platdata(codec->dev); @@ -1316,7 +1290,6 @@ static int wm8400_codec_probe(struct snd_soc_codec *codec) snd_soc_codec_set_drvdata(codec, priv); priv->wm8400 = wm8400; - priv->codec = codec; ret = devm_regulator_bulk_get(wm8400->dev, ARRAY_SIZE(power), &power[0]); @@ -1325,8 +1298,6 @@ static int wm8400_codec_probe(struct snd_soc_codec *codec) return ret; } - INIT_WORK(&priv->work, wm8400_probe_deferred); - wm8400_codec_reset(codec); reg = snd_soc_read(codec, WM8400_POWER_MANAGEMENT_1); @@ -1343,8 +1314,6 @@ static int wm8400_codec_probe(struct snd_soc_codec *codec) snd_soc_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); snd_soc_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); - if (!schedule_work(&priv->work)) - return -EINVAL; return 0; } @@ -1369,10 +1338,9 @@ static struct regmap *wm8400_get_regmap(struct device *dev) static struct snd_soc_codec_driver soc_codec_dev_wm8400 = { .probe = wm8400_codec_probe, .remove = wm8400_codec_remove, - .suspend = wm8400_suspend, - .resume = wm8400_resume, .get_regmap = wm8400_get_regmap, .set_bias_level = wm8400_set_bias_level, + .suspend_bias_off = true, .controls = wm8400_snd_controls, .num_controls = ARRAY_SIZE(wm8400_snd_controls), diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index e11127f9069..8736ad094b2 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -575,41 +575,17 @@ static struct snd_soc_dai_driver wm8510_dai = { .symmetric_rates = 1, }; -static int wm8510_suspend(struct snd_soc_codec *codec) -{ - wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8510_resume(struct snd_soc_codec *codec) -{ - wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8510_probe(struct snd_soc_codec *codec) { wm8510_reset(codec); - /* power on device */ - wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -/* power down chip */ -static int wm8510_remove(struct snd_soc_codec *codec) -{ - wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } static struct snd_soc_codec_driver soc_codec_dev_wm8510 = { .probe = wm8510_probe, - .remove = wm8510_remove, - .suspend = wm8510_suspend, - .resume = wm8510_resume, .set_bias_level = wm8510_set_bias_level, + .suspend_bias_off = true, .controls = wm8510_snd_controls, .num_controls = ARRAY_SIZE(wm8510_snd_controls), diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index ec1f5740dbd..b1cc94f5fc4 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -372,23 +372,6 @@ static struct snd_soc_dai_driver wm8523_dai = { .ops = &wm8523_dai_ops, }; -#ifdef CONFIG_PM -static int wm8523_suspend(struct snd_soc_codec *codec) -{ - wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8523_resume(struct snd_soc_codec *codec) -{ - wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define wm8523_suspend NULL -#define wm8523_resume NULL -#endif - static int wm8523_probe(struct snd_soc_codec *codec) { struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); @@ -402,23 +385,13 @@ static int wm8523_probe(struct snd_soc_codec *codec) WM8523_DACR_VU, WM8523_DACR_VU); snd_soc_update_bits(codec, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC); - wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int wm8523_remove(struct snd_soc_codec *codec) -{ - wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } static struct snd_soc_codec_driver soc_codec_dev_wm8523 = { .probe = wm8523_probe, - .remove = wm8523_remove, - .suspend = wm8523_suspend, - .resume = wm8523_resume, .set_bias_level = wm8523_set_bias_level, + .suspend_bias_off = true, .controls = wm8523_controls, .num_controls = ARRAY_SIZE(wm8523_controls), diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 911605ee25b..0a887c5ec83 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -882,8 +882,6 @@ static int wm8580_probe(struct snd_soc_codec *codec) goto err_regulator_enable; } - wm8580_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; err_regulator_enable: @@ -897,8 +895,6 @@ static int wm8580_remove(struct snd_soc_codec *codec) { struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec); - wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF); - regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); return 0; diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index 32187e739b4..121e46d5377 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -350,19 +350,6 @@ static struct snd_soc_dai_driver wm8711_dai = { .ops = &wm8711_ops, }; -static int wm8711_suspend(struct snd_soc_codec *codec) -{ - snd_soc_write(codec, WM8711_ACTIVE, 0x0); - wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8711_resume(struct snd_soc_codec *codec) -{ - wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8711_probe(struct snd_soc_codec *codec) { int ret; @@ -373,8 +360,6 @@ static int wm8711_probe(struct snd_soc_codec *codec) return ret; } - wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* Latch the update bits */ snd_soc_update_bits(codec, WM8711_LOUT1V, 0x0100, 0x0100); snd_soc_update_bits(codec, WM8711_ROUT1V, 0x0100, 0x0100); @@ -383,19 +368,11 @@ static int wm8711_probe(struct snd_soc_codec *codec) } -/* power down chip */ -static int wm8711_remove(struct snd_soc_codec *codec) -{ - wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8711 = { .probe = wm8711_probe, - .remove = wm8711_remove, - .suspend = wm8711_suspend, - .resume = wm8711_resume, .set_bias_level = wm8711_set_bias_level, + .suspend_bias_off = true, + .controls = wm8711_snd_controls, .num_controls = ARRAY_SIZE(wm8711_snd_controls), .dapm_widgets = wm8711_dapm_widgets, diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 38ff826f589..55c7fb4fc78 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -212,40 +212,10 @@ static struct snd_soc_dai_driver wm8728_dai = { .ops = &wm8728_dai_ops, }; -static int wm8728_suspend(struct snd_soc_codec *codec) -{ - wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int wm8728_resume(struct snd_soc_codec *codec) -{ - wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int wm8728_probe(struct snd_soc_codec *codec) -{ - /* power on device */ - wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -static int wm8728_remove(struct snd_soc_codec *codec) -{ - wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8728 = { - .probe = wm8728_probe, - .remove = wm8728_remove, - .suspend = wm8728_suspend, - .resume = wm8728_resume, .set_bias_level = wm8728_set_bias_level, + .suspend_bias_off = true, + .controls = wm8728_snd_controls, .num_controls = ARRAY_SIZE(wm8728_snd_controls), .dapm_widgets = wm8728_dapm_widgets, diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index eebb3280bfa..b9211b42f6e 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -24,6 +24,7 @@ #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> #include <linux/of_device.h> +#include <linux/mutex.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -50,6 +51,8 @@ struct wm8731_priv { int sysclk_type; int playback_fs; bool deemph; + + struct mutex lock; }; @@ -138,7 +141,7 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, if (deemph > 1) return -EINVAL; - mutex_lock(&codec->mutex); + mutex_lock(&wm8731->lock); if (wm8731->deemph != deemph) { wm8731->deemph = deemph; @@ -146,7 +149,7 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, ret = 1; } - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8731->lock); return ret; } @@ -559,25 +562,6 @@ static struct snd_soc_dai_driver wm8731_dai = { .symmetric_rates = 1, }; -#ifdef CONFIG_PM -static int wm8731_suspend(struct snd_soc_codec *codec) -{ - wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int wm8731_resume(struct snd_soc_codec *codec) -{ - wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} -#else -#define wm8731_suspend NULL -#define wm8731_resume NULL -#endif - static int wm8731_probe(struct snd_soc_codec *codec) { struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); @@ -633,8 +617,6 @@ static int wm8731_remove(struct snd_soc_codec *codec) { struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); - wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); - regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); return 0; @@ -643,9 +625,9 @@ static int wm8731_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_wm8731 = { .probe = wm8731_probe, .remove = wm8731_remove, - .suspend = wm8731_suspend, - .resume = wm8731_resume, .set_bias_level = wm8731_set_bias_level, + .suspend_bias_off = true, + .dapm_widgets = wm8731_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), .dapm_routes = wm8731_intercon, @@ -680,11 +662,12 @@ static int wm8731_spi_probe(struct spi_device *spi) struct wm8731_priv *wm8731; int ret; - wm8731 = devm_kzalloc(&spi->dev, sizeof(struct wm8731_priv), - GFP_KERNEL); + wm8731 = devm_kzalloc(&spi->dev, sizeof(*wm8731), GFP_KERNEL); if (wm8731 == NULL) return -ENOMEM; + mutex_init(&wm8731->lock); + wm8731->regmap = devm_regmap_init_spi(spi, &wm8731_regmap); if (IS_ERR(wm8731->regmap)) { ret = PTR_ERR(wm8731->regmap); diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 744a422ecb0..ada9ac1ba2c 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -277,17 +277,6 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF", NULL, "ADCR" }, }; -static int wm8737_add_widgets(struct snd_soc_codec *codec) -{ - struct snd_soc_dapm_context *dapm = &codec->dapm; - - snd_soc_dapm_new_controls(dapm, wm8737_dapm_widgets, - ARRAY_SIZE(wm8737_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); - - return 0; -} - /* codec mclk clock divider coefficients */ static const struct { u32 mclk; @@ -548,23 +537,6 @@ static struct snd_soc_dai_driver wm8737_dai = { .ops = &wm8737_dai_ops, }; -#ifdef CONFIG_PM -static int wm8737_suspend(struct snd_soc_codec *codec) -{ - wm8737_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8737_resume(struct snd_soc_codec *codec) -{ - wm8737_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define wm8737_suspend NULL -#define wm8737_resume NULL -#endif - static int wm8737_probe(struct snd_soc_codec *codec) { struct wm8737_priv *wm8737 = snd_soc_codec_get_drvdata(codec); @@ -593,10 +565,6 @@ static int wm8737_probe(struct snd_soc_codec *codec) /* Bias level configuration will have done an extra enable */ regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), wm8737->supplies); - snd_soc_add_codec_controls(codec, wm8737_snd_controls, - ARRAY_SIZE(wm8737_snd_controls)); - wm8737_add_widgets(codec); - return 0; err_enable: @@ -605,18 +573,17 @@ err_get: return ret; } -static int wm8737_remove(struct snd_soc_codec *codec) -{ - wm8737_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8737 = { .probe = wm8737_probe, - .remove = wm8737_remove, - .suspend = wm8737_suspend, - .resume = wm8737_resume, .set_bias_level = wm8737_set_bias_level, + .suspend_bias_off = true, + + .controls = wm8737_snd_controls, + .num_controls = ARRAY_SIZE(wm8737_snd_controls), + .dapm_widgets = wm8737_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8737_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), }; static const struct of_device_id wm8737_of_match[] = { diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 67653a2db22..f6847fdd6dd 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -686,18 +686,6 @@ static struct snd_soc_dai_driver wm8750_dai = { .ops = &wm8750_dai_ops, }; -static int wm8750_suspend(struct snd_soc_codec *codec) -{ - wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8750_resume(struct snd_soc_codec *codec) -{ - wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8750_probe(struct snd_soc_codec *codec) { int ret; @@ -708,9 +696,6 @@ static int wm8750_probe(struct snd_soc_codec *codec) return ret; } - /* charge output caps */ - wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* set the update bits */ snd_soc_update_bits(codec, WM8750_LDAC, 0x0100, 0x0100); snd_soc_update_bits(codec, WM8750_RDAC, 0x0100, 0x0100); @@ -724,18 +709,10 @@ static int wm8750_probe(struct snd_soc_codec *codec) return ret; } -static int wm8750_remove(struct snd_soc_codec *codec) -{ - wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8750 = { .probe = wm8750_probe, - .remove = wm8750_remove, - .suspend = wm8750_suspend, - .resume = wm8750_resume, .set_bias_level = wm8750_set_bias_level, + .suspend_bias_off = true, .controls = wm8750_snd_controls, .num_controls = ARRAY_SIZE(wm8750_snd_controls), diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index 70952ceb278..c13050b7793 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -408,24 +408,6 @@ static struct snd_soc_dai_driver wm8776_dai[] = { }, }; -#ifdef CONFIG_PM -static int wm8776_suspend(struct snd_soc_codec *codec) -{ - wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - -static int wm8776_resume(struct snd_soc_codec *codec) -{ - wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} -#else -#define wm8776_suspend NULL -#define wm8776_resume NULL -#endif - static int wm8776_probe(struct snd_soc_codec *codec) { int ret = 0; @@ -436,8 +418,6 @@ static int wm8776_probe(struct snd_soc_codec *codec) return ret; } - wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* Latch the update bits; right channel only since we always * update both. */ snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100); @@ -446,19 +426,10 @@ static int wm8776_probe(struct snd_soc_codec *codec) return ret; } -/* power down chip */ -static int wm8776_remove(struct snd_soc_codec *codec) -{ - wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8776 = { .probe = wm8776_probe, - .remove = wm8776_remove, - .suspend = wm8776_suspend, - .resume = wm8776_resume, .set_bias_level = wm8776_set_bias_level, + .suspend_bias_off = true, .controls = wm8776_snd_controls, .num_controls = ARRAY_SIZE(wm8776_snd_controls), diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 3addc5fe5cb..1315f764250 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -524,7 +524,6 @@ static int wm8804_remove(struct snd_soc_codec *codec) int i; wm8804 = snd_soc_codec_get_drvdata(codec); - wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF); for (i = 0; i < ARRAY_SIZE(wm8804->supplies); ++i) regulator_unregister_notifier(wm8804->supplies[i].consumer, @@ -606,8 +605,6 @@ static int wm8804_probe(struct snd_soc_codec *codec) goto err_reg_enable; } - wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; err_reg_enable: diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 44a5f1511f0..3a0d4b7d692 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -1209,16 +1209,8 @@ static int wm8900_probe(struct snd_soc_codec *codec) return 0; } -/* power down chip */ -static int wm8900_remove(struct snd_soc_codec *codec) -{ - wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8900 = { .probe = wm8900_probe, - .remove = wm8900_remove, .suspend = wm8900_suspend, .resume = wm8900_resume, .set_bias_level = wm8900_set_bias_level, diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index c038b3e0439..cc6b0ef98a3 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -26,6 +26,7 @@ #include <linux/regmap.h> #include <linux/slab.h> #include <linux/irq.h> +#include <linux/mutex.h> #include <sound/core.h> #include <sound/jack.h> #include <sound/pcm.h> @@ -117,12 +118,12 @@ static const struct reg_default wm8903_reg_defaults[] = { struct wm8903_priv { struct wm8903_platform_data *pdata; struct device *dev; - struct snd_soc_codec *codec; struct regmap *regmap; int sysclk; int irq; + struct mutex lock; int fs; int deemph; @@ -457,7 +458,7 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, if (deemph > 1) return -EINVAL; - mutex_lock(&codec->mutex); + mutex_lock(&wm8903->lock); if (wm8903->deemph != deemph) { wm8903->deemph = deemph; @@ -465,7 +466,7 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, ret = 1; } - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8903->lock); return ret; } @@ -1757,21 +1758,12 @@ static struct snd_soc_dai_driver wm8903_dai = { .symmetric_rates = 1, }; -static int wm8903_suspend(struct snd_soc_codec *codec) -{ - wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static int wm8903_resume(struct snd_soc_codec *codec) { struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); regcache_sync(wm8903->regmap); - wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; } @@ -1889,33 +1881,12 @@ static void wm8903_free_gpio(struct wm8903_priv *wm8903) } #endif -static int wm8903_probe(struct snd_soc_codec *codec) -{ - struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - - wm8903->codec = codec; - - /* power on device */ - wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} - -/* power down chip */ -static int wm8903_remove(struct snd_soc_codec *codec) -{ - wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8903 = { - .probe = wm8903_probe, - .remove = wm8903_remove, - .suspend = wm8903_suspend, .resume = wm8903_resume, .set_bias_level = wm8903_set_bias_level, .seq_notifier = wm8903_seq_notifier, + .suspend_bias_off = true, + .controls = wm8903_snd_controls, .num_controls = ARRAY_SIZE(wm8903_snd_controls), .dapm_widgets = wm8903_dapm_widgets, @@ -2023,6 +1994,8 @@ static int wm8903_i2c_probe(struct i2c_client *i2c, GFP_KERNEL); if (wm8903 == NULL) return -ENOMEM; + + mutex_init(&wm8903->lock); wm8903->dev = &i2c->dev; wm8903->regmap = devm_regmap_init_i2c(i2c, &wm8903_regmap); diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 52011043e54..e4142b4309e 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -695,17 +695,6 @@ static struct snd_soc_dai_driver wm8940_dai = { .symmetric_rates = 1, }; -static int wm8940_suspend(struct snd_soc_codec *codec) -{ - return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF); -} - -static int wm8940_resume(struct snd_soc_codec *codec) -{ - wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8940_probe(struct snd_soc_codec *codec) { struct wm8940_setup_data *pdata = codec->dev->platform_data; @@ -736,18 +725,11 @@ static int wm8940_probe(struct snd_soc_codec *codec) return ret; } -static int wm8940_remove(struct snd_soc_codec *codec) -{ - wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8940 = { .probe = wm8940_probe, - .remove = wm8940_remove, - .suspend = wm8940_suspend, - .resume = wm8940_resume, .set_bias_level = wm8940_set_bias_level, + .suspend_bias_off = true, + .controls = wm8940_snd_controls, .num_controls = ARRAY_SIZE(wm8940_snd_controls), .dapm_widgets = wm8940_dapm_widgets, diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 09d91d9dc4e..1173f7fef5a 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -866,29 +866,6 @@ static struct snd_soc_dai_driver wm8955_dai = { .ops = &wm8955_dai_ops, }; -#ifdef CONFIG_PM -static int wm8955_suspend(struct snd_soc_codec *codec) -{ - struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); - - wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF); - - regcache_mark_dirty(wm8955->regmap); - - return 0; -} - -static int wm8955_resume(struct snd_soc_codec *codec) -{ - wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return 0; -} -#else -#define wm8955_suspend NULL -#define wm8955_resume NULL -#endif - static int wm8955_probe(struct snd_soc_codec *codec) { struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); @@ -964,18 +941,10 @@ err_enable: return ret; } -static int wm8955_remove(struct snd_soc_codec *codec) -{ - wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8955 = { .probe = wm8955_probe, - .remove = wm8955_remove, - .suspend = wm8955_suspend, - .resume = wm8955_resume, .set_bias_level = wm8955_set_bias_level, + .suspend_bias_off = true, .controls = wm8955_snd_controls, .num_controls = ARRAY_SIZE(wm8955_snd_controls), diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index 0dada7f0105..3cbc82b3329 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -867,9 +867,9 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); if (fw && (wm8958_dsp2_fw(codec, "ENH_EQ", fw, true) == 0)) { - mutex_lock(&codec->mutex); + mutex_lock(&wm8994->fw_lock); wm8994->enh_eq = fw; - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8994->fw_lock); } } @@ -879,9 +879,9 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); if (fw && (wm8958_dsp2_fw(codec, "MBC+VSS", fw, true) == 0)) { - mutex_lock(&codec->mutex); + mutex_lock(&wm8994->fw_lock); wm8994->mbc_vss = fw; - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8994->fw_lock); } } @@ -891,9 +891,9 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); if (fw && (wm8958_dsp2_fw(codec, "MBC", fw, true) == 0)) { - mutex_lock(&codec->mutex); + mutex_lock(&wm8994->fw_lock); wm8994->mbc = fw; - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8994->fw_lock); } } diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 4dc4e85116c..031a1ae71d9 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -125,9 +125,10 @@ struct wm8960_priv { struct snd_soc_dapm_widget *out3; bool deemph; int playback_fs; + struct wm8960_data pdata; }; -#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) /* enumerated controls */ static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", @@ -440,8 +441,8 @@ static const struct snd_soc_dapm_route audio_paths_capless[] = { static int wm8960_add_widgets(struct snd_soc_codec *codec) { - struct wm8960_data *pdata = codec->dev->platform_data; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = &wm8960->pdata; struct snd_soc_dapm_context *dapm = &codec->dapm; struct snd_soc_dapm_widget *w; @@ -942,56 +943,15 @@ static struct snd_soc_dai_driver wm8960_dai = { .symmetric_rates = 1, }; -static int wm8960_suspend(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8960_resume(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static int wm8960_probe(struct snd_soc_codec *codec) { struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - struct wm8960_data *pdata = dev_get_platdata(codec->dev); - int ret; - - wm8960->set_bias_level = wm8960_set_bias_level_out3; - - if (!pdata) { - dev_warn(codec->dev, "No platform data supplied\n"); - } else { - if (pdata->capless) - wm8960->set_bias_level = wm8960_set_bias_level_capless; - } - - ret = wm8960_reset(codec); - if (ret < 0) { - dev_err(codec->dev, "Failed to issue reset\n"); - return ret; - } - - wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + struct wm8960_data *pdata = &wm8960->pdata; - /* Latch the update bits */ - snd_soc_update_bits(codec, WM8960_LINVOL, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RINVOL, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LADC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RADC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LDAC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RDAC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LOUT1, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_ROUT1, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100); + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; snd_soc_add_codec_controls(codec, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls)); @@ -1000,21 +960,10 @@ static int wm8960_probe(struct snd_soc_codec *codec) return 0; } -/* power down chip */ -static int wm8960_remove(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { .probe = wm8960_probe, - .remove = wm8960_remove, - .suspend = wm8960_suspend, - .resume = wm8960_resume, .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = true, }; static const struct regmap_config wm8960_regmap = { @@ -1029,6 +978,18 @@ static const struct regmap_config wm8960_regmap = { .volatile_reg = wm8960_volatile, }; +static void wm8960_set_pdata_from_of(struct i2c_client *i2c, + struct wm8960_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + + if (of_property_read_bool(np, "wlf,capless")) + pdata->capless = true; + + if (of_property_read_bool(np, "wlf,shared-lrclk")) + pdata->shared_lrclk = true; +} + static int wm8960_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1045,7 +1006,18 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, if (IS_ERR(wm8960->regmap)) return PTR_ERR(wm8960->regmap); - if (pdata && pdata->shared_lrclk) { + if (pdata) + memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); + else if (i2c->dev.of_node) + wm8960_set_pdata_from_of(i2c, &wm8960->pdata); + + ret = wm8960_reset(wm8960->regmap); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, 0x4, 0x4); if (ret != 0) { @@ -1055,6 +1027,18 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, } } + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + i2c_set_clientdata(i2c, wm8960); ret = snd_soc_register_codec(&i2c->dev, @@ -1075,10 +1059,17 @@ static const struct i2c_device_id wm8960_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); +static const struct of_device_id wm8960_of_match[] = { + { .compatible = "wlf,wm8960", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8960_of_match); + static struct i2c_driver wm8960_i2c_driver = { .driver = { .name = "wm8960", .owner = THIS_MODULE, + .of_match_table = wm8960_of_match, }, .probe = wm8960_i2c_probe, .remove = wm8960_i2c_remove, diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 41d23e920ad..eeffd05384b 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -835,7 +835,6 @@ static struct snd_soc_dai_driver wm8961_dai = { static int wm8961_probe(struct snd_soc_codec *codec) { - struct snd_soc_dapm_context *dapm = &codec->dapm; u16 reg; /* Enable class W */ @@ -871,50 +870,33 @@ static int wm8961_probe(struct snd_soc_codec *codec) reg &= ~WM8961_MANUAL_MODE; snd_soc_write(codec, WM8961_CLOCKING_3, reg); - wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - snd_soc_add_codec_controls(codec, wm8961_snd_controls, - ARRAY_SIZE(wm8961_snd_controls)); - snd_soc_dapm_new_controls(dapm, wm8961_dapm_widgets, - ARRAY_SIZE(wm8961_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); - - return 0; -} - -static int wm8961_remove(struct snd_soc_codec *codec) -{ - wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } #ifdef CONFIG_PM -static int wm8961_suspend(struct snd_soc_codec *codec) -{ - wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); - - return 0; -} static int wm8961_resume(struct snd_soc_codec *codec) { snd_soc_cache_sync(codec); - wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; } #else -#define wm8961_suspend NULL #define wm8961_resume NULL #endif static struct snd_soc_codec_driver soc_codec_dev_wm8961 = { .probe = wm8961_probe, - .remove = wm8961_remove, - .suspend = wm8961_suspend, .resume = wm8961_resume, .set_bias_level = wm8961_set_bias_level, + .suspend_bias_off = true, + + .controls = wm8961_snd_controls, + .num_controls = ARRAY_SIZE(wm8961_snd_controls), + .dapm_widgets = wm8961_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8961_dapm_widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), }; static const struct regmap_config wm8961_regmap = { diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index cfd38917acb..1534d88a66e 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -26,6 +26,7 @@ #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/workqueue.h> +#include <linux/mutex.h> #include <sound/core.h> #include <sound/jack.h> #include <sound/pcm.h> @@ -67,6 +68,7 @@ struct wm8962_priv { int fll_fref; int fll_fout; + struct mutex dsp2_ena_lock; u16 dsp2_ena; struct delayed_work mic_work; @@ -1570,7 +1572,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, int dsp2_running = snd_soc_read(codec, WM8962_DSP2_POWER_MANAGEMENT) & WM8962_DSP2_ENA; - mutex_lock(&codec->mutex); + mutex_lock(&wm8962->dsp2_ena_lock); if (ucontrol->value.integer.value[0]) wm8962->dsp2_ena |= 1 << shift; @@ -1590,7 +1592,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, } out: - mutex_unlock(&codec->mutex); + mutex_unlock(&wm8962->dsp2_ena_lock); return ret; } @@ -3556,6 +3558,8 @@ static int wm8962_i2c_probe(struct i2c_client *i2c, if (wm8962 == NULL) return -ENOMEM; + mutex_init(&wm8962->dsp2_ena_lock); + i2c_set_clientdata(i2c, wm8962); INIT_DELAYED_WORK(&wm8962->mic_work, wm8962_mic_work); diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 682e9eda101..ff0e4646b93 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -568,18 +568,6 @@ static struct snd_soc_dai_driver wm8974_dai = { .symmetric_rates = 1, }; -static int wm8974_suspend(struct snd_soc_codec *codec) -{ - wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static int wm8974_resume(struct snd_soc_codec *codec) -{ - wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - static const struct regmap_config wm8974_regmap = { .reg_bits = 7, .val_bits = 9, @@ -599,24 +587,13 @@ static int wm8974_probe(struct snd_soc_codec *codec) return ret; } - wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - return ret; -} - -/* power down chip */ -static int wm8974_remove(struct snd_soc_codec *codec) -{ - wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } static struct snd_soc_codec_driver soc_codec_dev_wm8974 = { .probe = wm8974_probe, - .remove = wm8974_remove, - .suspend = wm8974_suspend, - .resume = wm8974_resume, .set_bias_level = wm8974_set_bias_level, + .suspend_bias_off = true, .controls = wm8974_snd_controls, .num_controls = ARRAY_SIZE(wm8974_snd_controls), diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 1fcb9f3f309..dbca6e0cc93 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -4457,6 +4457,8 @@ static int wm8994_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, wm8994); + mutex_init(&wm8994->fw_lock); + wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent); pm_runtime_enable(&pdev->dev); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 6536f8d45ac..dd73387b1cc 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -13,6 +13,7 @@ #include <linux/firmware.h> #include <linux/completion.h> #include <linux/workqueue.h> +#include <linux/mutex.h> #include "wm_hubs.h" @@ -156,6 +157,7 @@ struct wm8994_priv { unsigned int aif1clk_disable:1; unsigned int aif2clk_disable:1; + struct mutex fw_lock; int dsp_active; const struct firmware *cur_fw; const struct firmware *mbc; diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index c0b7f45dfa3..d3a800fa6f0 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -203,13 +203,14 @@ static const struct snd_soc_dapm_route wm9705_audio_map[] = { /* We use a register cache to enhance read performance. */ static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; switch (reg) { case AC97_RESET: case AC97_VENDOR_ID1: case AC97_VENDOR_ID2: - return soc_ac97_ops->read(codec->ac97, reg); + return soc_ac97_ops->read(ac97, reg); default: reg = reg >> 1; @@ -223,9 +224,10 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(ac97, reg, val); reg = reg >> 1; if (reg < (ARRAY_SIZE(wm9705_reg))) cache[reg] = val; @@ -263,7 +265,6 @@ static const struct snd_soc_dai_ops wm9705_dai_ops = { static struct snd_soc_dai_driver wm9705_dai[] = { { .name = "wm9705-hifi", - .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -294,36 +295,41 @@ static struct snd_soc_dai_driver wm9705_dai[] = { static int wm9705_reset(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + if (soc_ac97_ops->reset) { - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(ac97); if (ac97_read(codec, 0) == wm9705_reg[0]) return 0; /* Success */ } + dev_err(codec->dev, "Failed to reset: AC97 link error\n"); + return -EIO; } #ifdef CONFIG_PM static int wm9705_soc_suspend(struct snd_soc_codec *codec) { - soc_ac97_ops->write(codec->ac97, AC97_POWERDOWN, 0xffff); + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + soc_ac97_ops->write(ac97, AC97_POWERDOWN, 0xffff); return 0; } static int wm9705_soc_resume(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); int i, ret; u16 *cache = codec->reg_cache; ret = wm9705_reset(codec); - if (ret < 0) { - printk(KERN_ERR "could not reset AC97 codec\n"); + if (ret < 0) return ret; - } for (i = 2; i < ARRAY_SIZE(wm9705_reg) << 1; i += 2) { - soc_ac97_ops->write(codec->ac97, i, cache[i>>1]); + soc_ac97_ops->write(ac97, i, cache[i>>1]); } return 0; @@ -335,31 +341,34 @@ static int wm9705_soc_resume(struct snd_soc_codec *codec) static int wm9705_soc_probe(struct snd_soc_codec *codec) { + struct snd_ac97 *ac97; int ret = 0; - ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0); - if (ret < 0) { - printk(KERN_ERR "wm9705: failed to register AC97 codec\n"); + ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(ac97)) { + ret = PTR_ERR(ac97); + dev_err(codec->dev, "Failed to register AC97 codec\n"); return ret; } + snd_soc_codec_set_drvdata(codec, ac97); + ret = wm9705_reset(codec); if (ret) goto reset_err; - snd_soc_add_codec_controls(codec, wm9705_snd_ac97_controls, - ARRAY_SIZE(wm9705_snd_ac97_controls)); - return 0; reset_err: - snd_soc_free_ac97_codec(codec); + snd_soc_free_ac97_codec(ac97); return ret; } static int wm9705_soc_remove(struct snd_soc_codec *codec) { - snd_soc_free_ac97_codec(codec); + struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec); + + snd_soc_free_ac97_codec(ac97); return 0; } @@ -374,6 +383,9 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9705 = { .reg_word_size = sizeof(u16), .reg_cache_step = 2, .reg_cache_default = wm9705_reg, + + .controls = wm9705_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9705_snd_ac97_controls), .dapm_widgets = wm9705_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm9705_dapm_widgets), .dapm_routes = wm9705_audio_map, diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index c5eb746087b..52a211be5b4 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -23,6 +23,12 @@ #include <sound/tlv.h> #include "wm9712.h" +struct wm9712_priv { + struct snd_ac97 *ac97; + unsigned int hp_mixer[2]; + struct mutex lock; +}; + static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg); static int ac97_write(struct snd_soc_codec *codec, @@ -48,12 +54,10 @@ static const u16 wm9712_reg[] = { 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */ 0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */ - 0x0000, 0x0000 /* virtual hp mixers */ }; -/* virtual HP mixers regs */ -#define HPL_MIXER 0x80 -#define HPR_MIXER 0x82 +#define HPL_MIXER 0x0 +#define HPR_MIXER 0x1 static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"}; static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"}; @@ -157,75 +161,108 @@ SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv), SOC_SINGLE_TLV("Mic Boost Volume", AC97_MIC, 7, 1, 0, boost_tlv), }; +static const unsigned int wm9712_mixer_mute_regs[] = { + AC97_VIDEO, + AC97_PCM, + AC97_LINE, + AC97_PHONE, + AC97_CD, + AC97_PC_BEEP, +}; + /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path. */ -static int mixer_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) +static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - u16 l, r, beep, line, phone, mic, pcm, aux; - - l = ac97_read(w->codec, HPL_MIXER); - r = ac97_read(w->codec, HPR_MIXER); - beep = ac97_read(w->codec, AC97_PC_BEEP); - mic = ac97_read(w->codec, AC97_VIDEO); - phone = ac97_read(w->codec, AC97_PHONE); - line = ac97_read(w->codec, AC97_LINE); - pcm = ac97_read(w->codec, AC97_PCM); - aux = ac97_read(w->codec, AC97_CD); - - if (l & 0x1 || r & 0x1) - ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); + unsigned int val = ucontrol->value.enumerated.item[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, mask, shift, old; + struct snd_soc_dapm_update update; + bool change; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + mask = 1 << shift; + + mutex_lock(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.enumerated.item[0]) + wm9712->hp_mixer[mixer] |= mask; else - ac97_write(w->codec, AC97_VIDEO, mic | 0x8000); + wm9712->hp_mixer[mixer] &= ~mask; + + change = old != wm9712->hp_mixer[mixer]; + if (change) { + update.kcontrol = kcontrol; + update.reg = wm9712_mixer_mute_regs[shift]; + update.mask = 0x8000; + if ((wm9712->hp_mixer[0] & mask) || + (wm9712->hp_mixer[1] & mask)) + update.val = 0x0; + else + update.val = 0x8000; + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, + &update); + } - if (l & 0x2 || r & 0x2) - ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); - else - ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + mutex_unlock(&wm9712->lock); - if (l & 0x4 || r & 0x4) - ac97_write(w->codec, AC97_LINE, line & 0x7fff); - else - ac97_write(w->codec, AC97_LINE, line | 0x8000); + return change; +} - if (l & 0x8 || r & 0x8) - ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); - else - ac97_write(w->codec, AC97_PHONE, phone | 0x8000); +static int wm9712_hp_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int shift, mixer; - if (l & 0x10 || r & 0x10) - ac97_write(w->codec, AC97_CD, aux & 0x7fff); - else - ac97_write(w->codec, AC97_CD, aux | 0x8000); + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; - if (l & 0x20 || r & 0x20) - ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); - else - ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + ucontrol->value.enumerated.item[0] = + (wm9712->hp_mixer[mixer] >> shift) & 1; return 0; } +#define WM9712_HP_MIXER_CTRL(xname, xmixer, xshift) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = wm9712_hp_mixer_get, .put = wm9712_hp_mixer_put, \ + .private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, \ + (xmixer << 8) | xshift, 1, 0, 0) \ +} + /* Left Headphone Mixers */ static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = { - SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0), - SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0), - SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0), - SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0), - SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0), - SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0), + WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPL_MIXER, 5), + WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 4), + WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPL_MIXER, 3), + WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPL_MIXER, 2), + WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 1), + WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPL_MIXER, 0), }; /* Right Headphone Mixers */ static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = { - SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0), - SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0), - SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0), - SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0), - SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0), - SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0), + WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPR_MIXER, 5), + WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 4), + WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPR_MIXER, 3), + WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPR_MIXER, 2), + WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 1), + WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPR_MIXER, 0), }; /* Speaker Mixer */ @@ -299,12 +336,10 @@ SND_SOC_DAPM_MUX("Right Mic Select Source", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0, &wm9712_diff_sel_controls), SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), -SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1, - &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls), - mixer_event, SND_SOC_DAPM_POST_REG), -SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1, - &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls), - mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_INT_PAGING, 9, 1, + &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_INT_PAGING, 8, 1, + &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls)), SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1, &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)), SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1, @@ -450,12 +485,13 @@ static const struct snd_soc_dapm_route wm9712_audio_map[] = { static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) { + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || reg == AC97_REC_GAIN) - return soc_ac97_ops->read(codec->ac97, reg); + return soc_ac97_ops->read(wm9712->ac97, reg); else { reg = reg >> 1; @@ -469,10 +505,10 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; - if (reg < 0x7c) - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(wm9712->ac97, reg, val); reg = reg >> 1; if (reg < (ARRAY_SIZE(wm9712_reg))) cache[reg] = val; @@ -532,7 +568,6 @@ static const struct snd_soc_dai_ops wm9712_dai_ops_aux = { static struct snd_soc_dai_driver wm9712_dai[] = { { .name = "wm9712-hifi", - .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -581,21 +616,23 @@ static int wm9712_set_bias_level(struct snd_soc_codec *codec, static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) { + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); + if (try_warm && soc_ac97_ops->warm_reset) { - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(wm9712->ac97); if (ac97_read(codec, 0) == wm9712_reg[0]) return 1; } - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(wm9712->ac97); if (soc_ac97_ops->warm_reset) - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(wm9712->ac97); if (ac97_read(codec, 0) != wm9712_reg[0]) goto err; return 0; err: - printk(KERN_ERR "WM9712 AC97 reset failed\n"); + dev_err(codec->dev, "Failed to reset: AC97 link error\n"); return -EIO; } @@ -607,14 +644,13 @@ static int wm9712_soc_suspend(struct snd_soc_codec *codec) static int wm9712_soc_resume(struct snd_soc_codec *codec) { + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); int i, ret; u16 *cache = codec->reg_cache; ret = wm9712_reset(codec, 1); - if (ret < 0) { - printk(KERN_ERR "could not reset AC97 codec\n"); + if (ret < 0) return ret; - } wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -624,7 +660,7 @@ static int wm9712_soc_resume(struct snd_soc_codec *codec) if (i == AC97_INT_PAGING || i == AC97_POWERDOWN || (i > 0x58 && i != 0x5c)) continue; - soc_ac97_ops->write(codec->ac97, i, cache[i>>1]); + soc_ac97_ops->write(wm9712->ac97, i, cache[i>>1]); } } @@ -633,37 +669,37 @@ static int wm9712_soc_resume(struct snd_soc_codec *codec) static int wm9712_soc_probe(struct snd_soc_codec *codec) { + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); int ret = 0; - ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0); - if (ret < 0) { - printk(KERN_ERR "wm9712: failed to register AC97 codec\n"); + wm9712->ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(wm9712->ac97)) { + ret = PTR_ERR(wm9712->ac97); + dev_err(codec->dev, "Failed to register AC97 codec: %d\n", ret); return ret; } ret = wm9712_reset(codec, 0); - if (ret < 0) { - printk(KERN_ERR "Failed to reset WM9712: AC97 link error\n"); + if (ret < 0) goto reset_err; - } /* set alc mux to none */ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000); wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - snd_soc_add_codec_controls(codec, wm9712_snd_ac97_controls, - ARRAY_SIZE(wm9712_snd_ac97_controls)); return 0; reset_err: - snd_soc_free_ac97_codec(codec); + snd_soc_free_ac97_codec(wm9712->ac97); return ret; } static int wm9712_soc_remove(struct snd_soc_codec *codec) { - snd_soc_free_ac97_codec(codec); + struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec); + + snd_soc_free_ac97_codec(wm9712->ac97); return 0; } @@ -679,6 +715,9 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9712 = { .reg_word_size = sizeof(u16), .reg_cache_step = 2, .reg_cache_default = wm9712_reg, + + .controls = wm9712_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9712_snd_ac97_controls), .dapm_widgets = wm9712_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm9712_dapm_widgets), .dapm_routes = wm9712_audio_map, @@ -687,6 +726,16 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9712 = { static int wm9712_probe(struct platform_device *pdev) { + struct wm9712_priv *wm9712; + + wm9712 = devm_kzalloc(&pdev->dev, sizeof(*wm9712), GFP_KERNEL); + if (wm9712 == NULL) + return -ENOMEM; + + mutex_init(&wm9712->lock); + + platform_set_drvdata(pdev, wm9712); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai)); } diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index bddee30a4bc..6c95d98b0eb 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -30,7 +30,10 @@ #include "wm9713.h" struct wm9713_priv { + struct snd_ac97 *ac97; u32 pll_in; /* PLL input frequency */ + unsigned int hp_mixer[2]; + struct mutex lock; }; static unsigned int ac97_read(struct snd_soc_codec *codec, @@ -59,13 +62,10 @@ static const u16 wm9713_reg[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0006, 0x0001, 0x0000, 0x574d, 0x4c13, - 0x0000, 0x0000, 0x0000 }; -/* virtual HP mixers regs */ -#define HPL_MIXER 0x80 -#define HPR_MIXER 0x82 -#define MICB_MUX 0x82 +#define HPL_MIXER 0 +#define HPR_MIXER 1 static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; @@ -110,7 +110,7 @@ SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ -SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */ +SOC_ENUM_SINGLE_VIRT(2, wm9713_micb_select), /* mic selection 19 */ }; static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0); @@ -234,6 +234,14 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, return 0; } +static const unsigned int wm9713_mixer_mute_regs[] = { + AC97_PC_BEEP, + AC97_MASTER_TONE, + AC97_PHONE, + AC97_REC_SEL, + AC97_PCM, + AC97_AUX, +}; /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. @@ -241,73 +249,95 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, * register map, thus we add a new (virtual) register to help determine the * audio route within the device. */ -static int mixer_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - u16 l, r, beep, tone, phone, rec, pcm, aux; - - l = ac97_read(w->codec, HPL_MIXER); - r = ac97_read(w->codec, HPR_MIXER); - beep = ac97_read(w->codec, AC97_PC_BEEP); - tone = ac97_read(w->codec, AC97_MASTER_TONE); - phone = ac97_read(w->codec, AC97_PHONE); - rec = ac97_read(w->codec, AC97_REC_SEL); - pcm = ac97_read(w->codec, AC97_PCM); - aux = ac97_read(w->codec, AC97_AUX); - - if (event & SND_SOC_DAPM_PRE_REG) - return 0; - if ((l & 0x1) || (r & 0x1)) - ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + unsigned int val = ucontrol->value.enumerated.item[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, mask, shift, old; + struct snd_soc_dapm_update update; + bool change; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + mask = (1 << shift); + + mutex_lock(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.enumerated.item[0]) + wm9713->hp_mixer[mixer] |= mask; else - ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + wm9713->hp_mixer[mixer] &= ~mask; + + change = old != wm9713->hp_mixer[mixer]; + if (change) { + update.kcontrol = kcontrol; + update.reg = wm9713_mixer_mute_regs[shift]; + update.mask = 0x8000; + if ((wm9713->hp_mixer[0] & mask) || + (wm9713->hp_mixer[1] & mask)) + update.val = 0x0; + else + update.val = 0x8000; + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, + &update); + } - if ((l & 0x2) || (r & 0x2)) - ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff); - else - ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000); + mutex_unlock(&wm9713->lock); - if ((l & 0x4) || (r & 0x4)) - ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); - else - ac97_write(w->codec, AC97_PHONE, phone | 0x8000); + return change; +} - if ((l & 0x8) || (r & 0x8)) - ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff); - else - ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000); +static int wm9713_hp_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, shift; - if ((l & 0x10) || (r & 0x10)) - ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); - else - ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; - if ((l & 0x20) || (r & 0x20)) - ac97_write(w->codec, AC97_AUX, aux & 0x7fff); - else - ac97_write(w->codec, AC97_AUX, aux | 0x8000); + ucontrol->value.enumerated.item[0] = + (wm9713->hp_mixer[mixer] >> shift) & 1; return 0; } +#define WM9713_HP_MIXER_CTRL(xname, xmixer, xshift) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = wm9713_hp_mixer_get, .put = wm9713_hp_mixer_put, \ + .private_value = SOC_DOUBLE_VALUE(SND_SOC_NOPM, \ + xshift, xmixer, 1, 0, 0) \ +} + /* Left Headphone Mixers */ static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { -SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0), -SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0), -SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0), -SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0), -SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0), -SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0), +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPL_MIXER, 5), +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPL_MIXER, 4), +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 3), +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 2), +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPL_MIXER, 1), +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPL_MIXER, 0), }; /* Right Headphone Mixers */ static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { -SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0), -SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0), -SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0), -SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0), -SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0), -SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0), +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPR_MIXER, 5), +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPR_MIXER, 4), +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 3), +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 2), +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPR_MIXER, 1), +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPR_MIXER, 0), }; /* headphone capture mux */ @@ -429,12 +459,10 @@ SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, &wm9713_mic_sel_mux_controls), SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, &wm9713_micb_sel_mux_controls), -SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, - &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls), - mixer_event, SND_SOC_DAPM_POST_REG), -SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, - &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls), - mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, + &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, + &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls)), SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, @@ -647,12 +675,13 @@ static const struct snd_soc_dapm_route wm9713_audio_map[] = { static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || reg == AC97_CD) - return soc_ac97_ops->read(codec->ac97, reg); + return soc_ac97_ops->read(wm9713->ac97, reg); else { reg = reg >> 1; @@ -666,9 +695,10 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + u16 *cache = codec->reg_cache; - if (reg < 0x7c) - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(wm9713->ac97, reg, val); reg = reg >> 1; if (reg < (ARRAY_SIZE(wm9713_reg))) cache[reg] = val; @@ -689,7 +719,8 @@ struct _pll_div { * to allow rounding later */ #define FIXED_PLL_SIZE ((1 << 22) * 10) -static void pll_factors(struct _pll_div *pll_div, unsigned int source) +static void pll_factors(struct snd_soc_codec *codec, + struct _pll_div *pll_div, unsigned int source) { u64 Kpart; unsigned int K, Ndiv, Nmod, target; @@ -724,7 +755,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source) Ndiv = target / source; if ((Ndiv < 5) || (Ndiv > 12)) - printk(KERN_WARNING + dev_warn(codec->dev, "WM9713 PLL N value %u out of recommended range!\n", Ndiv); @@ -768,7 +799,7 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, return 0; } - pll_factors(&pll_div, freq_in); + pll_factors(codec, &pll_div, freq_in); if (pll_div.k == 0) { reg = (pll_div.n << 12) | (pll_div.lf << 11) | @@ -1049,7 +1080,6 @@ static const struct snd_soc_dai_ops wm9713_dai_ops_voice = { static struct snd_soc_dai_driver wm9713_dai[] = { { .name = "wm9713-hifi", - .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -1095,17 +1125,22 @@ static struct snd_soc_dai_driver wm9713_dai[] = { int wm9713_reset(struct snd_soc_codec *codec, int try_warm) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + if (try_warm && soc_ac97_ops->warm_reset) { - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(wm9713->ac97); if (ac97_read(codec, 0) == wm9713_reg[0]) return 1; } - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(wm9713->ac97); if (soc_ac97_ops->warm_reset) - soc_ac97_ops->warm_reset(codec->ac97); - if (ac97_read(codec, 0) != wm9713_reg[0]) + soc_ac97_ops->warm_reset(wm9713->ac97); + if (ac97_read(codec, 0) != wm9713_reg[0]) { + dev_err(codec->dev, "Failed to reset: AC97 link error\n"); return -EIO; + } + return 0; } EXPORT_SYMBOL_GPL(wm9713_reset); @@ -1163,10 +1198,8 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec) u16 *cache = codec->reg_cache; ret = wm9713_reset(codec, 1); - if (ret < 0) { - printk(KERN_ERR "could not reset AC97 codec\n"); + if (ret < 0) return ret; - } wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1180,7 +1213,7 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec) if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || i == AC97_EXTENDED_MSTATUS || i > 0x66) continue; - soc_ac97_ops->write(codec->ac97, i, cache[i>>1]); + soc_ac97_ops->write(wm9713->ac97, i, cache[i>>1]); } } @@ -1189,26 +1222,19 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec) static int wm9713_soc_probe(struct snd_soc_codec *codec) { - struct wm9713_priv *wm9713; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); int ret = 0, reg; - wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); - if (wm9713 == NULL) - return -ENOMEM; - snd_soc_codec_set_drvdata(codec, wm9713); - - ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0); - if (ret < 0) - goto codec_err; + wm9713->ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(wm9713->ac97)) + return PTR_ERR(wm9713->ac97); /* do a cold reset for the controller and then try * a warm reset followed by an optional cold reset for codec */ wm9713_reset(codec, 0); ret = wm9713_reset(codec, 1); - if (ret < 0) { - printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n"); + if (ret < 0) goto reset_err; - } wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1216,23 +1242,18 @@ static int wm9713_soc_probe(struct snd_soc_codec *codec) reg = ac97_read(codec, AC97_CD) & 0x7fff; ac97_write(codec, AC97_CD, reg); - snd_soc_add_codec_controls(codec, wm9713_snd_ac97_controls, - ARRAY_SIZE(wm9713_snd_ac97_controls)); - return 0; reset_err: - snd_soc_free_ac97_codec(codec); -codec_err: - kfree(wm9713); + snd_soc_free_ac97_codec(wm9713->ac97); return ret; } static int wm9713_soc_remove(struct snd_soc_codec *codec) { struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); - snd_soc_free_ac97_codec(codec); - kfree(wm9713); + + snd_soc_free_ac97_codec(wm9713->ac97); return 0; } @@ -1248,6 +1269,9 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9713 = { .reg_word_size = sizeof(u16), .reg_cache_step = 2, .reg_cache_default = wm9713_reg, + + .controls = wm9713_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9713_snd_ac97_controls), .dapm_widgets = wm9713_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets), .dapm_routes = wm9713_audio_map, @@ -1256,6 +1280,16 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9713 = { static int wm9713_probe(struct platform_device *pdev) { + struct wm9713_priv *wm9713; + + wm9713 = devm_kzalloc(&pdev->dev, sizeof(*wm9713), GFP_KERNEL); + if (wm9713 == NULL) + return -ENOMEM; + + mutex_init(&wm9713->lock); + + platform_set_drvdata(pdev, wm9713); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai)); } diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index f412a9911a7..720d6e85298 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -21,6 +21,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/vmalloc.h> #include <linux/workqueue.h> #include <sound/core.h> #include <sound/pcm.h> @@ -169,11 +170,12 @@ static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len, if (buf == NULL) return NULL; - buf->buf = kmemdup(src, len, GFP_KERNEL | GFP_DMA); + buf->buf = vmalloc(len); if (!buf->buf) { - kfree(buf); + vfree(buf); return NULL; } + memcpy(buf->buf, src, len); if (list) list_add_tail(&buf->list, list); @@ -188,7 +190,7 @@ static void wm_adsp_buf_free(struct list_head *list) struct wm_adsp_buf, list); list_del(&buf->list); - kfree(buf->buf); + vfree(buf->buf); kfree(buf); } } @@ -684,38 +686,24 @@ static int wm_adsp_load(struct wm_adsp *dsp) } if (reg) { - size_t to_write = PAGE_SIZE; - size_t remain = le32_to_cpu(region->len); - const u8 *data = region->data; - - while (remain > 0) { - if (remain < PAGE_SIZE) - to_write = remain; - - buf = wm_adsp_buf_alloc(data, - to_write, - &buf_list); - if (!buf) { - adsp_err(dsp, "Out of memory\n"); - ret = -ENOMEM; - goto out_fw; - } - - ret = regmap_raw_write_async(regmap, reg, - buf->buf, - to_write); - if (ret != 0) { - adsp_err(dsp, - "%s.%d: Failed to write %zd bytes at %d in %s: %d\n", - file, regions, - to_write, offset, - region_name, ret); - goto out_fw; - } + buf = wm_adsp_buf_alloc(region->data, + le32_to_cpu(region->len), + &buf_list); + if (!buf) { + adsp_err(dsp, "Out of memory\n"); + ret = -ENOMEM; + goto out_fw; + } - data += to_write; - reg += to_write / 2; - remain -= to_write; + ret = regmap_raw_write_async(regmap, reg, buf->buf, + le32_to_cpu(region->len)); + if (ret != 0) { + adsp_err(dsp, + "%s.%d: Failed to write %d bytes at %d in %s: %d\n", + file, regions, + le32_to_cpu(region->len), offset, + region_name, ret); + goto out_fw; } } @@ -1065,8 +1053,10 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp) be32_to_cpu(adsp1_alg[i].zm)); region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) - return -ENOMEM; + if (!region) { + ret = -ENOMEM; + goto out; + } region->type = WMFW_ADSP1_DM; region->alg = be32_to_cpu(adsp1_alg[i].alg.id); region->base = be32_to_cpu(adsp1_alg[i].dm); @@ -1083,8 +1073,10 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp) } region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) - return -ENOMEM; + if (!region) { + ret = -ENOMEM; + goto out; + } region->type = WMFW_ADSP1_ZM; region->alg = be32_to_cpu(adsp1_alg[i].alg.id); region->base = be32_to_cpu(adsp1_alg[i].zm); @@ -1113,8 +1105,10 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp) be32_to_cpu(adsp2_alg[i].zm)); region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) - return -ENOMEM; + if (!region) { + ret = -ENOMEM; + goto out; + } region->type = WMFW_ADSP2_XM; region->alg = be32_to_cpu(adsp2_alg[i].alg.id); region->base = be32_to_cpu(adsp2_alg[i].xm); @@ -1131,8 +1125,10 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp) } region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) - return -ENOMEM; + if (!region) { + ret = -ENOMEM; + goto out; + } region->type = WMFW_ADSP2_YM; region->alg = be32_to_cpu(adsp2_alg[i].alg.id); region->base = be32_to_cpu(adsp2_alg[i].ym); @@ -1149,8 +1145,10 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp) } region = kzalloc(sizeof(*region), GFP_KERNEL); - if (!region) - return -ENOMEM; + if (!region) { + ret = -ENOMEM; + goto out; + } region->type = WMFW_ADSP2_ZM; region->alg = be32_to_cpu(adsp2_alg[i].alg.id); region->base = be32_to_cpu(adsp2_alg[i].zm); @@ -1355,6 +1353,7 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) file, blocks, pos - firmware->size); out_fw: + regmap_async_complete(regmap); release_firmware(firmware); wm_adsp_buf_free(&buf_list); out: @@ -1594,13 +1593,6 @@ static void wm_adsp2_boot_work(struct work_struct *work) if (ret != 0) goto err; - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA, - ADSP2_CORE_ENA); - if (ret != 0) - goto err; - dsp->running = true; return; @@ -1650,8 +1642,8 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_START, - ADSP2_START); + ADSP2_CORE_ENA | ADSP2_START, + ADSP2_CORE_ENA | ADSP2_START); if (ret != 0) goto err; break; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 0eed9b1b24e..0dab382ba14 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -70,6 +70,7 @@ struct davinci_mcasp { void __iomem *base; u32 fifo_base; struct device *dev; + struct snd_pcm_substream *substreams[2]; /* McASP specific data */ int tdm_slots; @@ -80,6 +81,7 @@ struct davinci_mcasp { u8 bclk_div; u16 bclk_lrclk_ratio; int streams; + u32 irq_request[2]; int sysclk_freq; bool bclk_master; @@ -90,6 +92,9 @@ struct davinci_mcasp { bool dat_port; + /* Used for comstraint setting on the second stream */ + u32 channels; + #ifdef CONFIG_PM_SLEEP struct davinci_mcasp_context context; #endif @@ -154,9 +159,16 @@ static bool mcasp_is_synchronous(struct davinci_mcasp *mcasp) static void mcasp_start_rx(struct davinci_mcasp *mcasp) { + if (mcasp->rxnumevt) { /* enable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + mcasp_set_bits(mcasp, reg, FIFO_ENABLE); + } + + /* Start clocks */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST); mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST); - /* * When ASYNC == 0 the transmit and receive sections operate * synchronously from the transmit clock and frame sync. We need to make @@ -167,74 +179,69 @@ static void mcasp_start_rx(struct davinci_mcasp *mcasp) mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); } + /* Activate serializer(s) */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR); - mcasp_set_reg(mcasp, DAVINCI_MCASP_RXBUF_REG, 0); - - mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSMRST); - mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXFSRST); - mcasp_set_reg(mcasp, DAVINCI_MCASP_RXBUF_REG, 0); - + /* Release RX state machine */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSMRST); + /* Release Frame Sync generator */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXFSRST); - if (mcasp_is_synchronous(mcasp)) mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); + + /* enable receive IRQs */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]); } static void mcasp_start_tx(struct davinci_mcasp *mcasp) { - u8 offset = 0, i; u32 cnt; + if (mcasp->txnumevt) { /* enable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + mcasp_set_bits(mcasp, reg, FIFO_ENABLE); + } + + /* Start clocks */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); + /* Activate serializer(s) */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR); - mcasp_set_reg(mcasp, DAVINCI_MCASP_TXBUF_REG, 0); - mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSMRST); - mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); - mcasp_set_reg(mcasp, DAVINCI_MCASP_TXBUF_REG, 0); - for (i = 0; i < mcasp->num_serializer; i++) { - if (mcasp->serial_dir[i] == TX_MODE) { - offset = i; - break; - } - } - - /* wait for TX ready */ + /* wait for XDATA to be cleared */ cnt = 0; - while (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_XRSRCTL_REG(offset)) & - TXSTATE) && (cnt < 100000)) + while (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & + ~XRDATA) && (cnt < 100000)) cnt++; - mcasp_set_reg(mcasp, DAVINCI_MCASP_TXBUF_REG, 0); + /* Release TX state machine */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSMRST); + /* Release Frame Sync generator */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); + + /* enable transmit IRQs */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]); } static void davinci_mcasp_start(struct davinci_mcasp *mcasp, int stream) { - u32 reg; - mcasp->streams++; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (mcasp->txnumevt) { /* enable FIFO */ - reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; - mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); - mcasp_set_bits(mcasp, reg, FIFO_ENABLE); - } + if (stream == SNDRV_PCM_STREAM_PLAYBACK) mcasp_start_tx(mcasp); - } else { - if (mcasp->rxnumevt) { /* enable FIFO */ - reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; - mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); - mcasp_set_bits(mcasp, reg, FIFO_ENABLE); - } + else mcasp_start_rx(mcasp); - } } static void mcasp_stop_rx(struct davinci_mcasp *mcasp) { + /* disable IRQ sources */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]); + /* * In synchronous mode stop the TX clocks if no other stream is * running @@ -244,12 +251,22 @@ static void mcasp_stop_rx(struct davinci_mcasp *mcasp) mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, 0); mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); + + if (mcasp->rxnumevt) { /* disable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + } } static void mcasp_stop_tx(struct davinci_mcasp *mcasp) { u32 val = 0; + /* disable IRQ sources */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]); + /* * In synchronous mode keep TX clocks running if the capture stream is * still running. @@ -259,27 +276,92 @@ static void mcasp_stop_tx(struct davinci_mcasp *mcasp) mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, val); mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + + if (mcasp->txnumevt) { /* disable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + } } static void davinci_mcasp_stop(struct davinci_mcasp *mcasp, int stream) { - u32 reg; - mcasp->streams--; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (mcasp->txnumevt) { /* disable FIFO */ - reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; - mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); - } + if (stream == SNDRV_PCM_STREAM_PLAYBACK) mcasp_stop_tx(mcasp); - } else { - if (mcasp->rxnumevt) { /* disable FIFO */ - reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; - mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); - } + else mcasp_stop_rx(mcasp); +} + +static irqreturn_t davinci_mcasp_tx_irq_handler(int irq, void *data) +{ + struct davinci_mcasp *mcasp = (struct davinci_mcasp *)data; + struct snd_pcm_substream *substream; + u32 irq_mask = mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]; + u32 handled_mask = 0; + u32 stat; + + stat = mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG); + if (stat & XUNDRN & irq_mask) { + dev_warn(mcasp->dev, "Transmit buffer underflow\n"); + handled_mask |= XUNDRN; + + substream = mcasp->substreams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream) { + snd_pcm_stream_lock_irq(substream); + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irq(substream); + } } + + if (!handled_mask) + dev_warn(mcasp->dev, "unhandled tx event. txstat: 0x%08x\n", + stat); + + if (stat & XRERR) + handled_mask |= XRERR; + + /* Ack the handled event only */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, handled_mask); + + return IRQ_RETVAL(handled_mask); +} + +static irqreturn_t davinci_mcasp_rx_irq_handler(int irq, void *data) +{ + struct davinci_mcasp *mcasp = (struct davinci_mcasp *)data; + struct snd_pcm_substream *substream; + u32 irq_mask = mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]; + u32 handled_mask = 0; + u32 stat; + + stat = mcasp_get_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG); + if (stat & ROVRN & irq_mask) { + dev_warn(mcasp->dev, "Receive buffer overflow\n"); + handled_mask |= ROVRN; + + substream = mcasp->substreams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream) { + snd_pcm_stream_lock_irq(substream); + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irq(substream); + } + } + + if (!handled_mask) + dev_warn(mcasp->dev, "unhandled rx event. rxstat: 0x%08x\n", + stat); + + if (stat & XRERR) + handled_mask |= XRERR; + + /* Ack the handled event only */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, handled_mask); + + return IRQ_RETVAL(handled_mask); } static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, @@ -500,8 +582,17 @@ static int davinci_config_channel_size(struct davinci_mcasp *mcasp, * both left and right channels), so it has to be divided by number of * tdm-slots (for I2S - divided by 2). */ - if (mcasp->bclk_lrclk_ratio) - word_length = mcasp->bclk_lrclk_ratio / mcasp->tdm_slots; + if (mcasp->bclk_lrclk_ratio) { + u32 slot_length = mcasp->bclk_lrclk_ratio / mcasp->tdm_slots; + + /* + * When we have more bclk then it is needed for the data, we + * need to use the rotation to move the received samples to have + * correct alignment. + */ + rx_rotate = (slot_length - word_length) / 4; + word_length = slot_length; + } /* mapping of the XSSZ bit-field as described in the datasheet */ fmt = (word_length >> 1) - 1; @@ -635,19 +726,29 @@ static int mcasp_common_hw_param(struct davinci_mcasp *mcasp, int stream, return 0; } -static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream) +static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream, + int channels) { int i, active_slots; + int total_slots; + int active_serializers; u32 mask = 0; u32 busel = 0; - if ((mcasp->tdm_slots < 2) || (mcasp->tdm_slots > 32)) { - dev_err(mcasp->dev, "tdm slot %d not supported\n", - mcasp->tdm_slots); - return -EINVAL; - } + total_slots = mcasp->tdm_slots; + + /* + * If more than one serializer is needed, then use them with + * their specified tdm_slots count. Otherwise, one serializer + * can cope with the transaction using as many slots as channels + * in the stream, requires channels symmetry + */ + active_serializers = (channels + total_slots - 1) / total_slots; + if (active_serializers == 1) + active_slots = channels; + else + active_slots = total_slots; - active_slots = (mcasp->tdm_slots > 31) ? 32 : mcasp->tdm_slots; for (i = 0; i < active_slots; i++) mask |= (1 << i); @@ -659,12 +760,12 @@ static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream) mcasp_set_reg(mcasp, DAVINCI_MCASP_TXTDM_REG, mask); mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, busel | TXORD); mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, - FSXMOD(mcasp->tdm_slots), FSXMOD(0x1FF)); + FSXMOD(total_slots), FSXMOD(0x1FF)); mcasp_set_reg(mcasp, DAVINCI_MCASP_RXTDM_REG, mask); mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, busel | RXORD); mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, - FSRMOD(mcasp->tdm_slots), FSRMOD(0x1FF)); + FSRMOD(total_slots), FSRMOD(0x1FF)); return 0; } @@ -778,7 +879,8 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) ret = mcasp_dit_hw_param(mcasp, params_rate(params)); else - ret = mcasp_i2s_hw_param(mcasp, substream->stream); + ret = mcasp_i2s_hw_param(mcasp, substream->stream, + channels); if (ret) return ret; @@ -826,6 +928,9 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, davinci_config_channel_size(mcasp, word_length); + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) + mcasp->channels = channels; + return 0; } @@ -854,7 +959,65 @@ static int davinci_mcasp_trigger(struct snd_pcm_substream *substream, return ret; } +static int davinci_mcasp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + u32 max_channels = 0; + int i, dir; + + mcasp->substreams[substream->stream] = substream; + + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + return 0; + + /* + * Limit the maximum allowed channels for the first stream: + * number of serializers for the direction * tdm slots per serializer + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = TX_MODE; + else + dir = RX_MODE; + + for (i = 0; i < mcasp->num_serializer; i++) { + if (mcasp->serial_dir[i] == dir) + max_channels++; + } + max_channels *= mcasp->tdm_slots; + /* + * If the already active stream has less channels than the calculated + * limnit based on the seirializers * tdm_slots, we need to use that as + * a constraint for the second stream. + * Otherwise (first stream or less allowed channels) we use the + * calculated constraint. + */ + if (mcasp->channels && mcasp->channels < max_channels) + max_channels = mcasp->channels; + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2, max_channels); + return 0; +} + +static void davinci_mcasp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + + mcasp->substreams[substream->stream] = NULL; + + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + return; + + if (!cpu_dai->active) + mcasp->channels = 0; +} + static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { + .startup = davinci_mcasp_startup, + .shutdown = davinci_mcasp_shutdown, .trigger = davinci_mcasp_trigger, .hw_params = davinci_mcasp_hw_params, .set_fmt = davinci_mcasp_set_dai_fmt, @@ -971,6 +1134,7 @@ static struct snd_soc_dai_driver davinci_mcasp_dai[] = { }, .ops = &davinci_mcasp_dai_ops, + .symmetric_samplebits = 1, }, { .name = "davinci-mcasp.1", @@ -1194,6 +1358,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev) struct resource *mem, *ioarea, *res, *dat; struct davinci_mcasp_pdata *pdata; struct davinci_mcasp *mcasp; + char *irq_name; + int irq; int ret; if (!pdev->dev.platform_data && !pdev->dev.of_node) { @@ -1235,6 +1401,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev) ret = pm_runtime_get_sync(&pdev->dev); if (IS_ERR_VALUE(ret)) { dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n"); + pm_runtime_disable(&pdev->dev); return ret; } @@ -1246,7 +1413,21 @@ static int davinci_mcasp_probe(struct platform_device *pdev) } mcasp->op_mode = pdata->op_mode; - mcasp->tdm_slots = pdata->tdm_slots; + /* sanity check for tdm slots parameter */ + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + if (pdata->tdm_slots < 2) { + dev_err(&pdev->dev, "invalid tdm slots: %d\n", + pdata->tdm_slots); + mcasp->tdm_slots = 2; + } else if (pdata->tdm_slots > 32) { + dev_err(&pdev->dev, "invalid tdm slots: %d\n", + pdata->tdm_slots); + mcasp->tdm_slots = 32; + } else { + mcasp->tdm_slots = pdata->tdm_slots; + } + } + mcasp->num_serializer = pdata->num_serializer; #ifdef CONFIG_PM_SLEEP mcasp->context.xrsr_regs = devm_kzalloc(&pdev->dev, @@ -1260,6 +1441,36 @@ static int davinci_mcasp_probe(struct platform_device *pdev) mcasp->dev = &pdev->dev; + irq = platform_get_irq_byname(pdev, "rx"); + if (irq >= 0) { + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_rx\n", + dev_name(&pdev->dev)); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + davinci_mcasp_rx_irq_handler, + IRQF_ONESHOT, irq_name, mcasp); + if (ret) { + dev_err(&pdev->dev, "RX IRQ request failed\n"); + goto err; + } + + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE] = ROVRN; + } + + irq = platform_get_irq_byname(pdev, "tx"); + if (irq >= 0) { + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_tx\n", + dev_name(&pdev->dev)); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + davinci_mcasp_tx_irq_handler, + IRQF_ONESHOT, irq_name, mcasp); + if (ret) { + dev_err(&pdev->dev, "TX IRQ request failed\n"); + goto err; + } + + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK] = XUNDRN; + } + dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); if (dat) mcasp->dat_port = true; diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h index 98fbc451892..79dc511180b 100644 --- a/sound/soc/davinci/davinci-mcasp.h +++ b/sound/soc/davinci/davinci-mcasp.h @@ -253,6 +253,13 @@ #define TXFSRST BIT(12) /* Frame Sync Generator Reset */ /* + * DAVINCI_MCASP_TXSTAT_REG - Transmitter Status Register Bits + * DAVINCI_MCASP_RXSTAT_REG - Receiver Status Register Bits + */ +#define XRERR BIT(8) /* Transmit/Receive error */ +#define XRDATA BIT(5) /* Transmit/Receive data ready */ + +/* * DAVINCI_MCASP_AMUTE_REG - Mute Control Register Bits */ #define MUTENA(val) (val) @@ -279,6 +286,16 @@ #define TXDATADMADIS BIT(0) /* + * DAVINCI_MCASP_EVTCTLR_REG - Receiver Interrupt Control Register Bits + */ +#define ROVRN BIT(0) + +/* + * DAVINCI_MCASP_EVTCTLX_REG - Transmitter Interrupt Control Register Bits + */ +#define XUNDRN BIT(0) + +/* * DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits */ #define FIFO_ENABLE BIT(16) diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index e961388e6e9..08f0229f8d6 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -338,31 +338,34 @@ static int dw_i2s_probe(struct platform_device *pdev) return -EINVAL; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "no i2s resource defined\n"); - return -ENODEV; - } - - if (!devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), pdev->name)) { - dev_err(&pdev->dev, "i2s region already claimed\n"); - return -EBUSY; - } - dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { dev_warn(&pdev->dev, "kzalloc fail\n"); return -ENOMEM; } - dev->i2s_base = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (!dev->i2s_base) { - dev_err(&pdev->dev, "ioremap fail for i2s_region\n"); + dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL); + if (!dw_i2s_dai) { + dev_err(&pdev->dev, "mem allocation failed for dai driver\n"); return -ENOMEM; } + dw_i2s_dai->ops = &dw_i2s_dai_ops; + dw_i2s_dai->suspend = dw_i2s_suspend; + dw_i2s_dai->resume = dw_i2s_resume; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no i2s resource defined\n"); + return -ENODEV; + } + + dev->i2s_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->i2s_base)) { + dev_err(&pdev->dev, "ioremap fail for i2s_region\n"); + return PTR_ERR(dev->i2s_base); + } + cap = pdata->cap; dev->capability = cap; dev->i2s_clk_cfg = pdata->i2s_clk_cfg; @@ -388,13 +391,6 @@ static int dw_i2s_probe(struct platform_device *pdev) if (ret < 0) goto err_clk_put; - dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL); - if (!dw_i2s_dai) { - dev_err(&pdev->dev, "mem allocation failed for dai driver\n"); - ret = -ENOMEM; - goto err_clk_disable; - } - if (cap & DWC_I2S_PLAY) { dev_dbg(&pdev->dev, " designware: play supported\n"); dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; @@ -411,10 +407,6 @@ static int dw_i2s_probe(struct platform_device *pdev) dw_i2s_dai->capture.rates = pdata->snd_rates; } - dw_i2s_dai->ops = &dw_i2s_dai_ops; - dw_i2s_dai->suspend = dw_i2s_suspend; - dw_i2s_dai->resume = dw_i2s_resume; - dev->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, dev); ret = snd_soc_register_component(&pdev->dev, &dw_i2s_component, diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index eb093d5b85c..b175b0145a4 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -105,7 +105,7 @@ static int eukrea_tlv320_probe(struct platform_device *pdev) int ret; int int_port = 0, ext_port; struct device_node *np = pdev->dev.of_node; - struct device_node *ssi_np, *codec_np; + struct device_node *ssi_np = NULL, *codec_np = NULL; eukrea_tlv320.dev = &pdev->dev; if (np) { @@ -217,8 +217,7 @@ static int eukrea_tlv320_probe(struct platform_device *pdev) err: if (ret) dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); - if (np) - of_node_put(ssi_np); + of_node_put(ssi_np); return ret; } diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 007c772f3ce..3f6959c8e2f 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -51,6 +51,7 @@ struct codec_priv { * @sysclk_freq[2]: SYSCLK rates for set_sysclk() * @sysclk_dir[2]: SYSCLK directions for set_sysclk() * @sysclk_id[2]: SYSCLK ids for set_sysclk() + * @slot_width: Slot width of each frame * * Note: [1] for tx and [0] for rx */ @@ -58,6 +59,7 @@ struct cpu_priv { unsigned long sysclk_freq[2]; u32 sysclk_dir[2]; u32 sysclk_id[2]; + u32 slot_width; }; /** @@ -125,7 +127,12 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, priv->sample_rate = params_rate(params); priv->sample_format = params_format(params); - if (priv->card.set_bias_level) + /* + * If codec-dai is DAI Master and all configurations are already in the + * set_bias_level(), bypass the remaining settings in hw_params(). + * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS. + */ + if (priv->card.set_bias_level && priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) return 0; /* Specific configurations of DAIs starts from here */ @@ -137,6 +144,15 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, return ret; } + if (cpu_priv->slot_width) { + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, + cpu_priv->slot_width); + if (ret) { + dev_err(dev, "failed to set TDM slot for cpu dai\n"); + return ret; + } + } + return 0; } @@ -448,6 +464,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq; priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; + priv->cpu_priv.slot_width = 32; priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { priv->codec_priv.mclk_id = SGTL5000_SYSCLK; diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 3b145313f93..9deabdd2b1a 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -684,12 +684,38 @@ static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg) } } +static struct reg_default fsl_asrc_reg[] = { + { REG_ASRCTR, 0x0000 }, { REG_ASRIER, 0x0000 }, + { REG_ASRCNCR, 0x0000 }, { REG_ASRCFG, 0x0000 }, + { REG_ASRCSR, 0x0000 }, { REG_ASRCDR1, 0x0000 }, + { REG_ASRCDR2, 0x0000 }, { REG_ASRSTR, 0x0000 }, + { REG_ASRRA, 0x0000 }, { REG_ASRRB, 0x0000 }, + { REG_ASRRC, 0x0000 }, { REG_ASRPM1, 0x0000 }, + { REG_ASRPM2, 0x0000 }, { REG_ASRPM3, 0x0000 }, + { REG_ASRPM4, 0x0000 }, { REG_ASRPM5, 0x0000 }, + { REG_ASRTFR1, 0x0000 }, { REG_ASRCCR, 0x0000 }, + { REG_ASRDIA, 0x0000 }, { REG_ASRDOA, 0x0000 }, + { REG_ASRDIB, 0x0000 }, { REG_ASRDOB, 0x0000 }, + { REG_ASRDIC, 0x0000 }, { REG_ASRDOC, 0x0000 }, + { REG_ASRIDRHA, 0x0000 }, { REG_ASRIDRLA, 0x0000 }, + { REG_ASRIDRHB, 0x0000 }, { REG_ASRIDRLB, 0x0000 }, + { REG_ASRIDRHC, 0x0000 }, { REG_ASRIDRLC, 0x0000 }, + { REG_ASR76K, 0x0A47 }, { REG_ASR56K, 0x0DF3 }, + { REG_ASRMCRA, 0x0000 }, { REG_ASRFSTA, 0x0000 }, + { REG_ASRMCRB, 0x0000 }, { REG_ASRFSTB, 0x0000 }, + { REG_ASRMCRC, 0x0000 }, { REG_ASRFSTC, 0x0000 }, + { REG_ASRMCR1A, 0x0000 }, { REG_ASRMCR1B, 0x0000 }, + { REG_ASRMCR1C, 0x0000 }, +}; + static const struct regmap_config fsl_asrc_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = REG_ASRMCR1C, + .reg_defaults = fsl_asrc_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_asrc_reg), .readable_reg = fsl_asrc_readable_reg, .volatile_reg = fsl_asrc_volatile_reg, .writeable_reg = fsl_asrc_writeable_reg, @@ -792,7 +818,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) return -ENOMEM; asrc_priv->pdev = pdev; - strcpy(asrc_priv->name, np->name); + strncpy(asrc_priv->name, np->name, sizeof(asrc_priv->name) - 1); /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 8bcdfda09d7..ca319d59f84 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -513,10 +513,15 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, u32 width = snd_pcm_format_width(params_format(params)); u32 channels = params_channels(params); u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + u32 slot_width = width; u32 bclk, mask, val; int ret; - bclk = params_rate(params) * esai_priv->slot_width * esai_priv->slots; + /* Override slot_width if being specifially set */ + if (esai_priv->slot_width) + slot_width = esai_priv->slot_width; + + bclk = params_rate(params) * slot_width * esai_priv->slots; ret = fsl_esai_set_bclk(dai, tx, bclk); if (ret) @@ -538,7 +543,7 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); - val = ESAI_xCR_xSWS(esai_priv->slot_width, width) | (tx ? ESAI_xCR_PADC : 0); + val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0); regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); @@ -734,7 +739,7 @@ static int fsl_esai_probe(struct platform_device *pdev) return -ENOMEM; esai_priv->pdev = pdev; - strcpy(esai_priv->name, np->name); + strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1); /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -780,9 +785,6 @@ static int fsl_esai_probe(struct platform_device *pdev) return ret; } - /* Set a default slot size */ - esai_priv->slot_width = 32; - /* Set a default slot number */ esai_priv->slots = 2; diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index e6955170dc4..b6b0d25f6ac 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -67,8 +67,6 @@ /** * FSLSSI_I2S_FORMATS: audio formats supported by the SSI * - * This driver currently only supports the SSI running in I2S slave mode. - * * The SSI has a limitation in that the samples must be in the same byte * order as the host CPU. This is because when multiple bytes are written * to the STX register, the bytes and bits must be written in the same @@ -1099,7 +1097,7 @@ static const struct snd_soc_component_driver fsl_ssi_component = { }; static struct snd_soc_dai_driver fsl_ssi_ac97_dai = { - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 Playback", .channels_min = 2, @@ -1363,7 +1361,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) return PTR_ERR(ssi_private->regs); } - ssi_private->irq = irq_of_parse_and_map(np, 0); + ssi_private->irq = platform_get_irq(pdev, 0); if (!ssi_private->irq) { dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); return -ENXIO; @@ -1389,7 +1387,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) if (ssi_private->soc->imx) { ret = fsl_ssi_imx_probe(pdev, ssi_private, iomem); if (ret) - goto error_irqmap; + return ret; } ret = snd_soc_register_component(&pdev->dev, &fsl_ssi_component, @@ -1412,7 +1410,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) ret = fsl_ssi_debugfs_create(&ssi_private->dbg_stats, &pdev->dev); if (ret) - goto error_asoc_register; + goto error_irq; /* * If codec-handle property is missing from SSI node, we assume @@ -1460,10 +1458,6 @@ error_asoc_register: if (ssi_private->soc->imx) fsl_ssi_imx_clean(pdev, ssi_private); -error_irqmap: - if (ssi_private->use_dma) - irq_dispose_mapping(ssi_private->irq); - return ret; } @@ -1480,9 +1474,6 @@ static int fsl_ssi_remove(struct platform_device *pdev) if (ssi_private->soc->imx) fsl_ssi_imx_clean(pdev, ssi_private); - if (ssi_private->use_dma) - irq_dispose_mapping(ssi_private->irq); - return 0; } diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c index 1cb22dd034e..1dab963a59f 100644 --- a/sound/soc/fsl/imx-sgtl5000.c +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -175,10 +175,8 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) fail: if (data && !IS_ERR(data->codec_clk)) clk_put(data->codec_clk); - if (ssi_np) - of_node_put(ssi_np); - if (codec_np) - of_node_put(codec_np); + of_node_put(ssi_np); + of_node_put(codec_np); return ret; } diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index e1dc4014360..0c9068ebe1e 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -74,8 +74,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); end: - if (spdif_np) - of_node_put(spdif_np); + of_node_put(spdif_np); return ret; } diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c index ab2fdd76b69..60b0a5b1f1f 100644 --- a/sound/soc/fsl/imx-ssi.c +++ b/sound/soc/fsl/imx-ssi.c @@ -382,7 +382,7 @@ static struct snd_soc_dai_driver imx_ssi_dai = { static struct snd_soc_dai_driver imx_ac97_dai = { .probe = imx_ssi_dai_probe, - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 Playback", .channels_min = 2, diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c index 3a3d17ce6ba..48179ffe154 100644 --- a/sound/soc/fsl/imx-wm8962.c +++ b/sound/soc/fsl/imx-wm8962.c @@ -281,10 +281,8 @@ static int imx_wm8962_probe(struct platform_device *pdev) clk_fail: clk_disable_unprepare(data->codec_clk); fail: - if (ssi_np) - of_node_put(ssi_np); - if (codec_np) - of_node_put(codec_np); + of_node_put(ssi_np); + of_node_put(codec_np); return ret; } diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index f2b5d756b1f..0b82e209b6e 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -327,9 +327,6 @@ static int psc_dma_new(struct snd_soc_pcm_runtime *rtd) goto capture_alloc_err; } - if (rtd->codec->ac97) - rtd->codec->ac97->private_data = psc_dma; - return 0; capture_alloc_err: diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 24eafa2cfbf..c6ed6ba965a 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -237,7 +237,7 @@ static const struct snd_soc_dai_ops psc_ac97_digital_ops = { static struct snd_soc_dai_driver psc_ac97_dai[] = { { .name = "mpc5200-psc-ac97.0", - .ac97_control = 1, + .bus_control = true, .probe = psc_ac97_probe, .playback = { .stream_name = "AC97 Playback", @@ -257,7 +257,7 @@ static struct snd_soc_dai_driver psc_ac97_dai[] = { }, { .name = "mpc5200-psc-ac97.1", - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 SPDIF", .channels_min = 1, @@ -282,7 +282,6 @@ static const struct snd_soc_component_driver psc_ac97_component = { static int psc_ac97_of_probe(struct platform_device *op) { int rc; - struct snd_ac97 ac97; struct mpc52xx_psc __iomem *regs; rc = mpc5200_audio_dma_create(op); @@ -304,7 +303,6 @@ static int psc_ac97_of_probe(struct platform_device *op) psc_dma = dev_get_drvdata(&op->dev); regs = psc_dma->psc_regs; - ac97.private_data = psc_dma; psc_dma->imr = 0; out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index d1b7293c133..ece22d55ba8 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -29,7 +29,9 @@ struct simple_card_data { } *dai_props; unsigned int mclk_fs; int gpio_hp_det; + int gpio_hp_det_invert; int gpio_mic_det; + int gpio_mic_det_invert; struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ }; @@ -148,6 +150,7 @@ static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) simple_card_hp_jack_pins); simple_card_hp_jack_gpio.gpio = priv->gpio_hp_det; + simple_card_hp_jack_gpio.invert = priv->gpio_hp_det_invert; snd_soc_jack_add_gpios(&simple_card_hp_jack, 1, &simple_card_hp_jack_gpio); } @@ -159,6 +162,7 @@ static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) ARRAY_SIZE(simple_card_mic_jack_pins), simple_card_mic_jack_pins); simple_card_mic_jack_gpio.gpio = priv->gpio_mic_det; + simple_card_mic_jack_gpio.invert = priv->gpio_mic_det_invert; snd_soc_jack_add_gpios(&simple_card_mic_jack, 1, &simple_card_mic_jack_gpio); } @@ -226,6 +230,52 @@ asoc_simple_card_sub_parse_of(struct device_node *np, return 0; } +static int asoc_simple_card_parse_daifmt(struct device_node *node, + struct simple_card_data *priv, + struct device_node *codec, + char *prefix, int idx) +{ + struct device *dev = simple_priv_to_dev(priv); + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, prefix, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (strlen(prefix) && !bitclkmaster && !framemaster) { + /* + * No dai-link level and master setting was not found from + * sound node level, revert back to legacy DT parsing and + * take the settings from codec node. + */ + dev_dbg(dev, "Revert to legacy daifmt parsing\n"); + + cpu_dai->fmt = codec_dai->fmt = + snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | + (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); + } else { + if (codec == bitclkmaster) + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + + cpu_dai->fmt = daifmt; + codec_dai->fmt = daifmt; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + return 0; +} + static int asoc_simple_card_dai_link_of(struct device_node *node, struct simple_card_data *priv, int idx, @@ -234,10 +284,8 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, struct device *dev = simple_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); - struct device_node *np = NULL; - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; - unsigned int daifmt; + struct device_node *cpu = NULL; + struct device_node *codec = NULL; char *name; char prop[128]; char *prefix = ""; @@ -247,85 +295,36 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, if (is_top_level_node) prefix = "simple-audio-card,"; - daifmt = snd_soc_of_parse_daifmt(node, prefix, - &bitclkmaster, &framemaster); - daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; - snprintf(prop, sizeof(prop), "%scpu", prefix); - np = of_get_child_by_name(node, prop); - if (!np) { + cpu = of_get_child_by_name(node, prop); + + snprintf(prop, sizeof(prop), "%scodec", prefix); + codec = of_get_child_by_name(node, prop); + + if (!cpu || !codec) { ret = -EINVAL; dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); goto dai_link_of_err; } - ret = asoc_simple_card_sub_parse_of(np, &dai_props->cpu_dai, + ret = asoc_simple_card_parse_daifmt(node, priv, + codec, prefix, idx); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_sub_parse_of(cpu, &dai_props->cpu_dai, &dai_link->cpu_of_node, &dai_link->cpu_dai_name, &cpu_args); if (ret < 0) goto dai_link_of_err; - dai_props->cpu_dai.fmt = daifmt; - switch (((np == bitclkmaster) << 4) | (np == framemaster)) { - case 0x11: - dai_props->cpu_dai.fmt |= SND_SOC_DAIFMT_CBS_CFS; - break; - case 0x10: - dai_props->cpu_dai.fmt |= SND_SOC_DAIFMT_CBS_CFM; - break; - case 0x01: - dai_props->cpu_dai.fmt |= SND_SOC_DAIFMT_CBM_CFS; - break; - default: - dai_props->cpu_dai.fmt |= SND_SOC_DAIFMT_CBM_CFM; - break; - } - - of_node_put(np); - snprintf(prop, sizeof(prop), "%scodec", prefix); - np = of_get_child_by_name(node, prop); - if (!np) { - ret = -EINVAL; - dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); - goto dai_link_of_err; - } - - ret = asoc_simple_card_sub_parse_of(np, &dai_props->codec_dai, + ret = asoc_simple_card_sub_parse_of(codec, &dai_props->codec_dai, &dai_link->codec_of_node, &dai_link->codec_dai_name, NULL); if (ret < 0) goto dai_link_of_err; - if (strlen(prefix) && !bitclkmaster && !framemaster) { - /* - * No DAI link level and master setting was found - * from sound node level, revert back to legacy DT - * parsing and take the settings from codec node. - */ - dev_dbg(dev, "%s: Revert to legacy daifmt parsing\n", - __func__); - dai_props->cpu_dai.fmt = dai_props->codec_dai.fmt = - snd_soc_of_parse_daifmt(np, NULL, NULL, NULL) | - (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); - } else { - dai_props->codec_dai.fmt = daifmt; - switch (((np == bitclkmaster) << 4) | (np == framemaster)) { - case 0x11: - dai_props->codec_dai.fmt |= SND_SOC_DAIFMT_CBM_CFM; - break; - case 0x10: - dai_props->codec_dai.fmt |= SND_SOC_DAIFMT_CBM_CFS; - break; - case 0x01: - dai_props->codec_dai.fmt |= SND_SOC_DAIFMT_CBS_CFM; - break; - default: - dai_props->codec_dai.fmt |= SND_SOC_DAIFMT_CBS_CFS; - break; - } - } - if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) { ret = -EINVAL; goto dai_link_of_err; @@ -368,12 +367,9 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, dai_link->cpu_dai_name = NULL; dai_link_of_err: - if (np) - of_node_put(np); - if (bitclkmaster) - of_node_put(bitclkmaster); - if (framemaster) - of_node_put(framemaster); + of_node_put(cpu); + of_node_put(codec); + return ret; } @@ -381,6 +377,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, struct simple_card_data *priv) { struct device *dev = simple_priv_to_dev(priv); + enum of_gpio_flags flags; u32 val; int ret; @@ -436,13 +433,15 @@ static int asoc_simple_card_parse_of(struct device_node *node, return ret; } - priv->gpio_hp_det = of_get_named_gpio(node, - "simple-audio-card,hp-det-gpio", 0); + priv->gpio_hp_det = of_get_named_gpio_flags(node, + "simple-audio-card,hp-det-gpio", 0, &flags); + priv->gpio_hp_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); if (priv->gpio_hp_det == -EPROBE_DEFER) return -EPROBE_DEFER; - priv->gpio_mic_det = of_get_named_gpio(node, - "simple-audio-card,mic-det-gpio", 0); + priv->gpio_mic_det = of_get_named_gpio_flags(node, + "simple-audio-card,mic-det-gpio", 0, &flags); + priv->gpio_mic_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); if (priv->gpio_mic_det == -EPROBE_DEFER) return -EPROBE_DEFER; @@ -457,18 +456,13 @@ static int asoc_simple_card_unref(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); struct snd_soc_dai_link *dai_link; - struct device_node *np; int num_links; for (num_links = 0, dai_link = card->dai_link; num_links < card->num_links; num_links++, dai_link++) { - np = (struct device_node *) dai_link->cpu_of_node; - if (np) - of_node_put(np); - np = (struct device_node *) dai_link->codec_of_node; - if (np) - of_node_put(np); + of_node_put(dai_link->cpu_of_node); + of_node_put(dai_link->codec_of_node); } return 0; } diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index f5b4a9c79cd..e989ecf046c 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -3,6 +3,7 @@ config SND_MFLD_MACHINE depends on INTEL_SCU_IPC select SND_SOC_SN95031 select SND_SST_MFLD_PLATFORM + select SND_SST_IPC_PCI help This adds support for ASoC machine driver for Intel(R) MID Medfield platform used as alsa device in audio substem in Intel(R) MID devices @@ -12,10 +13,23 @@ config SND_MFLD_MACHINE config SND_SST_MFLD_PLATFORM tristate +config SND_SST_IPC + tristate + +config SND_SST_IPC_PCI + tristate + select SND_SST_IPC + +config SND_SST_IPC_ACPI + tristate + select SND_SST_IPC + depends on ACPI + config SND_SOC_INTEL_SST tristate "ASoC support for Intel(R) Smart Sound Technology" select SND_SOC_INTEL_SST_ACPI if ACPI depends on (X86 || COMPILE_TEST) + depends on DW_DMAC_CORE help This adds support for Intel(R) Smart Sound Technology (SST). Say Y if you have such a device @@ -32,7 +46,8 @@ config SND_SOC_INTEL_BAYTRAIL config SND_SOC_INTEL_HASWELL_MACH tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" - depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C + depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && I2C && \\ + I2C_DESIGNWARE_PLATFORM select SND_SOC_INTEL_HASWELL select SND_SOC_RT5640 help @@ -61,7 +76,8 @@ config SND_SOC_INTEL_BYT_MAX98090_MACH config SND_SOC_INTEL_BROADWELL_MACH tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint" - depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC + depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC && \\ + I2C_DESIGNWARE_PLATFORM select SND_SOC_INTEL_HASWELL select SND_COMPRESS_OFFLOAD select SND_SOC_RT286 @@ -70,3 +86,27 @@ config SND_SOC_INTEL_BROADWELL_MACH Ultrabook platforms. Say Y if you have such a device If unsure select "N". + +config SND_SOC_INTEL_BYTCR_RT5640_MACH + tristate "ASoC Audio DSP Support for MID BYT Platform" + depends on X86 + select SND_SOC_RT5640 + select SND_SST_MFLD_PLATFORM + select SND_SST_IPC_ACPI + help + This adds support for ASoC machine driver for Intel(R) MID Baytrail platform + used as alsa device in audio substem in Intel(R) MID devices + Say Y if you have such a device + If unsure select "N". + +config SND_SOC_INTEL_CHT_BSW_RT5672_MACH + tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec" + depends on X86_INTEL_LPSS + select SND_SOC_RT5670 + select SND_SST_MFLD_PLATFORM + select SND_SST_IPC_ACPI + help + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with RT5672 audio codec. + Say Y if you have such a device + If unsure select "N". diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index f841786dad1..e928ec38530 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -26,8 +26,15 @@ snd-soc-sst-haswell-objs := haswell.o snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o snd-soc-sst-broadwell-objs := broadwell.o +snd-soc-sst-bytcr-dpcm-rt5640-objs := bytcr_dpcm_rt5640.o +snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o +obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-dpcm-rt5640.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o + +# DSP driver +obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/broadwell.c b/sound/soc/intel/broadwell.c index 0e550f14028..c256764e3c4 100644 --- a/sound/soc/intel/broadwell.c +++ b/sound/soc/intel/broadwell.c @@ -19,6 +19,7 @@ #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> +#include <sound/jack.h> #include <sound/pcm_params.h> #include "sst-dsp.h" @@ -26,8 +27,26 @@ #include "../codecs/rt286.h" +static struct snd_soc_jack broadwell_headset; +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin broadwell_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new broadwell_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + static const struct snd_soc_dapm_widget broadwell_widgets[] = { - SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL), SND_SOC_DAPM_MIC("DMIC1", NULL), @@ -42,7 +61,7 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = { {"Speaker", NULL, "SPOL"}, /* HP jack connectors - unknown if we have jack deteck */ - {"Headphones", NULL, "HPO Pin"}, + {"Headphone Jack", NULL, "HPO Pin"}, /* other jacks */ {"MIC1", NULL, "Mic Jack"}, @@ -57,6 +76,27 @@ static const struct snd_soc_dapm_route broadwell_rt286_map[] = { {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, }; +static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + ret = snd_soc_jack_new(codec, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset); + + if (ret) + return ret; + + ret = snd_soc_jack_add_pins(&broadwell_headset, + ARRAY_SIZE(broadwell_headset_pins), + broadwell_headset_pins); + if (ret) + return ret; + + rt286_mic_detect(codec, &broadwell_headset); + return 0; +} + + static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -116,7 +156,7 @@ static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) } /* always connected - check HP for jack detect */ - snd_soc_dapm_enable_pin(dapm, "Headphones"); + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); snd_soc_dapm_enable_pin(dapm, "Speaker"); snd_soc_dapm_enable_pin(dapm, "Mic Jack"); snd_soc_dapm_enable_pin(dapm, "Line Jack"); @@ -131,7 +171,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { /* Front End DAI links */ { .name = "System PCM", - .stream_name = "System Playback", + .stream_name = "System Playback/Capture", .cpu_dai_name = "System Pin", .platform_name = "haswell-pcm-audio", .dynamic = 1, @@ -140,6 +180,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { .init = broadwell_rtd_init, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_playback = 1, + .dpcm_capture = 1, }, { .name = "Offload0", @@ -174,18 +215,6 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_capture = 1, }, - { - .name = "Capture PCM", - .stream_name = "Capture", - .cpu_dai_name = "Capture Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - }, - /* Back End DAI links */ { /* SSP0 - Codec */ @@ -196,6 +225,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { .no_pcm = 1, .codec_name = "i2c-INT343A:00", .codec_dai_name = "rt286-aif1", + .init = broadwell_rt286_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, .ignore_suspend = 1, @@ -213,6 +243,8 @@ static struct snd_soc_card broadwell_rt286 = { .owner = THIS_MODULE, .dai_link = broadwell_rt286_dais, .num_links = ARRAY_SIZE(broadwell_rt286_dais), + .controls = broadwell_controls, + .num_controls = ARRAY_SIZE(broadwell_controls), .dapm_widgets = broadwell_widgets, .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), .dapm_routes = broadwell_rt286_map, diff --git a/sound/soc/intel/bytcr_dpcm_rt5640.c b/sound/soc/intel/bytcr_dpcm_rt5640.c new file mode 100644 index 00000000000..f5d0fc1ab10 --- /dev/null +++ b/sound/soc/intel/bytcr_dpcm_rt5640.c @@ -0,0 +1,230 @@ +/* + * byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../codecs/rt5640.h" +#include "sst-atom-controls.h" + +static const struct snd_soc_dapm_widget byt_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route byt_audio_map[] = { + {"IN2P", NULL, "Headset Mic"}, + {"IN2N", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS1"}, + {"IN1P", NULL, "MICBIAS1"}, + {"LDO2", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOLP"}, + {"Ext Spk", NULL, "SPOLN"}, + {"Ext Spk", NULL, "SPORP"}, + {"Ext Spk", NULL, "SPORN"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_kcontrol_new byt_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int byt_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + snd_soc_dai_set_bclk_ratio(codec_dai, 50); + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec clock %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, + params_rate(params) * 50, + params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_pcm_stream byt_dai_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static int byt_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - + SNDRV_PCM_HW_PARAM_FIRST_MASK], + SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int byt_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops byt_aif1_ops = { + .startup = byt_aif1_startup, +}; + +static struct snd_soc_ops byt_be_ssp2_ops = { + .hw_params = byt_aif1_hw_params, +}; + +static struct snd_soc_dai_link byt_dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Baytrail Audio Port", + .stream_name = "Baytrail Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Baytrail Compressed Port", + .stream_name = "Baytrail Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* back ends */ + { + .name = "SSP2-Codec", + .be_id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5640-aif1", + .codec_name = "i2c-10EC5640:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = byt_codec_fixup, + .ignore_suspend = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_byt = { + .name = "baytrailcraudio", + .dai_link = byt_dailink, + .num_links = ARRAY_SIZE(byt_dailink), + .dapm_widgets = byt_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_dapm_widgets), + .dapm_routes = byt_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_audio_map), + .controls = byt_mc_controls, + .num_controls = ARRAY_SIZE(byt_mc_controls), +}; + +static int snd_byt_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + snd_soc_card_byt.dev = &pdev->dev; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_byt); + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_byt); + return ret_val; +} + +static struct platform_driver snd_byt_mc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "bytt100_rt5640", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_byt_mc_probe, +}; + +module_platform_driver(snd_byt_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty <subhransu.s.prusty@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytrt5640-audio"); diff --git a/sound/soc/intel/cht_bsw_rt5672.c b/sound/soc/intel/cht_bsw_rt5672.c new file mode 100644 index 00000000000..9b8b561171b --- /dev/null +++ b/sound/soc/intel/cht_bsw_rt5672.c @@ -0,0 +1,285 @@ +/* + * cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms + * Cherrytrail and Braswell, with RT5672 codec. + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty <subhransu.s.prusty@intel.com> + * Mengdong Lin <mengdong.lin@intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../codecs/rt5670.h" +#include "sst-atom-controls.h" + +/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5670-aif1" + +static inline struct snd_soc_dai *cht_get_codec_dai(struct snd_soc_card *card) +{ + int i; + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd; + + rtd = card->rtd + i; + if (!strncmp(rtd->codec_dai->name, CHT_CODEC_DAI, + strlen(CHT_CODEC_DAI))) + return rtd->codec_dai; + } + return NULL; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + + codec_dai = cht_get_codec_dai(card); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (!SND_SOC_DAPM_EVENT_OFF(event)) + return 0; + + /* Set codec sysclk source to its internal clock because codec PLL will + * be off when idle and MCLK will also be off by ACPI when codec is + * runtime suspended. Codec needs clock for jack detection and button + * press. + */ + snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, + 0, SND_SOC_CLOCK_IN); + + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOLP"}, + {"Ext Spk", NULL, "SPOLN"}, + {"Ext Spk", NULL, "SPORP"}, + {"Ext Spk", NULL, "SPORN"}, + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + return 0; +} + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - + SNDRV_PCM_HW_PARAM_FIRST_MASK], + SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .be_id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5670-aif1", + .codec_name = "i2c-10EC5670:00", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .ignore_suspend = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "cherrytrailcraudio", + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "cht-bsw-rt5672", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-rt5672"); diff --git a/sound/soc/intel/haswell.c b/sound/soc/intel/haswell.c index 3981982674a..cb8a482b5f3 100644 --- a/sound/soc/intel/haswell.c +++ b/sound/soc/intel/haswell.c @@ -109,7 +109,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { /* Front End DAI links */ { .name = "System", - .stream_name = "System Playback", + .stream_name = "System Playback/Capture", .cpu_dai_name = "System Pin", .platform_name = "haswell-pcm-audio", .dynamic = 1, @@ -118,6 +118,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { .init = haswell_rtd_init, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_playback = 1, + .dpcm_capture = 1, }, { .name = "Offload0", @@ -152,17 +153,6 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_capture = 1, }, - { - .name = "Capture", - .stream_name = "Capture", - .cpu_dai_name = "Capture Pin", - .platform_name = "haswell-pcm-audio", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - }, /* Back End DAI links */ { diff --git a/sound/soc/intel/sst-atom-controls.c b/sound/soc/intel/sst-atom-controls.c index 7104a34181a..90aa5c0476f 100644 --- a/sound/soc/intel/sst-atom-controls.c +++ b/sound/soc/intel/sst-atom-controls.c @@ -15,6 +15,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * + * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active + * we forward the settings and parameters, rest we keep the values in + * driver and forward when DAPM enables them * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -81,6 +84,183 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, return ret; } +/** + * tx map value is a bitfield where each bit represents a FW channel + * + * 3 2 1 0 # 0 = codec0, 1 = codec1 + * RLRLRLRL # 3, 4 = reserved + * + * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R + */ +static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ +}; + +/** + * rx map value is a bitfield where each bit represents a slot + * + * 76543210 # 0 = slot 0, 1 = slot 1 + * + * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2 + */ +static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ +}; + +/** + * NOTE: this is invoked with lock held + */ +static int sst_send_slot_map(struct sst_data *drv) +{ + struct sst_param_sba_ssp_slot_map cmd; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.command_id = SBA_SET_SSP_SLOT_MAP; + cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map) + - sizeof(struct sst_dsp_header); + + cmd.param_id = SBA_SET_SSP_SLOT_MAP; + cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map) + + sizeof(cmd.ssp_index); + cmd.ssp_index = SSP_CODEC; + + memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map)); + memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map)); + + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +int sst_slot_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_enum *e = (struct sst_enum *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = e->max; + + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; + strcpy(uinfo->value.enumerated.name, + e->texts[uinfo->value.enumerated.item]); + + return 0; +} + +/** + * sst_slot_get - get the status of the interleaver/deinterleaver control + * + * Searches the map where the control status is stored, and gets the + * channel/slot which is currently set for this enumerated control. Since it is + * an enumerated control, there is only one possible value. + */ +static int sst_slot_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + mutex_lock(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) + if (map[mux - 1] & val) + break; + + ucontrol->value.enumerated.item[0] = mux; + mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], mux ? map[mux - 1] : -1); + return 0; +} + +/* sst_check_and_send_slot_map - helper for checking power state and sending + * slot map cmd + * + * called with lock held + */ +static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + int ret = 0; + + if (e->w && e->w->power) + ret = sst_send_slot_map(drv); + else + dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n", + kcontrol->id.name); + return ret; +} + +/** + * sst_slot_put - set the status of interleaver/deinterleaver control + * + * (de)interleaver controls are defined in opposite sense to be user-friendly + * + * Instead of the enum value being the value written to the register, it is the + * register address; and the kcontrol number (register num) is the value written + * to the register. This is so that there can be only one value for each + * slot/channel since there is only one control for each slot/channel. + * + * This means that whenever an enum is set, we need to clear the bit + * for that kcontrol_no for all the interleaver OR deinterleaver registers + */ +static int sst_slot_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_enum *e = (void *)kcontrol->private_value; + int i, ret = 0; + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int slot_channel_no; + unsigned int val, mux; + u8 *map; + + map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + val = 1 << ctl_no; + mux = ucontrol->value.enumerated.item[0]; + if (mux > e->max - 1) + return -EINVAL; + + mutex_lock(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; + + if (mux == 0) { + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; + } + + /* offset by one to take "None" into account */ + slot_channel_no = mux - 1; + map[slot_channel_no] |= val; + + dev_dbg(c->dev, "%s %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], map[slot_channel_no]); + + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; +} + static int sst_send_algo_cmd(struct sst_data *drv, struct sst_algo_control *bc) { @@ -104,6 +284,34 @@ static int sst_send_algo_cmd(struct sst_data *drv, return ret; } +/** + * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe + * + * The algos which are in each pipeline are sent to the firmware one by one + * + * Called with lock held + */ +static int sst_find_and_send_pipe_algo(struct sst_data *drv, + const char *pipe, struct sst_ids *ids) +{ + int ret = 0; + struct sst_algo_control *bc; + struct sst_module *algo = NULL; + + dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); + + list_for_each_entry(algo, &ids->algo_list, node) { + bc = (void *)algo->kctl->private_value; + + dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n", + algo->kctl->id.name, pipe); + ret = sst_send_algo_cmd(drv, bc); + if (ret) + return ret; + } + return ret; +} + static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -162,6 +370,743 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, return ret; } +static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = mc->stereo ? 2 : 1; + uinfo->value.integer.min = mc->min; + uinfo->value.integer.max = mc->max; + + return 0; +} + +/** + * sst_send_gain_cmd - send the gain algorithm IPC to the FW + * @gv: the stored value of gain (also contains rampduration) + * @mute: flag that indicates whether this was called from the + * digital_mute callback or directly. If called from the + * digital_mute callback, module will be muted/unmuted based on this + * flag. The flag is always 0 if called directly. + * + * Called with sst_data.lock held + * + * The user-set gain value is sent only if the user-controllable 'mute' control + * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is + * sent. + */ +static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv, + u16 task_id, u16 loc_id, u16 module_id, int mute) +{ + struct sst_cmd_set_gain_dual cmd; + + dev_dbg(&drv->pdev->dev, "Enter\n"); + + cmd.header.command_id = MMX_SET_GAIN; + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.gain_cell_num = 1; + + if (mute || gv->mute) { + cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE; + cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE; + } else { + cmd.cell_gains[0].cell_gain_left = gv->l_gain; + cmd.cell_gains[0].cell_gain_right = gv->r_gain; + } + + SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest, + loc_id, module_id); + cmd.cell_gains[0].gain_time_constant = gv->ramp_duration; + + cmd.header.length = sizeof(struct sst_cmd_set_gain_dual) + - sizeof(struct sst_dsp_header); + + /* we are with lock held, so call the unlocked api to send */ + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + switch (mc->type) { + case SST_GAIN_TLV: + ucontrol->value.integer.value[0] = gv->l_gain; + ucontrol->value.integer.value[1] = gv->r_gain; + break; + + case SST_GAIN_MUTE: + ucontrol->value.integer.value[0] = gv->mute ? 1 : 0; + break; + + case SST_GAIN_RAMP_DURATION: + ucontrol->value.integer.value[0] = gv->ramp_duration; + break; + + default: + dev_err(component->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + return 0; +} + +static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + mutex_lock(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: + gv->l_gain = ucontrol->value.integer.value[0]; + gv->r_gain = ucontrol->value.integer.value[1]; + dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n", + mc->pname, gv->l_gain, gv->r_gain); + break; + + case SST_GAIN_MUTE: + gv->mute = !!ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute); + break; + + case SST_GAIN_RAMP_DURATION: + gv->ramp_duration = ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n", + mc->pname, gv->ramp_duration); + break; + + default: + mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); + mutex_unlock(&drv->lock); + + return ret; +} + +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute); + +static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + mutex_lock(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); + mutex_unlock(&drv->lock); + + return 0; +} + +static int sst_generic_modules_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + return sst_send_pipe_module_params(w, k); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0); + +/* Look up table to convert MIXER SW bit regs to SWM inputs */ +static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { + [SST_IP_CODEC0] = SST_SWM_IN_CODEC0, + [SST_IP_CODEC1] = SST_SWM_IN_CODEC1, + [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP, + [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1, + [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2, + [SST_IP_PCM0] = SST_SWM_IN_PCM0, + [SST_IP_PCM1] = SST_SWM_IN_PCM1, + [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0, + [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1, + [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2, + [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3, +}; + +/** + * fill_swm_input - fill in the SWM input ids given the register + * + * The register value is a bit-field inicated which mixer inputs are ON. Use the + * lookup table to get the input-id and fill it in the structure. + */ +static int fill_swm_input(struct snd_soc_component *cmpnt, + struct swm_input_ids *swm_input, unsigned int reg) +{ + uint i, is_set, nb_inputs = 0; + u16 input_loc_id; + + dev_dbg(cmpnt->dev, "reg: %#x\n", reg); + for (i = 0; i < SST_SWM_INPUT_COUNT; i++) { + is_set = reg & BIT(i); + if (!is_set) + continue; + + input_loc_id = swm_mixer_input_ids[i]; + SST_FILL_DESTINATION(2, swm_input->input_id, + input_loc_id, SST_DEFAULT_MODULE_ID); + nb_inputs++; + swm_input++; + dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n", + input_loc_id, nb_inputs); + + if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) { + dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached"); + break; + } + } + return nb_inputs; +} + + +/** + * called with lock held + */ +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute) +{ + int ret = 0; + struct sst_gain_mixer_control *mc; + struct sst_gain_value *gv; + struct sst_module *gain = NULL; + + list_for_each_entry(gain, &ids->gain_list, node) { + struct snd_kcontrol *kctl = gain->kctl; + + dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name); + mc = (void *)kctl->private_value; + gv = mc->gain_val; + + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, mute); + if (ret) + return ret; + } + return ret; +} + +static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sst_cmd_set_swm cmd; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_ids *ids = w->priv; + bool set_mixer = false; + struct soc_mixer_control *mc; + int val = 0; + int i = 0; + + dev_dbg(cmpnt->dev, "widget = %s\n", w->name); + /* + * Identify which mixer input is on and send the bitmap of the + * inputs as an IPC to the DSP. + */ + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value; + val |= 1 << mc->shift; + } + } + dev_dbg(cmpnt->dev, "val = %#x\n", val); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + set_mixer = true; + break; + case SND_SOC_DAPM_POST_REG: + if (w->power) + set_mixer = true; + break; + default: + set_mixer = false; + } + + if (set_mixer == false) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event) || + event == SND_SOC_DAPM_POST_REG) + cmd.switch_state = SST_SWM_ON; + else + cmd.switch_state = SST_SWM_OFF; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + /* MMX_SET_SWM == SBA_SET_SWM */ + cmd.header.command_id = SBA_SET_SWM; + + SST_FILL_DESTINATION(2, cmd.output_id, + ids->location_id, SST_DEFAULT_MODULE_ID); + cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val); + cmd.header.length = offsetof(struct sst_cmd_set_swm, input) + - sizeof(struct sst_dsp_header) + + (cmd.nb_inputs * sizeof(cmd.input[0])); + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +/* SBA mixers - 16 inputs */ +#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \ + SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \ + SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \ + SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \ + SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \ + SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \ + SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \ + } + +#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \ + { mix_name, "codec_in0 Switch", "codec_in0" }, \ + { mix_name, "codec_in1 Switch", "codec_in1" }, \ + { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \ + { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \ + { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \ + { mix_name, "pcm0_in Switch", "pcm0_in" }, \ + { mix_name, "pcm1_in Switch", "pcm1_in" } + +#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \ + SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \ + SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \ + SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \ + } + +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls); +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls); + +/* 18 SBA mixers */ +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); + +/* + * sst_handle_vb_timer - Start/Stop the DSP scheduler + * + * The DSP expects first cmd to be SBA_VB_START, so at first startup send + * that. + * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that. + * + * Do refcount internally so that we send command only at first start + * and last end. Since SST driver does its own ref count, invoke sst's + * power ops always! + */ +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) +{ + int ret = 0; + struct sst_cmd_generic cmd; + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + static int timer_usage; + + if (enable) + cmd.header.command_id = SBA_VB_START; + else + cmd.header.command_id = SBA_IDLE; + dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage); + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.length = 0; + + if (enable) { + ret = sst->ops->power(sst->dev, true); + if (ret < 0) + return ret; + } + + mutex_lock(&drv->lock); + if (enable) + timer_usage++; + else + timer_usage--; + + /* + * Send the command only if this call is the first enable or last + * disable + */ + if ((enable && (timer_usage == 1)) || + (!enable && (timer_usage == 0))) { + ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret && enable) { + timer_usage--; + enable = false; + } + } + mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); + return ret; +} + +/** + * sst_ssp_config - contains SSP configuration for media UC + */ +static const struct sst_ssp_config sst_ssp_configs = { + .ssp_id = SSP_CODEC, + .bits_per_slot = 24, + .slots = 4, + .ssp_mode = SSP_MODE_MASTER, + .pcm_mode = SSP_PCM_MODE_NETWORK, + .duplex = SSP_DUPLEX, + .ssp_protocol = SSP_MODE_PCM, + .fs_width = 1, + .fs_frequency = SSP_FS_48_KHZ, + .active_slot_map = 0xF, + .start_delay = 0, +}; + +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable) +{ + struct sst_cmd_sba_hw_set_ssp cmd; + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + const struct sst_ssp_config *config; + + dev_info(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id); + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.command_id = SBA_HW_SET_SSP; + cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp) + - sizeof(struct sst_dsp_header); + + config = &sst_ssp_configs; + dev_dbg(dai->dev, "ssp_id: %u\n", config->ssp_id); + + if (enable) + cmd.switch_state = SST_SWITCH_ON; + else + cmd.switch_state = SST_SWITCH_OFF; + + cmd.selection = config->ssp_id; + cmd.nb_bits_per_slots = config->bits_per_slot; + cmd.nb_slots = config->slots; + cmd.mode = config->ssp_mode | (config->pcm_mode << 1); + cmd.duplex = config->duplex; + cmd.active_tx_slot_map = config->active_slot_map; + cmd.active_rx_slot_map = config->active_slot_map; + cmd.frame_sync_frequency = config->fs_frequency; + cmd.frame_sync_polarity = SSP_FS_ACTIVE_HIGH; + cmd.data_polarity = 1; + cmd.frame_sync_width = config->fs_width; + cmd.ssp_protocol = config->ssp_protocol; + cmd.start_delay = config->start_delay; + cmd.reserved1 = cmd.reserved2 = 0xFF; + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_set_be_modules(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + + dev_dbg(c->dev, "Enter: widget=%s\n", w->name); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = sst_send_slot_map(drv); + if (ret) + return ret; + ret = sst_send_pipe_module_params(w, k); + } + return ret; +} + +static int sst_set_media_path(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_set_media_path cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "widget=%s\n", w->name); + dev_dbg(c->dev, "task=%u, location=%#x\n", + ids->task_id, ids->location_id); + + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_PATH_ON; + else + cmd.switch_state = SST_PATH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */ + cmd.header.command_id = MMX_SET_MEDIA_PATH; + cmd.header.length = sizeof(struct sst_cmd_set_media_path) + - sizeof(struct sst_dsp_header); + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static int sst_set_media_loop(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_sba_set_media_loop_map cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "Enter:widget=%s\n", w->name); + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_SWITCH_ON; + else + cmd.switch_state = SST_SWITCH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP; + cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map) + - sizeof(struct sst_dsp_header); + cmd.param.part.cfg.rate = 2; /* 48khz */ + + cmd.param.part.cfg.format = ids->format; /* stereo/Mono */ + cmd.param.part.cfg.s_length = 1; /* 24bit left justified */ + cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */ + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static const struct snd_soc_dapm_widget sst_dapm_widgets[] = { + SST_AIF_IN("codec_in0", sst_set_be_modules), + SST_AIF_IN("codec_in1", sst_set_be_modules), + SST_AIF_OUT("codec_out0", sst_set_be_modules), + SST_AIF_OUT("codec_out1", sst_set_be_modules), + + /* Media Paths */ + /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */ + SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event), + SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL), + SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path), + SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL), + SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path), + SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path), + + /* SBA PCM Paths */ + SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path), + SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path), + SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path), + + /* SBA Loops */ + SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL), + SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL), + SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL), + SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_MONO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_MONO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop), + + /* Media Mixers */ + SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0, + sst_mix_media0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1, + sst_mix_media1_controls, sst_swm_mixer_event), + + /* SBA PCM mixers */ + SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0, + sst_mix_pcm0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1, + sst_mix_pcm1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2, + sst_mix_pcm2_controls, sst_swm_mixer_event), + + /* SBA Loop mixers */ + SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, + sst_mix_sprot_l0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, + sst_mix_media_l1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, + sst_mix_media_l2_controls, sst_swm_mixer_event), + + /* SBA Backend mixers */ + SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0, + sst_mix_codec0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1, + sst_mix_codec1_controls, sst_swm_mixer_event), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"media0_in", NULL, "Compress Playback"}, + {"media1_in", NULL, "Headset Playback"}, + {"media2_in", NULL, "pcm0_out"}, + + {"media0_out mix 0", "media0_in Switch", "media0_in"}, + {"media0_out mix 0", "media1_in Switch", "media1_in"}, + {"media0_out mix 0", "media2_in Switch", "media2_in"}, + {"media0_out mix 0", "media3_in Switch", "media3_in"}, + {"media1_out mix 0", "media0_in Switch", "media0_in"}, + {"media1_out mix 0", "media1_in Switch", "media1_in"}, + {"media1_out mix 0", "media2_in Switch", "media2_in"}, + {"media1_out mix 0", "media3_in Switch", "media3_in"}, + + {"media0_out", NULL, "media0_out mix 0"}, + {"media1_out", NULL, "media1_out mix 0"}, + {"pcm0_in", NULL, "media0_out"}, + {"pcm1_in", NULL, "media1_out"}, + + {"Headset Capture", NULL, "pcm1_out"}, + {"Headset Capture", NULL, "pcm2_out"}, + {"pcm0_out", NULL, "pcm0_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"), + {"pcm1_out", NULL, "pcm1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"), + {"pcm2_out", NULL, "pcm2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"), + + {"media_loop1_in", NULL, "media_loop1_out"}, + {"media_loop1_out", NULL, "media_loop1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"), + {"media_loop2_in", NULL, "media_loop2_out"}, + {"media_loop2_out", NULL, "media_loop2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"), + {"sprot_loop_in", NULL, "sprot_loop_out"}, + {"sprot_loop_out", NULL, "sprot_loop_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"), + + {"codec_out0", NULL, "codec_out0 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"), + {"codec_out1", NULL, "codec_out1 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"), + +}; +static const char * const slot_names[] = { + "none", + "slot 0", "slot 1", "slot 2", "slot 3", + "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */ +}; + +static const char * const channel_names[] = { + "none", + "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1", + "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */ +}; + +#define SST_INTERLEAVER(xpname, slot_name, slotno) \ + SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \ + channel_names, sst_slot_get, sst_slot_put) + +#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \ + SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \ + slot_names, sst_slot_get, sst_slot_put) + +static const struct snd_kcontrol_new sst_slot_controls[] = { + SST_INTERLEAVER("codec_out", "slot 0", 0), + SST_INTERLEAVER("codec_out", "slot 1", 1), + SST_INTERLEAVER("codec_out", "slot 2", 2), + SST_INTERLEAVER("codec_out", "slot 3", 3), + SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0), + SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1), + SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2), + SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3), +}; + +/* Gain helper with min/max set */ +#define SST_GAIN(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_VOLUME, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +static struct sst_gain_value sst_gains[]; + +static const struct snd_kcontrol_new sst_gain_controls[] = { + SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]), + SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]), + SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]), + SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]), + + SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]), + SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]), + SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]), + SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]), + + SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]), + SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]), + SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]), + SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]), + SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]), + SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]), + SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]), + SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]), +}; + +#define SST_GAIN_NUM_CONTROLS 3 +/* the SST_GAIN macro above will create three alsa controls for each + * instance invoked, gain, mute and ramp duration, which use the same gain + * cell sst_gain to keep track of data + * To calculate number of gain cell instances we need to device by 3 in + * below caulcation for gain cell memory. + * This gets rid of static number and issues while adding new controls + */ +static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS]; + static const struct snd_kcontrol_new sst_algo_controls[] = { SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), @@ -198,21 +1143,280 @@ static int sst_algo_control_init(struct device *dev) return 0; } -int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) +static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) +{ + switch (w->id) { + case snd_soc_dapm_pga: + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + case snd_soc_dapm_input: + case snd_soc_dapm_output: + case snd_soc_dapm_mixer: + return true; + default: + return false; + } +} + +/** + * sst_send_pipe_gains - send gains for the front-end DAIs + * + * The gains in the pipes connected to the front-ends are muted/unmuted + * automatically via the digital_mute() DAPM callback. This function sends the + * gains for the front-end pipes. + */ +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) +{ + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p = NULL; + + dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->playback_widget->name); + w = dai->playback_widget; + list_for_each_entry(p, &w->sinks, list_source) { + if (p->connected && !p->connected(w, p->sink)) + continue; + + if (p->connect && p->sink->power && + is_sst_dapm_widget(p->sink)) { + struct sst_ids *ids = p->sink->priv; + + dev_dbg(dai->dev, "send gains for widget=%s\n", + p->sink->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } else { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->capture_widget->name); + w = dai->capture_widget; + list_for_each_entry(p, &w->sources, list_sink) { + if (p->connected && !p->connected(w, p->sink)) + continue; + + if (p->connect && p->source->power && + is_sst_dapm_widget(p->source)) { + struct sst_ids *ids = p->source->priv; + + dev_dbg(dai->dev, "send gain for widget=%s\n", + p->source->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } + return 0; +} + +/** + * sst_fill_module_list - populate the list of modules/gains for a pipe + * + * + * Fills the widget pointer in the kcontrol private data, and also fills the + * kcontrol pointer in the widget private data. + * + * Widget pointer is used to send the algo/gain in the .put() handler if the + * widget is powerd on. + * + * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF + * event handler. Each widget (pipe) has multiple algos stored in the algo_list. + */ +static int sst_fill_module_list(struct snd_kcontrol *kctl, + struct snd_soc_dapm_widget *w, int type) { + struct sst_module *module = NULL; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_ids *ids = w->priv; int ret = 0; + + module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL); + if (!module) + return -ENOMEM; + + if (type == SST_MODULE_GAIN) { + struct sst_gain_mixer_control *mc = (void *)kctl->private_value; + + mc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->gain_list); + } else if (type == SST_MODULE_ALGO) { + struct sst_algo_control *bc = (void *)kctl->private_value; + + bc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->algo_list); + } else { + dev_err(c->dev, "invoked for unknown type %d module %s", + type, kctl->id.name); + ret = -EINVAL; + } + + return ret; +} + +/** + * sst_fill_widget_module_info - fill list of gains/algos for the pipe + * @widget: pipe modelled as a DAPM widget + * + * Fill the list of gains/algos for the widget by looking at all the card + * controls and comparing the name of the widget with the first part of control + * name. First part of control name contains the pipe name (widget name). + */ +static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, + struct snd_soc_platform *platform) +{ + struct snd_kcontrol *kctl; + int index, ret = 0; + struct snd_card *card = platform->component.card->snd_card; + char *idx; + + down_read(&card->controls_rwsem); + + list_for_each_entry(kctl, &card->controls, list) { + idx = strstr(kctl->id.name, " "); + if (idx == NULL) + continue; + index = strlen(kctl->id.name) - strlen(idx); + + if (strstr(kctl->id.name, "Volume") && + !strncmp(kctl->id.name, w->name, index)) + ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN); + + else if (strstr(kctl->id.name, "params") && + !strncmp(kctl->id.name, w->name, index)) + ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO); + + else if (strstr(kctl->id.name, "Switch") && + !strncmp(kctl->id.name, w->name, index) && + strstr(kctl->id.name, "Gain")) { + struct sst_gain_mixer_control *mc = + (void *)kctl->private_value; + + mc->w = w; + + } else if (strstr(kctl->id.name, "interleaver") && + !strncmp(kctl->id.name, w->name, index)) { + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + + } else if (strstr(kctl->id.name, "deinterleaver") && + !strncmp(kctl->id.name, w->name, index)) { + + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + } + + if (ret < 0) { + up_read(&card->controls_rwsem); + return ret; + } + } + + up_read(&card->controls_rwsem); + return 0; +} + +/** + * sst_fill_linked_widgets - fill the parent pointer for the linked widget + */ +static void sst_fill_linked_widgets(struct snd_soc_platform *platform, + struct sst_ids *ids) +{ + struct snd_soc_dapm_widget *w; + unsigned int len = strlen(ids->parent_wname); + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (!strncmp(ids->parent_wname, w->name, len)) { + ids->parent_w = w; + break; + } + } +} + +/** + * sst_map_modules_to_pipe - fill algo/gains list for all pipes + */ +static int sst_map_modules_to_pipe(struct snd_soc_platform *platform) +{ + struct snd_soc_dapm_widget *w; + int ret = 0; + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (is_sst_dapm_widget(w) && (w->priv)) { + struct sst_ids *ids = w->priv; + + dev_dbg(platform->dev, "widget type=%d name=%s\n", + w->id, w->name); + INIT_LIST_HEAD(&ids->algo_list); + INIT_LIST_HEAD(&ids->gain_list); + ret = sst_fill_widget_module_info(w, platform); + + if (ret < 0) + return ret; + + /* fill linked widgets */ + if (ids->parent_wname != NULL) + sst_fill_linked_widgets(platform, ids); + } + } + return 0; +} + +int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform) +{ + int i, ret = 0; + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(&platform->component); struct sst_data *drv = snd_soc_platform_get_drvdata(platform); + unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3; drv->byte_stream = devm_kzalloc(platform->dev, SST_MAX_BIN_BYTES, GFP_KERNEL); if (!drv->byte_stream) return -ENOMEM; - /*Initialize algo control params*/ + snd_soc_dapm_new_controls(dapm, sst_dapm_widgets, + ARRAY_SIZE(sst_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon, + ARRAY_SIZE(intercon)); + snd_soc_dapm_new_widgets(dapm->card); + + for (i = 0; i < gains; i++) { + sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT; + sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT; + } + + ret = snd_soc_add_platform_controls(platform, sst_gain_controls, + ARRAY_SIZE(sst_gain_controls)); + if (ret) + return ret; + + /* Initialize algo control params */ ret = sst_algo_control_init(platform->dev); if (ret) return ret; ret = snd_soc_add_platform_controls(platform, sst_algo_controls, ARRAY_SIZE(sst_algo_controls)); + if (ret) + return ret; + + ret = snd_soc_add_platform_controls(platform, sst_slot_controls, + ARRAY_SIZE(sst_slot_controls)); + if (ret) + return ret; + + ret = sst_map_modules_to_pipe(platform); + return ret; } diff --git a/sound/soc/intel/sst-atom-controls.h b/sound/soc/intel/sst-atom-controls.h index a73e894b175..dfebfdd5eb2 100644 --- a/sound/soc/intel/sst-atom-controls.h +++ b/sound/soc/intel/sst-atom-controls.h @@ -23,6 +23,9 @@ #ifndef __SST_ATOM_CONTROLS_H__ #define __SST_ATOM_CONTROLS_H__ +#include <sound/soc.h> +#include <sound/tlv.h> + enum { MERR_DPCM_AUDIO = 0, MERR_DPCM_COMPR, @@ -360,16 +363,416 @@ struct sst_dsp_header { struct sst_cmd_generic { struct sst_dsp_header header; } __packed; + +struct swm_input_ids { + struct sst_destination_id input_id; +} __packed; + +struct sst_cmd_set_swm { + struct sst_dsp_header header; + struct sst_destination_id output_id; + u16 switch_state; + u16 nb_inputs; + struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS]; +} __packed; + +struct sst_cmd_set_media_path { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +struct pcm_cfg { + u8 s_length:2; + u8 rate:3; + u8 format:3; +} __packed; + +struct sst_cmd_set_speech_path { + struct sst_dsp_header header; + u16 switch_state; + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } config; +} __packed; + +struct gain_cell { + struct sst_destination_id dest; + s16 cell_gain_left; + s16 cell_gain_right; + u16 gain_time_constant; +} __packed; + +#define NUM_GAIN_CELLS 1 +struct sst_cmd_set_gain_dual { + struct sst_dsp_header header; + u16 gain_cell_num; + struct gain_cell cell_gains[NUM_GAIN_CELLS]; +} __packed; struct sst_cmd_set_params { struct sst_destination_id dst; u16 command_id; char params[0]; } __packed; + + +struct sst_cmd_sba_vb_start { + struct sst_dsp_header header; +} __packed; + +union sba_media_loop_params { + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } part; + u16 full; +} __packed; + +struct sst_cmd_sba_set_media_loop_map { + struct sst_dsp_header header; + u16 switch_state; + union sba_media_loop_params param; + u16 map; +} __packed; + +struct sst_cmd_tone_stop { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +enum sst_ssp_mode { + SSP_MODE_MASTER = 0, + SSP_MODE_SLAVE = 1, +}; + +enum sst_ssp_pcm_mode { + SSP_PCM_MODE_NORMAL = 0, + SSP_PCM_MODE_NETWORK = 1, +}; + +enum sst_ssp_duplex { + SSP_DUPLEX = 0, + SSP_RX = 1, + SSP_TX = 2, +}; + +enum sst_ssp_fs_frequency { + SSP_FS_8_KHZ = 0, + SSP_FS_16_KHZ = 1, + SSP_FS_44_1_KHZ = 2, + SSP_FS_48_KHZ = 3, +}; + +enum sst_ssp_fs_polarity { + SSP_FS_ACTIVE_LOW = 0, + SSP_FS_ACTIVE_HIGH = 1, +}; + +enum sst_ssp_protocol { + SSP_MODE_PCM = 0, + SSP_MODE_I2S = 1, +}; + +enum sst_ssp_port_id { + SSP_MODEM = 0, + SSP_BT = 1, + SSP_FM = 2, + SSP_CODEC = 3, +}; + +struct sst_cmd_sba_hw_set_ssp { + struct sst_dsp_header header; + u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */ + + u16 switch_state; + + u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */ + u16 nb_slots:4; /* 0-8: slots per frame */ + u16 mode:3; /* 0:Master, 1: Slave */ + u16 duplex:3; + + u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */ + u16 reserved1:8; + + u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */ + u16 reserved2:8; + + u16 frame_sync_frequency; + + u16 frame_sync_polarity:8; + u16 data_polarity:8; + + u16 frame_sync_width; /* 1 to N clocks */ + u16 ssp_protocol:8; + u16 start_delay:8; /* Start delay in terms of clock ticks */ +} __packed; + +#define SST_MAX_TDM_SLOTS 8 + +struct sst_param_sba_ssp_slot_map { + struct sst_dsp_header header; + + u16 param_id; + u16 param_len; + u16 ssp_index; + + u8 rx_slot_map[SST_MAX_TDM_SLOTS]; + u8 tx_slot_map[SST_MAX_TDM_SLOTS]; +} __packed; + +enum { + SST_PROBE_EXTRACTOR = 0, + SST_PROBE_INJECTOR = 1, +}; + +/**** widget defines *****/ + +#define SST_MODULE_GAIN 1 +#define SST_MODULE_ALGO 2 + +#define SST_FMT_MONO 0 +#define SST_FMT_STEREO 3 + +/* physical SSP numbers */ +enum { + SST_SSP0 = 0, + SST_SSP1, + SST_SSP2, + SST_SSP_LAST = SST_SSP2, +}; + +#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */ +#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */ +#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */ + +struct sst_module { + struct snd_kcontrol *kctl; + struct list_head node; +}; + +struct sst_ssp_config { + u8 ssp_id; + u8 bits_per_slot; + u8 slots; + u8 ssp_mode; + u8 pcm_mode; + u8 duplex; + u8 ssp_protocol; + u8 fs_frequency; + u8 active_slot_map; + u8 start_delay; + u16 fs_width; +}; + +struct sst_ssp_cfg { + const u8 ssp_number; + const int *mux_shift; + const int (*domain_shift)[SST_MAX_SSP_MUX]; + const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS]; +}; + +struct sst_ids { + u16 location_id; + u16 module_id; + u8 task_id; + u8 format; + u8 reg; + const char *parent_wname; + struct snd_soc_dapm_widget *parent_w; + struct list_head algo_list; + struct list_head gain_list; + const struct sst_pcm_format *pcm_fmt; +}; + + +#define SST_AIF_IN(wname, wevent) \ +{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_AIF_OUT(wname, wevent) \ +{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_INPUT(wname, wevent) \ +{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_OUTPUT(wname, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\ + .pcm_fmt = wformat, } \ +} + +#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \ +} + +#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .parent_wname = linked_wname} \ +} + +#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .format = wformat,} \ +} + +/* output is triggered before input */ +#define SST_PATH_INPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \ + SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + + +#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \ +{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \ + SND_SOC_DAPM_POST_REG, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .reg = wreg } \ +} + +enum sst_gain_kcontrol_type { + SST_GAIN_TLV, + SST_GAIN_MUTE, + SST_GAIN_RAMP_DURATION, +}; + +struct sst_gain_mixer_control { + bool stereo; + enum sst_gain_kcontrol_type type; + struct sst_gain_value *gain_val; + int max; + int min; + u16 instance_id; + u16 module_id; + u16 pipe_id; + u16 task_id; + char pname[44]; + struct snd_soc_dapm_widget *w; +}; + +struct sst_gain_value { + u16 ramp_duration; + s16 l_gain; + s16 r_gain; + bool mute; +}; +#define SST_GAIN_VOLUME_DEFAULT (-1440) +#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */ +#define SST_GAIN_MUTE_DEFAULT true + +#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = sst_gain_ctl_info,\ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, xtype, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sst_gain_ctl_info, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .max = xmax, .min = xmin, .type = xtype, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\ + xmod, xpipe, xinstance, xtask, xgain_val, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_bool_ext, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .type = SST_GAIN_MUTE, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} #define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \ xpname " " xmname " " #xinstance " " xtype #define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \ xpname " " xmname " " #xinstance " " xtype " " xsubmodule + +/* + * 3 Controls for each Gain module + * e.g. - pcm0_in Gain 0 Volume + * - pcm0_in Gain 0 Ramp Delay + * - pcm0_in Gain 0 Switch + */ +#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \ + xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \ + { SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \ + xgain_val, xmin_tc, xmax_tc, xpname) }, \ + { SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \ + xgain_val, xpname) } ,\ + { SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \ + xgain_val, xmin_gain, xmax_gain, xpname) } + +#define SST_GAIN_TC_MIN 5 +#define SST_GAIN_TC_MAX 5000 +#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */ +#define SST_GAIN_MAX_VALUE 360 + enum sst_algo_kcontrol_type { SST_ALGO_PARAMS, SST_ALGO_BYPASS, @@ -439,4 +842,29 @@ struct sst_enum { struct snd_soc_dapm_widget *w; }; +/* only 4 slots/channels supported atm */ +#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \ + (struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, } + +#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \ + xpname " " xmname " " s_ch_name + +#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \ + .info = sst_slot_enum_info, \ + .get = xget, .put = xput, \ + .private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \ +} + +#define SST_MUX_CTL_NAME(xpname, xinstance) \ + xpname " " #xinstance + +#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \ + (struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts) + +#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \ + SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \ + SST_SSP_MUX_ENUM(xreg, xshift, xtexts)) + #endif diff --git a/sound/soc/intel/sst-baytrail-dsp.c b/sound/soc/intel/sst-baytrail-dsp.c index fc588764ffa..5a9e56700f3 100644 --- a/sound/soc/intel/sst-baytrail-dsp.c +++ b/sound/soc/intel/sst-baytrail-dsp.c @@ -67,17 +67,12 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, { struct dma_block_info *block; struct sst_module *mod; - struct sst_module_data block_data; struct sst_module_template template; int count; memset(&template, 0, sizeof(template)); template.id = module->type; template.entry = module->entry_point; - template.p.type = SST_MEM_DRAM; - template.p.data_type = SST_DATA_P; - template.s.type = SST_MEM_DRAM; - template.s.data_type = SST_DATA_S; mod = sst_module_new(fw, &template, NULL); if (mod == NULL) @@ -94,19 +89,19 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, switch (block->type) { case SST_BYT_IRAM: - block_data.offset = block->ram_offset + + mod->offset = block->ram_offset + dsp->addr.iram_offset; - block_data.type = SST_MEM_IRAM; + mod->type = SST_MEM_IRAM; break; case SST_BYT_DRAM: - block_data.offset = block->ram_offset + + mod->offset = block->ram_offset + dsp->addr.dram_offset; - block_data.type = SST_MEM_DRAM; + mod->type = SST_MEM_DRAM; break; case SST_BYT_CACHE: - block_data.offset = block->ram_offset + + mod->offset = block->ram_offset + (dsp->addr.fw_ext - dsp->addr.lpe); - block_data.type = SST_MEM_CACHE; + mod->type = SST_MEM_CACHE; break; default: dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", @@ -114,11 +109,10 @@ static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, return -EINVAL; } - block_data.size = block->size; - block_data.data_type = SST_DATA_M; - block_data.data = (void *)block + sizeof(*block); + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); - sst_module_insert_fixed_block(mod, &block_data); + sst_module_alloc_blocks(mod); block = (void *)block + sizeof(*block) + block->size; } diff --git a/sound/soc/intel/sst-dsp-priv.h b/sound/soc/intel/sst-dsp-priv.h index ffb308bd81c..b9da030e312 100644 --- a/sound/soc/intel/sst-dsp-priv.h +++ b/sound/soc/intel/sst-dsp-priv.h @@ -26,6 +26,9 @@ struct sst_mem_block; struct sst_module; struct sst_fw; +/* do we need to remove or keep */ +#define DSP_DRAM_ADDR_OFFSET 0x400000 + /* * DSP Operations exported by platform Audio DSP driver. */ @@ -33,6 +36,9 @@ struct sst_ops { /* DSP core boot / reset */ void (*boot)(struct sst_dsp *); void (*reset)(struct sst_dsp *); + int (*wake)(struct sst_dsp *); + void (*sleep)(struct sst_dsp *); + void (*stall)(struct sst_dsp *); /* Shim IO */ void (*write)(void __iomem *addr, u32 offset, u32 value); @@ -67,6 +73,8 @@ struct sst_addr { u32 shim_offset; u32 iram_offset; u32 dram_offset; + u32 dsp_iram_offset; + u32 dsp_dram_offset; void __iomem *lpe; void __iomem *shim; void __iomem *pci_cfg; @@ -84,15 +92,6 @@ struct sst_mailbox { }; /* - * Audio DSP Firmware data types. - */ -enum sst_data_type { - SST_DATA_M = 0, /* module block data */ - SST_DATA_P = 1, /* peristant data (text, data) */ - SST_DATA_S = 2, /* scratch data (usually buffers) */ -}; - -/* * Audio DSP memory block types. */ enum sst_mem_type { @@ -125,23 +124,6 @@ struct sst_fw { }; /* - * Audio DSP Generic Module data. - * - * This is used to dsecribe any sections of persistent (text and data) and - * scratch (buffers) of module data in ADSP memory space. - */ -struct sst_module_data { - - enum sst_mem_type type; /* destination memory type */ - enum sst_data_type data_type; /* type of module data */ - - u32 size; /* size in bytes */ - int32_t offset; /* offset in FW file */ - u32 data_offset; /* offset in ADSP memory space */ - void *data; /* module data */ -}; - -/* * Audio DSP Generic Module Template. * * Used to define and register a new FW module. This data is extracted from @@ -150,15 +132,52 @@ struct sst_module_data { struct sst_module_template { u32 id; u32 entry; /* entry point */ - struct sst_module_data s; /* scratch data */ - struct sst_module_data p; /* peristant data */ + u32 scratch_size; + u32 persistent_size; +}; + +/* + * Block Allocator - Used to allocate blocks of DSP memory. + */ +struct sst_block_allocator { + u32 id; + u32 offset; + int size; + enum sst_mem_type type; +}; + +/* + * Runtime Module Instance - A module object can be instanciated multiple + * times within the DSP FW. + */ +struct sst_module_runtime { + struct sst_dsp *dsp; + int id; + struct sst_module *module; /* parent module we belong too */ + + u32 persistent_offset; /* private memory offset */ + void *private; + + struct list_head list; + struct list_head block_list; /* list of blocks used */ +}; + +/* + * Runtime Module Context - The runtime context must be manually stored by the + * driver prior to enter S3 and restored after leaving S3. This should really be + * part of the memory context saved by the enter D3 message IPC ??? + */ +struct sst_module_runtime_context { + dma_addr_t dma_buffer; + u32 *buffer; }; /* * Audio DSP Generic Module. * * Each Firmware file can consist of 1..N modules. A module can span multiple - * ADSP memory blocks. The simplest FW will be a file with 1 module. + * ADSP memory blocks. The simplest FW will be a file with 1 module. A module + * can be instanciated multiple times in the DSP. */ struct sst_module { struct sst_dsp *dsp; @@ -167,10 +186,13 @@ struct sst_module { /* module configuration */ u32 id; u32 entry; /* module entry point */ - u32 offset; /* module offset in firmware file */ + s32 offset; /* module offset in firmware file */ u32 size; /* module size */ - struct sst_module_data s; /* scratch data */ - struct sst_module_data p; /* peristant data */ + u32 scratch_size; /* global scratch memory required */ + u32 persistent_size; /* private memory required */ + enum sst_mem_type type; /* destination memory type */ + u32 data_offset; /* offset in ADSP memory space */ + void *data; /* module data */ /* runtime */ u32 usage_count; /* can be unloaded if count == 0 */ @@ -180,6 +202,7 @@ struct sst_module { struct list_head block_list; /* Module list of blocks in use */ struct list_head list; /* DSP list of modules */ struct list_head list_fw; /* FW list of modules */ + struct list_head runtime_list; /* list of runtime module objects*/ }; /* @@ -208,7 +231,6 @@ struct sst_mem_block { struct sst_block_ops *ops; /* block operations, if any */ /* block status */ - enum sst_data_type data_type; /* data type held in this block */ u32 bytes_used; /* bytes in use by modules */ void *private; /* generic core does not touch this */ int users; /* number of modules using this block */ @@ -253,6 +275,11 @@ struct sst_dsp { struct list_head module_list; struct list_head fw_list; + /* scratch buffer */ + struct list_head scratch_block_list; + u32 scratch_offset; + u32 scratch_size; + /* platform data */ struct sst_pdata *pdata; @@ -290,18 +317,33 @@ void sst_fw_unload(struct sst_fw *sst_fw); /* Create/Free firmware modules */ struct sst_module *sst_module_new(struct sst_fw *sst_fw, struct sst_module_template *template, void *private); -void sst_module_free(struct sst_module *sst_module); -int sst_module_insert(struct sst_module *sst_module); -int sst_module_remove(struct sst_module *sst_module); -int sst_module_insert_fixed_block(struct sst_module *module, - struct sst_module_data *data); +void sst_module_free(struct sst_module *module); struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); - -/* allocate/free pesistent/scratch memory regions managed by drv */ -struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp); -void sst_mem_block_free_scratch(struct sst_dsp *dsp, - struct sst_module *scratch); -int sst_block_module_remove(struct sst_module *module); +int sst_module_alloc_blocks(struct sst_module *module); +int sst_module_free_blocks(struct sst_module *module); + +/* Create/Free firmware module runtime instances */ +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private); +void sst_module_runtime_free(struct sst_module_runtime *runtime); +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id); +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset); +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime); +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); + +/* generic block allocation */ +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list); +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list); + +/* scratch allocation */ +int sst_block_alloc_scratch(struct sst_dsp *dsp); +void sst_block_free_scratch(struct sst_dsp *dsp); /* Register the DSPs memory blocks - would be nice to read from ACPI */ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, @@ -309,4 +351,10 @@ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, void *private); void sst_mem_block_unregister_all(struct sst_dsp *dsp); +/* Create/Free DMA resources */ +int sst_dma_new(struct sst_dsp *sst); +void sst_dma_free(struct sst_dma *dma); + +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type); #endif diff --git a/sound/soc/intel/sst-dsp.c b/sound/soc/intel/sst-dsp.c index cd23060a0d8..86e41084567 100644 --- a/sound/soc/intel/sst-dsp.c +++ b/sound/soc/intel/sst-dsp.c @@ -245,6 +245,29 @@ int sst_dsp_boot(struct sst_dsp *sst) } EXPORT_SYMBOL_GPL(sst_dsp_boot); +int sst_dsp_wake(struct sst_dsp *sst) +{ + if (sst->ops->wake) + return sst->ops->wake(sst); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_wake); + +void sst_dsp_sleep(struct sst_dsp *sst) +{ + if (sst->ops->sleep) + sst->ops->sleep(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_sleep); + +void sst_dsp_stall(struct sst_dsp *sst) +{ + if (sst->ops->stall) + sst->ops->stall(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_stall); + void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) { sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); @@ -352,6 +375,7 @@ struct sst_dsp *sst_dsp_new(struct device *dev, INIT_LIST_HEAD(&sst->free_block_list); INIT_LIST_HEAD(&sst->module_list); INIT_LIST_HEAD(&sst->fw_list); + INIT_LIST_HEAD(&sst->scratch_block_list); /* Initialise SST Audio DSP */ if (sst->ops->init) { @@ -366,6 +390,10 @@ struct sst_dsp *sst_dsp_new(struct device *dev, if (err) goto irq_err; + err = sst_dma_new(sst); + if (err) + dev_warn(dev, "sst_dma_new failed %d\n", err); + return sst; irq_err: @@ -381,6 +409,9 @@ void sst_dsp_free(struct sst_dsp *sst) free_irq(sst->irq, sst); if (sst->ops->free) sst->ops->free(sst); + + if (sst->dma) + sst_dma_free(sst->dma); } EXPORT_SYMBOL_GPL(sst_dsp_free); diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h index 3165dfa9740..f291e32f007 100644 --- a/sound/soc/intel/sst-dsp.h +++ b/sound/soc/intel/sst-dsp.h @@ -30,6 +30,9 @@ #define SST_DMA_TYPE_DW 1 #define SST_DMA_TYPE_MID 2 +/* autosuspend delay 5s*/ +#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) + /* SST Shim register map * The register naming can differ between products. Some products also * contain extra functionality. @@ -156,12 +159,18 @@ #define SST_VDRTCTL3 0xaC /* VDRTCTL0 */ -#define SST_VDRTCL0_APLLSE_MASK 1 -#define SST_VDRTCL0_DSRAMPGE_SHIFT 16 -#define SST_VDRTCL0_DSRAMPGE_MASK (0xffff << SST_VDRTCL0_DSRAMPGE_SHIFT) -#define SST_VDRTCL0_ISRAMPGE_SHIFT 6 +#define SST_VDRTCL0_D3PGD (1 << 0) +#define SST_VDRTCL0_D3SRAMPGD (1 << 1) +#define SST_VDRTCL0_DSRAMPGE_SHIFT 12 +#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT) +#define SST_VDRTCL0_ISRAMPGE_SHIFT 2 #define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) +/* VDRTCTL2 */ +#define SST_VDRTCL2_DCLCGE (1 << 1) +#define SST_VDRTCL2_DTCGE (1 << 10) +#define SST_VDRTCL2_APLLSE_MASK (1 << 31) + /* PMCS */ #define SST_PMCS 0x84 #define SST_PMCS_PS_MASK 0x3 @@ -245,6 +254,17 @@ void sst_memcpy_fromio_32(struct sst_dsp *sst, /* DSP reset & boot */ void sst_dsp_reset(struct sst_dsp *sst); int sst_dsp_boot(struct sst_dsp *sst); +int sst_dsp_wake(struct sst_dsp *sst); +void sst_dsp_sleep(struct sst_dsp *sst); +void sst_dsp_stall(struct sst_dsp *sst); + +/* DMA */ +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id); +void sst_dsp_dma_put_channel(struct sst_dsp *dsp); +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); /* Msg IO */ void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); diff --git a/sound/soc/intel/sst-firmware.c b/sound/soc/intel/sst-firmware.c index 3bb43dac892..4a5bde9c686 100644 --- a/sound/soc/intel/sst-firmware.c +++ b/sound/soc/intel/sst-firmware.c @@ -23,6 +23,11 @@ #include <linux/dma-mapping.h> #include <linux/dmaengine.h> #include <linux/pci.h> +#include <linux/acpi.h> + +/* supported DMA engine drivers */ +#include <linux/platform_data/dma-dw.h> +#include <linux/dma/dw.h> #include <asm/page.h> #include <asm/pgtable.h> @@ -30,16 +35,301 @@ #include "sst-dsp.h" #include "sst-dsp-priv.h" -static void block_module_remove(struct sst_module *module); +#define SST_DMA_RESOURCES 2 +#define SST_DSP_DMA_MAX_BURST 0x3 +#define SST_HSW_BLOCK_ANY 0xffffffff + +#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 + +struct sst_dma { + struct sst_dsp *sst; + + struct dw_dma_chip *chip; + + struct dma_async_tx_descriptor *desc; + struct dma_chan *ch; +}; + +static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) +{ + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy((void *)dest, src, bytes/4); +} + +static void sst_dma_transfer_complete(void *arg) +{ + struct sst_dsp *sst = (struct sst_dsp *)arg; + + dev_dbg(sst->dev, "DMA: callback\n"); +} + +static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + struct dma_async_tx_descriptor *desc; + struct sst_dma *dma = sst->dma; + + if (dma->ch == NULL) { + dev_err(sst->dev, "error: no DMA channel\n"); + return -ENODEV; + } + + dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", + (unsigned long)src_addr, (unsigned long)dest_addr, size); + + desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, + src_addr, size, DMA_CTRL_ACK); + if (!desc){ + dev_err(sst->dev, "error: dma prep memcpy failed\n"); + return -EINVAL; + } + + desc->callback = sst_dma_transfer_complete; + desc->callback_param = sst; + + desc->tx_submit(desc); + dma_wait_for_async_tx(desc); + + return 0; +} + +/* copy to DSP */ +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, + src_addr, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); + +/* copy from DSP */ +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr, + src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); + +/* remove module from memory - callers hold locks */ +static void block_list_remove(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + int err; + + /* disable each block */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->disable) { + err = block->ops->disable(block); + if (err < 0) + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + } + } + + /* mark each block as free */ + list_for_each_entry_safe(block, tmp, block_list, module_list) { + list_del(&block->module_list); + list_move(&block->list, &dsp->free_block_list); + dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } +} + +/* prepare the memory block to receive data from host - callers hold locks */ +static int block_list_prepare(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block; + int ret = 0; + + /* enable each block so that's it'e ready for data */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->enable && !block->users) { + ret = block->ops->enable(block); + if (ret < 0) { + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + goto err; + } + } + } + return ret; + +err: + list_for_each_entry(block, block_list, module_list) { + if (block->ops && block->ops->disable) + block->ops->disable(block); + } + return ret; +} + +static struct dw_dma_platform_data dw_pdata = { + .is_private = 1, + .chan_allocation_order = CHAN_ALLOCATION_ASCENDING, + .chan_priority = CHAN_PRIORITY_ASCENDING, +}; + +static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, + int irq) +{ + struct dw_dma_chip *chip; + int err; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return ERR_PTR(-ENOMEM); + + chip->irq = irq; + chip->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(chip->regs)) + return ERR_CAST(chip->regs); + + err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); + if (err) + return ERR_PTR(err); + + chip->dev = dev; + err = dw_dma_probe(chip, &dw_pdata); + if (err) + return ERR_PTR(err); + + return chip; +} + +static void dw_remove(struct dw_dma_chip *chip) +{ + dw_dma_remove(chip); +} + +static bool dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct sst_dsp *dsp = (struct sst_dsp *)param; + + return chan->device->dev == dsp->dma_dev; +} -static void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) { - u32 i; + struct sst_dma *dma = dsp->dma; + struct dma_slave_config slave; + dma_cap_mask_t mask; + int ret; + + /* The Intel MID DMA engine driver needs the slave config set but + * Synopsis DMA engine driver safely ignores the slave config */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_MEMCPY, mask); + + dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); + if (dma->ch == NULL) { + dev_err(dsp->dev, "error: DMA request channel failed\n"); + return -EIO; + } + + memset(&slave, 0, sizeof(slave)); + slave.direction = DMA_MEM_TO_DEV; + slave.src_addr_width = + slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; + + ret = dmaengine_slave_config(dma->ch, &slave); + if (ret) { + dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", + ret); + dma_release_channel(dma->ch); + dma->ch = NULL; + } - /* copy one 32 bit word at a time as 64 bit access is not supported */ - for (i = 0; i < bytes; i += 4) - memcpy_toio(dest + i, src + i, 4); + return ret; } +EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); + +void sst_dsp_dma_put_channel(struct sst_dsp *dsp) +{ + struct sst_dma *dma = dsp->dma; + + if (!dma->ch) + return; + + dma_release_channel(dma->ch); + dma->ch = NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); + +int sst_dma_new(struct sst_dsp *sst) +{ + struct sst_pdata *sst_pdata = sst->pdata; + struct sst_dma *dma; + struct resource mem; + const char *dma_dev_name; + int ret = 0; + + /* configure the correct platform data for whatever DMA engine + * is attached to the ADSP IP. */ + switch (sst->pdata->dma_engine) { + case SST_DMA_TYPE_DW: + dma_dev_name = "dw_dmac"; + break; + case SST_DMA_TYPE_MID: + dma_dev_name = "Intel MID DMA"; + break; + default: + dev_err(sst->dev, "error: invalid DMA engine %d\n", + sst->pdata->dma_engine); + return -EINVAL; + } + + dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + dma->sst = sst; + + memset(&mem, 0, sizeof(mem)); + + mem.start = sst->addr.lpe_base + sst_pdata->dma_base; + mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; + mem.flags = IORESOURCE_MEM; + + /* now register DMA engine device */ + dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); + if (IS_ERR(dma->chip)) { + dev_err(sst->dev, "error: DMA device register failed\n"); + ret = PTR_ERR(dma->chip); + goto err_dma_dev; + } + + sst->dma = dma; + sst->fw_use_dma = true; + return 0; + +err_dma_dev: + devm_kfree(sst->dev, dma); + return ret; +} +EXPORT_SYMBOL(sst_dma_new); + +void sst_dma_free(struct sst_dma *dma) +{ + + if (dma == NULL) + return; + + if (dma->ch) + dma_release_channel(dma->ch); + + if (dma->chip) + dw_remove(dma->chip); + +} +EXPORT_SYMBOL(sst_dma_free); /* create new generic firmware object */ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, @@ -71,6 +361,12 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, /* copy FW data to DMA-able memory */ memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); + if (dsp->fw_use_dma) { + err = sst_dsp_dma_get_channel(dsp, 0); + if (err < 0) + goto chan_err; + } + /* call core specific FW paser to load FW data into DSP */ err = dsp->ops->parse_fw(sst_fw); if (err < 0) { @@ -78,6 +374,9 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, goto parse_err; } + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); + mutex_lock(&dsp->mutex); list_add(&sst_fw->list, &dsp->fw_list); mutex_unlock(&dsp->mutex); @@ -85,9 +384,13 @@ struct sst_fw *sst_fw_new(struct sst_dsp *dsp, return sst_fw; parse_err: - dma_free_coherent(dsp->dev, sst_fw->size, + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); +chan_err: + dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, sst_fw->dmable_fw_paddr); + sst_fw->dma_buf = NULL; kfree(sst_fw); return NULL; } @@ -111,21 +414,37 @@ EXPORT_SYMBOL_GPL(sst_fw_reload); void sst_fw_unload(struct sst_fw *sst_fw) { - struct sst_dsp *dsp = sst_fw->dsp; - struct sst_module *module, *tmp; + struct sst_dsp *dsp = sst_fw->dsp; + struct sst_module *module, *mtmp; + struct sst_module_runtime *runtime, *rtmp; + + dev_dbg(dsp->dev, "unloading firmware\n"); + + mutex_lock(&dsp->mutex); + + /* check module by module */ + list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { + if (module->sst_fw == sst_fw) { + + /* remove runtime modules */ + list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { - dev_dbg(dsp->dev, "unloading firmware\n"); + block_list_remove(dsp, &runtime->block_list); + list_del(&runtime->list); + kfree(runtime); + } + + /* now remove the module */ + block_list_remove(dsp, &module->block_list); + list_del(&module->list); + kfree(module); + } + } - mutex_lock(&dsp->mutex); - list_for_each_entry_safe(module, tmp, &dsp->module_list, list) { - if (module->sst_fw == sst_fw) { - block_module_remove(module); - list_del(&module->list); - kfree(module); - } - } + /* remove all scratch blocks */ + block_list_remove(dsp, &dsp->scratch_block_list); - mutex_unlock(&dsp->mutex); + mutex_unlock(&dsp->mutex); } EXPORT_SYMBOL_GPL(sst_fw_unload); @@ -138,7 +457,8 @@ void sst_fw_free(struct sst_fw *sst_fw) list_del(&sst_fw->list); mutex_unlock(&dsp->mutex); - dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, + if (sst_fw->dma_buf) + dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, sst_fw->dmable_fw_paddr); kfree(sst_fw); } @@ -175,11 +495,11 @@ struct sst_module *sst_module_new(struct sst_fw *sst_fw, sst_module->id = template->id; sst_module->dsp = dsp; sst_module->sst_fw = sst_fw; - - memcpy(&sst_module->s, &template->s, sizeof(struct sst_module_data)); - memcpy(&sst_module->p, &template->p, sizeof(struct sst_module_data)); + sst_module->scratch_size = template->scratch_size; + sst_module->persistent_size = template->persistent_size; INIT_LIST_HEAD(&sst_module->block_list); + INIT_LIST_HEAD(&sst_module->runtime_list); mutex_lock(&dsp->mutex); list_add(&sst_module->list, &dsp->module_list); @@ -202,73 +522,122 @@ void sst_module_free(struct sst_module *sst_module) } EXPORT_SYMBOL_GPL(sst_module_free); -static struct sst_mem_block *find_block(struct sst_dsp *dsp, int type, - u32 offset) +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private) +{ + struct sst_dsp *dsp = module->dsp; + struct sst_module_runtime *runtime; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return NULL; + + runtime->id = id; + runtime->dsp = dsp; + runtime->module = module; + INIT_LIST_HEAD(&runtime->block_list); + + mutex_lock(&dsp->mutex); + list_add(&runtime->list, &module->runtime_list); + mutex_unlock(&dsp->mutex); + + return runtime; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_new); + +void sst_module_runtime_free(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + list_del(&runtime->list); + mutex_unlock(&dsp->mutex); + + kfree(runtime); +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free); + +static struct sst_mem_block *find_block(struct sst_dsp *dsp, + struct sst_block_allocator *ba) { struct sst_mem_block *block; list_for_each_entry(block, &dsp->free_block_list, list) { - if (block->type == type && block->offset == offset) + if (block->type == ba->type && block->offset == ba->offset) return block; } return NULL; } -static int block_alloc_contiguous(struct sst_module *module, - struct sst_module_data *data, u32 offset, int size) +/* Block allocator must be on block boundary */ +static int block_alloc_contiguous(struct sst_dsp *dsp, + struct sst_block_allocator *ba, struct list_head *block_list) { struct list_head tmp = LIST_HEAD_INIT(tmp); - struct sst_dsp *dsp = module->dsp; struct sst_mem_block *block; + u32 block_start = SST_HSW_BLOCK_ANY; + int size = ba->size, offset = ba->offset; + + while (ba->size > 0) { - while (size > 0) { - block = find_block(dsp, data->type, offset); + block = find_block(dsp, ba); if (!block) { list_splice(&tmp, &dsp->free_block_list); + + ba->size = size; + ba->offset = offset; return -ENOMEM; } list_move_tail(&block->list, &tmp); - offset += block->size; - size -= block->size; + ba->offset += block->size; + ba->size -= block->size; } + ba->size = size; + ba->offset = offset; + + list_for_each_entry(block, &tmp, list) { + + if (block->offset < block_start) + block_start = block->offset; - list_for_each_entry(block, &tmp, list) - list_add(&block->module_list, &module->block_list); + list_add(&block->module_list, block_list); + + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } list_splice(&tmp, &dsp->used_block_list); return 0; } -/* allocate free DSP blocks for module data - callers hold locks */ -static int block_alloc(struct sst_module *module, - struct sst_module_data *data) +/* allocate first free DSP blocks for data - callers hold locks */ +static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) { - struct sst_dsp *dsp = module->dsp; struct sst_mem_block *block, *tmp; int ret = 0; - if (data->size == 0) + if (ba->size == 0) return 0; /* find first free whole blocks that can hold module */ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { /* ignore blocks with wrong type */ - if (block->type != data->type) + if (block->type != ba->type) continue; - if (data->size > block->size) + if (ba->size > block->size) continue; - data->offset = block->offset; - block->data_type = data->data_type; - block->bytes_used = data->size % block->size; - list_add(&block->module_list, &module->block_list); + ba->offset = block->offset; + block->bytes_used = ba->size % block->size; + list_add(&block->module_list, block_list); list_move(&block->list, &dsp->used_block_list); - dev_dbg(dsp->dev, " *module %d added block %d:%d\n", - module->id, block->type, block->index); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); return 0; } @@ -276,15 +645,19 @@ static int block_alloc(struct sst_module *module, list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { /* ignore blocks with wrong type */ - if (block->type != data->type) + if (block->type != ba->type) continue; /* do we span > 1 blocks */ - if (data->size > block->size) { - ret = block_alloc_contiguous(module, data, - block->offset, data->size); + if (ba->size > block->size) { + + /* align ba to block boundary */ + ba->offset = block->offset; + + ret = block_alloc_contiguous(dsp, ba, block_list); if (ret == 0) return ret; + } } @@ -292,93 +665,74 @@ static int block_alloc(struct sst_module *module, return -ENOMEM; } -/* remove module from memory - callers hold locks */ -static void block_module_remove(struct sst_module *module) +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) { - struct sst_mem_block *block, *tmp; - struct sst_dsp *dsp = module->dsp; - int err; + int ret; - /* disable each block */ - list_for_each_entry(block, &module->block_list, module_list) { + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba->size, ba->offset, ba->type); - if (block->ops && block->ops->disable) { - err = block->ops->disable(block); - if (err < 0) - dev_err(dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - } - } + mutex_lock(&dsp->mutex); - /* mark each block as free */ - list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { - list_del(&block->module_list); - list_move(&block->list, &dsp->free_block_list); + ret = block_alloc(dsp, ba, block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); + goto out; } -} - -/* prepare the memory block to receive data from host - callers hold locks */ -static int block_module_prepare(struct sst_module *module) -{ - struct sst_mem_block *block; - int ret = 0; - /* enable each block so that's it'e ready for module P/S data */ - list_for_each_entry(block, &module->block_list, module_list) { + /* prepare DSP blocks for module usage */ + ret = block_list_prepare(dsp, block_list); + if (ret < 0) + dev_err(dsp->dev, "error: prepare failed\n"); - if (block->ops && block->ops->enable) { - ret = block->ops->enable(block); - if (ret < 0) { - dev_err(module->dsp->dev, - "error: cant disable block %d:%d\n", - block->type, block->index); - goto err; - } - } - } +out: + mutex_unlock(&dsp->mutex); return ret; +} +EXPORT_SYMBOL_GPL(sst_alloc_blocks); -err: - list_for_each_entry(block, &module->block_list, module_list) { - if (block->ops && block->ops->disable) - block->ops->disable(block); - } - return ret; +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) +{ + mutex_lock(&dsp->mutex); + block_list_remove(dsp, block_list); + mutex_unlock(&dsp->mutex); + return 0; } +EXPORT_SYMBOL_GPL(sst_free_blocks); /* allocate memory blocks for static module addresses - callers hold locks */ -static int block_alloc_fixed(struct sst_module *module, - struct sst_module_data *data) +static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) { - struct sst_dsp *dsp = module->dsp; struct sst_mem_block *block, *tmp; - u32 end = data->offset + data->size, block_end; + u32 end = ba->offset + ba->size, block_end; int err; /* only IRAM/DRAM blocks are managed */ - if (data->type != SST_MEM_IRAM && data->type != SST_MEM_DRAM) + if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) return 0; /* are blocks already attached to this module */ - list_for_each_entry_safe(block, tmp, &module->block_list, module_list) { + list_for_each_entry_safe(block, tmp, block_list, module_list) { - /* force compacting mem blocks of the same data_type */ - if (block->data_type != data->data_type) + /* ignore blocks with wrong type */ + if (block->type != ba->type) continue; block_end = block->offset + block->size; /* find block that holds section */ - if (data->offset >= block->offset && end < block_end) + if (ba->offset >= block->offset && end <= block_end) return 0; /* does block span more than 1 section */ - if (data->offset >= block->offset && data->offset < block_end) { + if (ba->offset >= block->offset && ba->offset < block_end) { - err = block_alloc_contiguous(module, data, - block->offset + block->size, - data->size - block->size); + /* align ba to block boundary */ + ba->size -= block_end - ba->offset; + ba->offset = block_end; + err = block_alloc_contiguous(dsp, ba, block_list); if (err < 0) return -ENOMEM; @@ -391,82 +745,270 @@ static int block_alloc_fixed(struct sst_module *module, list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { block_end = block->offset + block->size; + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + /* find block that holds section */ - if (data->offset >= block->offset && end < block_end) { + if (ba->offset >= block->offset && end <= block_end) { /* add block */ - block->data_type = data->data_type; list_move(&block->list, &dsp->used_block_list); - list_add(&block->module_list, &module->block_list); + list_add(&block->module_list, block_list); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); return 0; } /* does block span more than 1 section */ - if (data->offset >= block->offset && data->offset < block_end) { + if (ba->offset >= block->offset && ba->offset < block_end) { - err = block_alloc_contiguous(module, data, - block->offset, data->size); + /* align ba to block boundary */ + ba->offset = block->offset; + + err = block_alloc_contiguous(dsp, ba, block_list); if (err < 0) return -ENOMEM; return 0; } - } return -ENOMEM; } /* Load fixed module data into DSP memory blocks */ -int sst_module_insert_fixed_block(struct sst_module *module, - struct sst_module_data *data) +int sst_module_alloc_blocks(struct sst_module *module) { struct sst_dsp *dsp = module->dsp; + struct sst_fw *sst_fw = module->sst_fw; + struct sst_block_allocator ba; int ret; + ba.size = module->size; + ba.type = module->type; + ba.offset = module->offset; + + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba.size, ba.offset, ba.type); + mutex_lock(&dsp->mutex); /* alloc blocks that includes this section */ - ret = block_alloc_fixed(module, data); + ret = block_alloc_fixed(dsp, &ba, &module->block_list); if (ret < 0) { dev_err(dsp->dev, "error: no free blocks for section at offset 0x%x size 0x%x\n", - data->offset, data->size); + module->offset, module->size); mutex_unlock(&dsp->mutex); return -ENOMEM; } /* prepare DSP blocks for module copy */ - ret = block_module_prepare(module); + ret = block_list_prepare(dsp, &module->block_list); if (ret < 0) { dev_err(dsp->dev, "error: fw module prepare failed\n"); goto err; } /* copy partial module data to blocks */ - sst_memcpy32(dsp->addr.lpe + data->offset, data->data, data->size); + if (dsp->fw_use_dma) { + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + module->offset, + sst_fw->dmable_fw_paddr + module->data_offset, + module->size); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + module->offset, module->data, + module->size); mutex_unlock(&dsp->mutex); return ret; err: - block_module_remove(module); + block_list_remove(dsp, &module->block_list); mutex_unlock(&dsp->mutex); return ret; } -EXPORT_SYMBOL_GPL(sst_module_insert_fixed_block); +EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); /* Unload entire module from DSP memory */ -int sst_block_module_remove(struct sst_module *module) +int sst_module_free_blocks(struct sst_module *module) { struct sst_dsp *dsp = module->dsp; mutex_lock(&dsp->mutex); - block_module_remove(module); + block_list_remove(dsp, &module->block_list); mutex_unlock(&dsp->mutex); return 0; } -EXPORT_SYMBOL_GPL(sst_block_module_remove); +EXPORT_SYMBOL_GPL(sst_module_free_blocks); + +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + struct sst_block_allocator ba; + int ret; + + if (module->persistent_size == 0) + return 0; + + ba.size = module->persistent_size; + ba.type = SST_MEM_DRAM; + + mutex_lock(&dsp->mutex); + + /* do we need to allocate at a fixed address ? */ + if (offset != 0) { + + ba.offset = offset; + + dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", + ba.size, ba.type, ba.offset); + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); + + } else { + dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", + ba.size, ba.type); + + /* alloc blocks that includes this section */ + ret = block_alloc(dsp, &ba, &runtime->block_list); + } + if (ret < 0) { + dev_err(dsp->dev, + "error: no free blocks for runtime module size 0x%x\n", + module->persistent_size); + mutex_unlock(&dsp->mutex); + return -ENOMEM; + } + runtime->persistent_offset = ba.offset; + + /* prepare DSP blocks for module copy */ + ret = block_list_prepare(dsp, &runtime->block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: runtime block prepare failed\n"); + goto err; + } + + mutex_unlock(&dsp->mutex); + return ret; + +err: + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); + +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &runtime->block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); + +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + context->buffer = dma_alloc_coherent(dsp->dma_dev, + module->persistent_size, + &context->dma_buffer, GFP_DMA | GFP_KERNEL); + if (!context->buffer) { + dev_err(dsp->dev, "error: DMA context alloc failed\n"); + return -ENOMEM; + } + + mutex_lock(&dsp->mutex); + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, + dsp->addr.lpe_base + runtime->persistent_offset, + module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: context copy failed\n"); + goto err; + } + } else + sst_memcpy32(context->buffer, dsp->addr.lpe + + runtime->persistent_offset, + module->persistent_size); + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_save); + +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + mutex_lock(&dsp->mutex); + + if (!context->buffer) { + dev_info(dsp->dev, "no context buffer need to restore!\n"); + goto err; + } + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + runtime->persistent_offset, + context->dma_buffer, module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, + context->buffer, module->persistent_size); + + dma_free_coherent(dsp->dma_dev, module->persistent_size, + context->buffer, context->dma_buffer); + context->buffer = NULL; + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_restore); /* register a DSP memory block for use with FW based modules */ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, @@ -519,80 +1061,84 @@ void sst_mem_block_unregister_all(struct sst_dsp *dsp) EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); /* allocate scratch buffer blocks */ -struct sst_module *sst_mem_block_alloc_scratch(struct sst_dsp *dsp) +int sst_block_alloc_scratch(struct sst_dsp *dsp) { - struct sst_module *sst_module, *scratch; - struct sst_mem_block *block, *tmp; - u32 block_size; - int ret = 0; - - scratch = kzalloc(sizeof(struct sst_module), GFP_KERNEL); - if (scratch == NULL) - return NULL; + struct sst_module *module; + struct sst_block_allocator ba; + int ret; mutex_lock(&dsp->mutex); /* calculate required scratch size */ - list_for_each_entry(sst_module, &dsp->module_list, list) { - if (scratch->s.size < sst_module->s.size) - scratch->s.size = sst_module->s.size; + dsp->scratch_size = 0; + list_for_each_entry(module, &dsp->module_list, list) { + dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", + module->id, module->scratch_size); + if (dsp->scratch_size < module->scratch_size) + dsp->scratch_size = module->scratch_size; } - dev_dbg(dsp->dev, "scratch buffer required is %d bytes\n", - scratch->s.size); - - /* init scratch module */ - scratch->dsp = dsp; - scratch->s.type = SST_MEM_DRAM; - scratch->s.data_type = SST_DATA_S; - INIT_LIST_HEAD(&scratch->block_list); + dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", + dsp->scratch_size); - /* check free blocks before looking at used blocks for space */ - if (!list_empty(&dsp->free_block_list)) - block = list_first_entry(&dsp->free_block_list, - struct sst_mem_block, list); - else - block = list_first_entry(&dsp->used_block_list, - struct sst_mem_block, list); - block_size = block->size; + if (dsp->scratch_size == 0) { + dev_info(dsp->dev, "no modules need scratch buffer\n"); + mutex_unlock(&dsp->mutex); + return 0; + } /* allocate blocks for module scratch buffers */ dev_dbg(dsp->dev, "allocating scratch blocks\n"); - ret = block_alloc(scratch, &scratch->s); + + ba.size = dsp->scratch_size; + ba.type = SST_MEM_DRAM; + + /* do we need to allocate at fixed offset */ + if (dsp->scratch_offset != 0) { + + dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", + ba.size, ba.type, ba.offset); + + ba.offset = dsp->scratch_offset; + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); + + } else { + dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", + ba.size, ba.type); + + ba.offset = 0; + ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); + } if (ret < 0) { dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); - goto err; + mutex_unlock(&dsp->mutex); + return ret; } - /* assign the same offset of scratch to each module */ - list_for_each_entry(sst_module, &dsp->module_list, list) - sst_module->s.offset = scratch->s.offset; - - mutex_unlock(&dsp->mutex); - return scratch; + ret = block_list_prepare(dsp, &dsp->scratch_block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: scratch block prepare failed\n"); + mutex_unlock(&dsp->mutex); + return ret; + } -err: - list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) - list_del(&block->module_list); + /* assign the same offset of scratch to each module */ + dsp->scratch_offset = ba.offset; mutex_unlock(&dsp->mutex); - return NULL; + return dsp->scratch_size; } -EXPORT_SYMBOL_GPL(sst_mem_block_alloc_scratch); +EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); /* free all scratch blocks */ -void sst_mem_block_free_scratch(struct sst_dsp *dsp, - struct sst_module *scratch) +void sst_block_free_scratch(struct sst_dsp *dsp) { - struct sst_mem_block *block, *tmp; - mutex_lock(&dsp->mutex); - - list_for_each_entry_safe(block, tmp, &scratch->block_list, module_list) - list_del(&block->module_list); - + block_list_remove(dsp, &dsp->scratch_block_list); mutex_unlock(&dsp->mutex); } -EXPORT_SYMBOL_GPL(sst_mem_block_free_scratch); +EXPORT_SYMBOL_GPL(sst_block_free_scratch); /* get a module from it's unique ID */ struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) @@ -612,3 +1158,40 @@ struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) return NULL; } EXPORT_SYMBOL_GPL(sst_module_get_from_id); + +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id) +{ + struct sst_module_runtime *runtime; + struct sst_dsp *dsp = module->dsp; + + mutex_lock(&dsp->mutex); + + list_for_each_entry(runtime, &module->runtime_list, list) { + if (runtime->id == id) { + mutex_unlock(&dsp->mutex); + return runtime; + } + } + + mutex_unlock(&dsp->mutex); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); + +/* returns block address in DSP address space */ +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type) +{ + switch (type) { + case SST_MEM_IRAM: + return offset - dsp->addr.iram_offset + + dsp->addr.dsp_iram_offset; + case SST_MEM_DRAM: + return offset - dsp->addr.dram_offset + + dsp->addr.dsp_dram_offset; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(sst_dsp_get_offset); diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c index 4b6c163c10f..57039b00efc 100644 --- a/sound/soc/intel/sst-haswell-dsp.c +++ b/sound/soc/intel/sst-haswell-dsp.c @@ -42,6 +42,10 @@ #define SST_LP_SHIM_OFFSET 0xE7000 #define SST_WPT_IRAM_OFFSET 0xA0000 #define SST_LP_IRAM_OFFSET 0x80000 +#define SST_WPT_DSP_DRAM_OFFSET 0x400000 +#define SST_WPT_DSP_IRAM_OFFSET 0x00000 +#define SST_LPT_DSP_DRAM_OFFSET 0x400000 +#define SST_LPT_DSP_IRAM_OFFSET 0x00000 #define SST_SHIM_PM_REG 0x84 @@ -86,9 +90,8 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, { struct dma_block_info *block; struct sst_module *mod; - struct sst_module_data block_data; struct sst_module_template template; - int count; + int count, ret; void __iomem *ram; /* TODO: allowed module types need to be configurable */ @@ -109,13 +112,9 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, memset(&template, 0, sizeof(template)); template.id = module->type; - template.entry = module->entry_point; - template.p.size = module->info.persistent_size; - template.p.type = SST_MEM_DRAM; - template.p.data_type = SST_DATA_P; - template.s.size = module->info.scratch_size; - template.s.type = SST_MEM_DRAM; - template.s.data_type = SST_DATA_S; + template.entry = module->entry_point - 4; + template.persistent_size = module->info.persistent_size; + template.scratch_size = module->info.scratch_size; mod = sst_module_new(fw, &template, NULL); if (mod == NULL) @@ -135,14 +134,14 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, switch (block->type) { case SST_HSW_IRAM: ram = dsp->addr.lpe; - block_data.offset = + mod->offset = block->ram_offset + dsp->addr.iram_offset; - block_data.type = SST_MEM_IRAM; + mod->type = SST_MEM_IRAM; break; case SST_HSW_DRAM: ram = dsp->addr.lpe; - block_data.offset = block->ram_offset; - block_data.type = SST_MEM_DRAM; + mod->offset = block->ram_offset; + mod->type = SST_MEM_DRAM; break; default: dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", @@ -151,30 +150,34 @@ static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, return -EINVAL; } - block_data.size = block->size; - block_data.data_type = SST_DATA_M; - block_data.data = (void *)block + sizeof(*block); - block_data.data_offset = block_data.data - fw->dma_buf; + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); + mod->data_offset = mod->data - fw->dma_buf; - dev_dbg(dsp->dev, "copy firmware block %d type 0x%x " + dev_dbg(dsp->dev, "module block %d type 0x%x " "size 0x%x ==> ram %p offset 0x%x\n", - count, block->type, block->size, ram, + count, mod->type, block->size, ram, block->ram_offset); - sst_module_insert_fixed_block(mod, &block_data); + ret = sst_module_alloc_blocks(mod); + if (ret < 0) { + dev_err(dsp->dev, "error: could not allocate blocks for module %d\n", + count); + sst_module_free(mod); + return ret; + } block = (void *)block + sizeof(*block) + block->size; } + return 0; } static int hsw_parse_fw_image(struct sst_fw *sst_fw) { struct fw_header *header; - struct sst_module *scratch; struct fw_module_header *module; struct sst_dsp *dsp = sst_fw->dsp; - struct sst_hsw *hsw = sst_fw->private; int ret, count; /* Read the header information from the data pointer */ @@ -204,12 +207,8 @@ static int hsw_parse_fw_image(struct sst_fw *sst_fw) module = (void *)module + sizeof(*module) + module->mod_size; } - /* allocate persistent/scratch mem regions */ - scratch = sst_mem_block_alloc_scratch(dsp); - if (scratch == NULL) - return -ENOMEM; - - sst_hsw_set_scratch_module(hsw, scratch); + /* allocate scratch mem regions */ + sst_block_alloc_scratch(dsp); return 0; } @@ -248,8 +247,94 @@ static irqreturn_t hsw_irq(int irq, void *context) return ret; } -static void hsw_boot(struct sst_dsp *sst) +static void hsw_set_dsp_D3(struct sst_dsp *sst) +{ + u32 val; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* enable power gating and switch off DRAM & IRAM blocks */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + val |= SST_VDRTCL0_DSRAMPGE_MASK | + SST_VDRTCL0_ISRAMPGE_MASK; + val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD); + writel(val, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* switch off audio PLL */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_APLLSE_MASK; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* disable MCLK(clkctl.smos = 0) */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, + SST_CLKCTL_MASK, 0); + + /* Set D3 state, delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_PMCS); + val |= SST_PMCS_PS_MASK; + writel(val, sst->addr.pci_cfg + SST_PMCS); + udelay(50); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + +} + +static void hsw_reset(struct sst_dsp *sst) { + /* put DSP into reset and stall */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL); + + /* keep in reset for 10ms */ + mdelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); +} + +static int hsw_set_dsp_D0(struct sst_dsp *sst) +{ + int tries = 10; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_D3PGD; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* Set D0 state */ + reg = readl(sst->addr.pci_cfg + SST_PMCS); + reg &= ~SST_PMCS_PS_MASK; + writel(reg, sst->addr.pci_cfg + SST_PMCS); + + /* check that ADSP shim is enabled */ + while (tries--) { + reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK; + if (reg == 0) + goto finish; + + msleep(1); + } + + return -ENODEV; + +finish: /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); @@ -264,34 +349,96 @@ static void hsw_boot(struct sst_dsp *sst) SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); + /* Stall and reset core, set CSR */ + hsw_reset(sst); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + /* switch on audio PLL */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~SST_VDRTCL2_APLLSE_MASK; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* disable DMA finish function for SSP0 & SSP1 */ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, SST_CSR2_SDFD_SSP1); - /* enable DMA engine 0,1 all channels to access host memory */ - sst_dsp_shim_update_bits_unlocked(sst, SST_HMDC, - SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff), - SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff)); + /* set on-demond mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + /* Enable Interrupt from both sides */ + sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE), + 0x0); + sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY | + SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0); + + /* clear IPC registers */ + sst_dsp_shim_write(sst, SST_IPCX, 0x0); + sst_dsp_shim_write(sst, SST_IPCD, 0x0); + sst_dsp_shim_write(sst, 0x80, 0x6); + sst_dsp_shim_write(sst, 0xe0, 0x300a); + + return 0; +} - /* disable all clock gating */ - writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); +static void hsw_boot(struct sst_dsp *sst) +{ + /* set oportunistic mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0); /* set DSP to RUN */ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); } -static void hsw_reset(struct sst_dsp *sst) +static void hsw_stall(struct sst_dsp *sst) +{ + /* stall DSP */ + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_STALL, + SST_CSR_STALL | SST_CSR_24MHZ_LPCS); +} + +static void hsw_sleep(struct sst_dsp *sst) { + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n"); + /* put DSP into reset and stall */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, SST_CSR_RST | SST_CSR_STALL); + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS); - /* keep in reset for 10ms */ - mdelay(10); + hsw_set_dsp_D3(sst); + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n"); +} - /* take DSP out of reset and keep stalled for FW loading */ - sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, - SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); +static int hsw_wake(struct sst_dsp *sst) +{ + int ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n"); + + ret = hsw_set_dsp_D0(sst); + if (ret < 0) + return ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n"); + + return 0; } struct sst_adsp_memregion { @@ -396,6 +543,11 @@ static int hsw_block_enable(struct sst_mem_block *block) dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", block->type, block->index, block->offset); + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); bit = hsw_block_get_bit(block); writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); @@ -403,6 +555,13 @@ static int hsw_block_enable(struct sst_mem_block *block) /* wait 18 DSP clock ticks */ udelay(10); + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ sst_mem_block_dummy_read(block); return 0; @@ -420,10 +579,26 @@ static int hsw_block_disable(struct sst_mem_block *block) dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", block->type, block->index, block->offset); + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); bit = hsw_block_get_bit(block); writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); + /* wait 18 DSP clock ticks */ + udelay(10); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + return 0; } @@ -432,27 +607,6 @@ static struct sst_block_ops sst_hsw_ops = { .disable = hsw_block_disable, }; -static int hsw_enable_shim(struct sst_dsp *sst) -{ - int tries = 10; - u32 reg; - - /* enable shim */ - reg = readl(sst->addr.pci_cfg + SST_SHIM_PM_REG); - writel(reg & ~0x3, sst->addr.pci_cfg + SST_SHIM_PM_REG); - - /* check that ADSP shim is enabled */ - while (tries--) { - reg = sst_dsp_shim_read_unlocked(sst, SST_CSR); - if (reg != 0xffffffff) - return 0; - - msleep(1); - } - - return -ENODEV; -} - static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) { const struct sst_adsp_memregion *region; @@ -467,12 +621,16 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) region = lp_region; region_count = ARRAY_SIZE(lp_region); sst->addr.iram_offset = SST_LP_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET; sst->addr.shim_offset = SST_LP_SHIM_OFFSET; break; case SST_DEV_ID_WILDCAT_POINT: region = wpt_region; region_count = ARRAY_SIZE(wpt_region); sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET; sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; break; default: @@ -487,7 +645,7 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) } /* enable the DSP SHIM */ - ret = hsw_enable_shim(sst); + ret = hsw_set_dsp_D0(sst); if (ret < 0) { dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); return ret; @@ -497,10 +655,6 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) if (ret) return ret; - /* Enable Interrupt from both sides */ - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, 0x3, 0x0); - sst_dsp_shim_update_bits_unlocked(sst, SST_IMRD, - (0x3 | 0x1 << 16 | 0x3 << 21), 0x0); /* register DSP memory blocks - ideally we should get this from ACPI */ for (i = 0; i < region_count; i++) { @@ -532,6 +686,9 @@ static void hsw_free(struct sst_dsp *sst) struct sst_ops haswell_ops = { .reset = hsw_reset, .boot = hsw_boot, + .stall = hsw_stall, + .wake = hsw_wake, + .sleep = hsw_sleep, .write = sst_shim32_write, .read = sst_shim32_read, .write64 = sst_shim32_write64, diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index b6291516dbb..3f8c4823136 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -30,6 +30,7 @@ #include <linux/firmware.h> #include <linux/dma-mapping.h> #include <linux/debugfs.h> +#include <linux/pm_runtime.h> #include "sst-haswell-ipc.h" #include "sst-dsp.h" @@ -276,6 +277,7 @@ struct sst_hsw { struct sst_hsw_ipc_fw_version version; struct sst_module *scratch; bool fw_done; + struct sst_fw *sst_fw; /* stream */ struct list_head stream_list; @@ -289,6 +291,8 @@ struct sst_hsw { /* DX */ struct sst_hsw_ipc_dx_reply dx; + void *dx_context; + dma_addr_t dx_context_paddr; /* boot */ wait_queue_head_t boot_wait; @@ -1038,14 +1042,9 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw, trace_ipc_request("set stream volume", stream->reply.stream_hw_id); - if (channel > 1) + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) return -EINVAL; - if (stream->mute[channel]) { - stream->mute_volume[channel] = volume; - return 0; - } - header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); @@ -1053,9 +1052,28 @@ int sst_hsw_stream_set_volume(struct sst_hsw *hsw, header |= (stage_id << IPC_STG_ID_SHIFT); req = &stream->vol_req; - req->channel = channel; req->target_volume = volume; + /* set both at same time ? */ + if (channel == SST_HSW_CHANNELS_ALL) { + if (hsw->mute[0] && hsw->mute[1]) { + hsw->mute_volume[0] = hsw->mute_volume[1] = volume; + return 0; + } else if (hsw->mute[0]) + req->channel = 1; + else if (hsw->mute[1]) + req->channel = 0; + else + req->channel = SST_HSW_CHANNELS_ALL; + } else { + /* set only 1 channel */ + if (hsw->mute[channel]) { + hsw->mute_volume[channel] = volume; + return 0; + } + req->channel = channel; + } + ret = ipc_tx_message_wait(hsw, header, req, sizeof(*req), NULL, 0); if (ret < 0) { dev_err(hsw->dev, "error: set stream volume failed\n"); @@ -1134,8 +1152,11 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, trace_ipc_request("set mixer volume", volume); + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + /* set both at same time ? */ - if (channel == 2) { + if (channel == SST_HSW_CHANNELS_ALL) { if (hsw->mute[0] && hsw->mute[1]) { hsw->mute_volume[0] = hsw->mute_volume[1] = volume; return 0; @@ -1144,7 +1165,7 @@ int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, else if (hsw->mute[1]) req.channel = 0; else - req.channel = 0xffffffff; + req.channel = SST_HSW_CHANNELS_ALL; } else { /* set only 1 channel */ if (hsw->mute[channel]) { @@ -1256,10 +1277,6 @@ int sst_hsw_stream_set_channels(struct sst_hsw *hsw, return -EINVAL; } - /* stereo is only supported atm */ - if (channels != 2) - return -EINVAL; - stream->request.format.ch_num = channels; return 0; } @@ -1355,10 +1372,11 @@ int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, } int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, - u32 entry_point) + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime) { struct sst_hsw_module_map *map = &stream->request.map; + struct sst_dsp *dsp = sst_hsw_get_dsp(hsw); + struct sst_module *module = runtime->module; if (stream->commited) { dev_err(hsw->dev, "error: stream committed for set module\n"); @@ -1367,36 +1385,25 @@ int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, /* only support initial module atm */ map->module_entries_count = 1; - map->module_entries[0].module_id = module_id; - map->module_entries[0].entry_point = entry_point; - - return 0; -} - -int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set pmem\n"); - return -EINVAL; - } - - stream->request.persistent_mem.offset = offset; - stream->request.persistent_mem.size = size; - - return 0; -} - -int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, u32 offset, u32 size) -{ - if (stream->commited) { - dev_err(hsw->dev, "error: stream committed for set smem\n"); - return -EINVAL; - } - - stream->request.scratch_mem.offset = offset; - stream->request.scratch_mem.size = size; + map->module_entries[0].module_id = module->id; + map->module_entries[0].entry_point = module->entry; + + stream->request.persistent_mem.offset = + sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM); + stream->request.persistent_mem.size = module->persistent_size; + + stream->request.scratch_mem.offset = + sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM); + stream->request.scratch_mem.size = dsp->scratch_size; + + dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id, + runtime->id); + dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n", + stream->request.persistent_mem.offset, + stream->request.persistent_mem.size); + dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n", + stream->request.scratch_mem.offset, + stream->request.scratch_mem.size); return 0; } @@ -1630,6 +1637,10 @@ int sst_hsw_device_set_config(struct sst_hsw *hsw, config.clock_frequency = mclk; config.mode = mode; config.clock_divider = clock_divider; + if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) + config.channels = 4; + else + config.channels = 2; trace_hsw_device_config_req(&config); @@ -1673,34 +1684,283 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw, dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", dx->entries_no, state); - memcpy(&hsw->dx, dx, sizeof(*dx)); - return 0; + return ret; } -/* Used to save state into hsw->dx_reply */ -int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, - u32 *offset, u32 *size, u32 *source) +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset) { - struct sst_hsw_ipc_dx_memory_item *dx_mem; - struct sst_hsw_ipc_dx_reply *dx_reply; - int entry_no; + struct sst_dsp *dsp = hsw->dsp; + struct sst_module *module; + struct sst_module_runtime *runtime; + int err; - dx_reply = &hsw->dx; - entry_no = dx_reply->entries_no; + module = sst_module_get_from_id(dsp, mod_id); + if (module == NULL) { + dev_err(dsp->dev, "error: failed to get module %d for pcm\n", + mod_id); + return NULL; + } + + runtime = sst_module_runtime_new(module, mod_id, NULL); + if (runtime == NULL) { + dev_err(dsp->dev, "error: failed to create module %d runtime\n", + mod_id); + return NULL; + } + + err = sst_module_runtime_alloc_blocks(runtime, offset); + if (err < 0) { + dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n", + mod_id); + sst_module_runtime_free(runtime); + return NULL; + } + + dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id, + mod_id); + return runtime; +} + +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime) +{ + sst_module_runtime_free_blocks(runtime); + sst_module_runtime_free(runtime); +} + +#ifdef CONFIG_PM +static int sst_hsw_dx_state_dump(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret = 0; - trace_ipc_request("PM get Dx state", entry_no); + trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS); - if (item >= entry_no) + if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) { + dev_err(hsw->dev, + "error: number of FW context regions greater than %d\n", + SST_HSW_MAX_DX_REGIONS); + memset(&hsw->dx, 0, sizeof(hsw->dx)); return -EINVAL; + } + + ret = sst_dsp_dma_get_channel(sst, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + /* set on-demond mode on engine 0 channel 3 */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset, + sst->addr.lpe_base + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context dump failed\n"); + memset(&hsw->dx, 0, sizeof(hsw->dx)); + goto out; + } + } + } + +out: + sst_dsp_dma_put_channel(sst); + return ret; +} + +static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret; + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset, + hsw->dx_context_paddr + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context restore failed\n"); + return ret; + } + } + } + + return 0; +} + +static void sst_hsw_drop_all(struct sst_hsw *hsw) +{ + struct ipc_message *msg, *tmp; + unsigned long flags; + int tx_drop_cnt = 0, rx_drop_cnt = 0; - dx_mem = &dx_reply->mem_info[item]; - *offset = dx_mem->offset; - *size = dx_mem->size; - *source = dx_mem->source; + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&hsw->dsp->spinlock, flags); + + list_for_each_entry_safe(msg, tmp, &hsw->tx_list, list) { + list_move(&msg->list, &hsw->empty_list); + tx_drop_cnt++; + } + + list_for_each_entry_safe(msg, tmp, &hsw->rx_list, list) { + list_move(&msg->list, &hsw->empty_list); + rx_drop_cnt++; + } + + spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); + + if (tx_drop_cnt || rx_drop_cnt) + dev_err(hsw->dev, "dropped IPC msg RX=%d, TX=%d\n", + tx_drop_cnt, rx_drop_cnt); +} + +int sst_hsw_dsp_load(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + int ret; + + dev_dbg(hsw->dev, "loading audio DSP...."); + + ret = sst_dsp_wake(dsp); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to wake audio DSP\n"); + return -ENODEV; + } + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + ret = sst_fw_reload(hsw->sst_fw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW reload failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + sst_dsp_dma_put_channel(dsp); return 0; } +static int sst_hsw_dsp_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + int ret; + + dev_dbg(hsw->dev, "restoring audio DSP...."); + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + ret = sst_hsw_dx_state_restore(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW context restore failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + sst_dsp_dma_put_channel(dsp); + + /* wait for DSP boot completion */ + sst_dsp_boot(dsp); + + return ret; +} + +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) +{ + int ret; + + dev_dbg(hsw->dev, "audio dsp runtime suspend\n"); + + ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx); + if (ret < 0) + return ret; + + sst_dsp_stall(hsw->dsp); + + ret = sst_hsw_dx_state_dump(hsw); + if (ret < 0) + return ret; + + sst_hsw_drop_all(hsw); + + return 0; +} + +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) +{ + sst_fw_unload(hsw->sst_fw); + sst_block_free_scratch(hsw->dsp); + + hsw->boot_complete = false; + + sst_dsp_sleep(hsw->dsp); + + return 0; +} + +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) +{ + struct device *dev = hsw->dev; + int ret; + + dev_dbg(dev, "audio dsp runtime resume\n"); + + if (hsw->boot_complete) + return 1; /* tell caller no action is required */ + + ret = sst_hsw_dsp_restore(hsw); + if (ret < 0) + dev_err(dev, "error: audio DSP boot failure\n"); + + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); + return -EIO; + } + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(hsw, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) + dev_err(dev, "error: SSP re-initialization failed\n"); + + return ret; +} +#endif + static int msg_empty_list_init(struct sst_hsw *hsw) { int i; @@ -1718,12 +1978,6 @@ static int msg_empty_list_init(struct sst_hsw *hsw) return 0; } -void sst_hsw_set_scratch_module(struct sst_hsw *hsw, - struct sst_module *scratch) -{ - hsw->scratch = scratch; -} - struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) { return hsw->dsp; @@ -1738,7 +1992,6 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) { struct sst_hsw_ipc_fw_version version; struct sst_hsw *hsw; - struct sst_fw *hsw_sst_fw; int ret; dev_dbg(dev, "initialising Audio DSP IPC\n"); @@ -1780,12 +2033,19 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) goto dsp_err; } + /* allocate DMA buffer for context storage */ + hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, + SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); + if (hsw->dx_context == NULL) { + ret = -ENOMEM; + goto dma_err; + } + /* keep the DSP in reset state for base FW loading */ sst_dsp_reset(hsw->dsp); - hsw_sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); - - if (hsw_sst_fw == NULL) { + hsw->sst_fw = sst_fw_new(hsw->dsp, pdata->fw, hsw); + if (hsw->sst_fw == NULL) { ret = -ENODEV; dev_err(dev, "error: failed to load firmware\n"); goto fw_err; @@ -1797,7 +2057,9 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) msecs_to_jiffies(IPC_BOOT_MSECS)); if (ret == 0) { ret = -EIO; - dev_err(hsw->dev, "error: ADSP boot timeout\n"); + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); goto boot_err; } @@ -1816,8 +2078,11 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) boot_err: sst_dsp_reset(hsw->dsp); - sst_fw_free(hsw_sst_fw); + sst_fw_free(hsw->sst_fw); fw_err: + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); +dma_err: sst_dsp_free(hsw->dsp); dsp_err: kthread_stop(hsw->tx_thread); @@ -1834,6 +2099,8 @@ void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) sst_dsp_reset(hsw->dsp); sst_fw_free_all(hsw->dsp); + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); sst_dsp_free(hsw->dsp); kfree(hsw->scratch); kthread_stop(hsw->tx_thread); diff --git a/sound/soc/intel/sst-haswell-ipc.h b/sound/soc/intel/sst-haswell-ipc.h index 2ac194a6d04..138e894ab41 100644 --- a/sound/soc/intel/sst-haswell-ipc.h +++ b/sound/soc/intel/sst-haswell-ipc.h @@ -21,8 +21,10 @@ #include <linux/kernel.h> #include <linux/platform_device.h> -#define SST_HSW_NO_CHANNELS 2 +#define SST_HSW_NO_CHANNELS 4 #define SST_HSW_MAX_DX_REGIONS 14 +#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024) +#define SST_HSW_CHANNELS_ALL 0xffffffff #define SST_HSW_FW_LOG_CONFIG_DWORDS 12 #define SST_HSW_GLOBAL_LOG 15 @@ -40,6 +42,7 @@ struct sst_hsw_stream; struct sst_hsw_log_stream; struct sst_pdata; struct sst_module; +struct sst_module_runtime; extern struct sst_ops haswell_ops; /* Stream Allocate Path ID */ @@ -84,6 +87,7 @@ enum sst_hsw_device_mclk { enum sst_hsw_device_mode { SST_HSW_DEVICE_CLOCK_SLAVE = 0, SST_HSW_DEVICE_CLOCK_MASTER = 1, + SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2, }; /* DX Power State */ @@ -295,7 +299,8 @@ struct sst_hsw_ipc_device_config_req { u32 clock_frequency; u32 mode; u16 clock_divider; - u16 reserved; + u8 channels; + u8 reserved; } __attribute__((packed)); /* Audio Data formats */ @@ -430,8 +435,7 @@ int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, enum sst_hsw_interleaving style); int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, - struct sst_hsw_stream *stream, enum sst_hsw_module_id module_id, - u32 entry_point); + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime); int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, struct sst_hsw_stream *stream, u32 offset, u32 size); int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, @@ -484,7 +488,16 @@ int sst_hsw_dx_get_state(struct sst_hsw *hsw, u32 item, int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); -void sst_hsw_set_scratch_module(struct sst_hsw *hsw, - struct sst_module *scratch); + +/* runtime module management */ +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset); +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime); + +/* PM */ +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw); +int sst_hsw_dsp_load(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw); #endif diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index 33fc5c3abf5..0180b386c42 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -18,6 +18,7 @@ #include <linux/dma-mapping.h> #include <linux/slab.h> #include <linux/delay.h> +#include <linux/pm_runtime.h> #include <asm/page.h> #include <asm/pgtable.h> #include <sound/core.h> @@ -73,6 +74,13 @@ static const u32 volume_map[] = { #define HSW_PCM_PERIODS_MAX 64 #define HSW_PCM_PERIODS_MIN 2 +#define HSW_PCM_DAI_ID_SYSTEM 0 +#define HSW_PCM_DAI_ID_OFFLOAD0 1 +#define HSW_PCM_DAI_ID_OFFLOAD1 2 +#define HSW_PCM_DAI_ID_LOOPBACK 3 +#define HSW_PCM_DAI_ID_CAPTURE 4 + + static const struct snd_pcm_hardware hsw_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | @@ -89,22 +97,39 @@ static const struct snd_pcm_hardware hsw_pcm_hardware = { .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, }; +struct hsw_pcm_module_map { + int dai_id; + enum sst_hsw_module_id mod_id; +}; + /* private data for each PCM DSP stream */ struct hsw_pcm_data { int dai_id; struct sst_hsw_stream *stream; + struct sst_module_runtime *runtime; + struct sst_module_runtime_context context; + struct snd_pcm *hsw_pcm; u32 volume[2]; struct snd_pcm_substream *substream; struct snd_compr_stream *cstream; unsigned int wpos; struct mutex mutex; bool allocated; + int persistent_offset; +}; + +enum hsw_pm_state { + HSW_PM_STATE_D3 = 0, + HSW_PM_STATE_D0 = 1, }; /* private data for the driver */ struct hsw_priv_data { /* runtime DSP */ struct sst_hsw *hsw; + struct device *dev; + enum hsw_pm_state pm_state; + struct snd_soc_card *soc_card; /* page tables */ struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; @@ -138,21 +163,25 @@ static inline unsigned int hsw_ipc_to_mixer(u32 value) static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; struct sst_hsw *hsw = pdata->hsw; u32 volume; mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); if (!pcm_data->stream) { pcm_data->volume[0] = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); pcm_data->volume[1] = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return 0; } @@ -160,7 +189,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, if (ucontrol->value.integer.value[0] == ucontrol->value.integer.value[1]) { volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 2, volume); + /* apply volume value to all channels */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); } else { volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); @@ -168,6 +198,8 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); } + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return 0; } @@ -175,21 +207,25 @@ static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_platform_get_drvdata(platform); struct hsw_pcm_data *pcm_data = &pdata->pcm[mc->reg]; struct sst_hsw *hsw = pdata->hsw; u32 volume; mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); if (!pcm_data->stream) { ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(pcm_data->volume[0]); ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(pcm_data->volume[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return 0; } @@ -198,6 +234,9 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return 0; @@ -206,16 +245,18 @@ static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, static int hsw_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); struct sst_hsw *hsw = pdata->hsw; u32 volume; + pm_runtime_get_sync(pdata->dev); + if (ucontrol->value.integer.value[0] == ucontrol->value.integer.value[1]) { volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); - sst_hsw_mixer_set_volume(hsw, 0, 2, volume); + sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); } else { volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); @@ -225,23 +266,28 @@ static int hsw_volume_put(struct snd_kcontrol *kcontrol, sst_hsw_mixer_set_volume(hsw, 0, 1, volume); } + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); return 0; } static int hsw_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); - struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(cmpnt); + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct hsw_priv_data *pdata = snd_soc_platform_get_drvdata(platform); struct sst_hsw *hsw = pdata->hsw; unsigned int volume = 0; + pm_runtime_get_sync(pdata->dev); sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); return 0; } @@ -252,23 +298,19 @@ static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); static const struct snd_kcontrol_new hsw_volume_controls[] = { /* Global DSP volume */ SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, - ARRAY_SIZE(volume_map) -1, 0, + ARRAY_SIZE(volume_map) - 1, 0, hsw_volume_get, hsw_volume_put, hsw_vol_tlv), /* Offload 0 volume */ SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, - ARRAY_SIZE(volume_map), 0, + ARRAY_SIZE(volume_map) - 1, 0, hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), /* Offload 1 volume */ SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, - ARRAY_SIZE(volume_map), 0, - hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), - /* Loopback volume */ - SOC_DOUBLE_EXT_TLV("Loopback Capture Volume", 3, 0, 8, - ARRAY_SIZE(volume_map), 0, + ARRAY_SIZE(volume_map) - 1, 0, hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), /* Mic Capture volume */ - SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, - ARRAY_SIZE(volume_map), 0, + SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 0, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), }; @@ -354,8 +396,14 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, /* DSP stream type depends on DAI ID */ switch (rtd->cpu_dai->id) { case 0: - stream_type = SST_HSW_STREAM_TYPE_SYSTEM; - module_id = SST_HSW_MODULE_PCM_SYSTEM; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_type = SST_HSW_STREAM_TYPE_SYSTEM; + module_id = SST_HSW_MODULE_PCM_SYSTEM; + } + else { + stream_type = SST_HSW_STREAM_TYPE_CAPTURE; + module_id = SST_HSW_MODULE_PCM_CAPTURE; + } break; case 1: case 2: @@ -368,10 +416,6 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, path_id = SST_HSW_STREAM_PATH_SSP0_OUT; module_id = SST_HSW_MODULE_PCM_REFERENCE; break; - case 4: - stream_type = SST_HSW_STREAM_TYPE_CAPTURE; - module_id = SST_HSW_MODULE_PCM_CAPTURE; - break; default: dev_err(rtd->dev, "error: invalid DAI ID %d\n", rtd->cpu_dai->id); @@ -421,13 +465,7 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, return ret; } - /* we only support stereo atm */ channels = params_channels(params); - if (channels != 2) { - dev_err(rtd->dev, "error: invalid channels %d\n", channels); - return -EINVAL; - } - map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); sst_hsw_stream_set_map_config(hsw, pcm_data->stream, map, SST_HSW_CHANNEL_CONFIG_STEREO); @@ -478,35 +516,23 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - /* we use hardcoded memory offsets atm, will be updated for new FW */ - if (stream_type == SST_HSW_STREAM_TYPE_CAPTURE) { - sst_hsw_stream_set_module_info(hsw, pcm_data->stream, - SST_HSW_MODULE_PCM_CAPTURE, module_data->entry); - sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, - 0x449400, 0x4000); - sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, - 0x400000, 0); - } else { /* stream_type == SST_HSW_STREAM_TYPE_SYSTEM */ - sst_hsw_stream_set_module_info(hsw, pcm_data->stream, - SST_HSW_MODULE_PCM_SYSTEM, module_data->entry); - - sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, - module_data->offset, module_data->size); - sst_hsw_stream_set_pmemory_info(hsw, pcm_data->stream, - 0x44d400, 0x3800); - - sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, - module_data->offset, module_data->size); - sst_hsw_stream_set_smemory_info(hsw, pcm_data->stream, - 0x400000, 0); - } + sst_hsw_stream_set_module_info(hsw, pcm_data->stream, + pcm_data->runtime); ret = sst_hsw_stream_commit(hsw, pcm_data->stream); if (ret < 0) { dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); return ret; } - pcm_data->allocated = true; + + if (!pcm_data->allocated) { + /* Set previous saved volume */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 0, pcm_data->volume[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 1, pcm_data->volume[1]); + pcm_data->allocated = true; + } ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); if (ret < 0) @@ -558,7 +584,7 @@ static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) pos = frames_to_bytes(runtime, (runtime->control->appl_ptr % runtime->buffer_size)); - dev_dbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); + dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); /* let alsa know we have play a period */ snd_pcm_period_elapsed(substream); @@ -580,7 +606,7 @@ static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) offset = bytes_to_frames(runtime, position); ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); - dev_dbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", + dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", position, ppos); return offset; } @@ -596,6 +622,7 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) pcm_data = &pdata->pcm[rtd->cpu_dai->id]; mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); snd_soc_pcm_set_drvdata(rtd, pcm_data); pcm_data->substream = substream; @@ -606,16 +633,12 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) hsw_notify_pointer, pcm_data); if (pcm_data->stream == NULL) { dev_err(rtd->dev, "error: failed to create stream\n"); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return -EINVAL; } - /* Set previous saved volume */ - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 0, pcm_data->volume[0]); - sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, - 1, pcm_data->volume[1]); - mutex_unlock(&pcm_data->mutex); return 0; } @@ -645,6 +668,8 @@ static int hsw_pcm_close(struct snd_pcm_substream *substream) pcm_data->stream = NULL; out: + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); mutex_unlock(&pcm_data->mutex); return ret; } @@ -660,6 +685,56 @@ static struct snd_pcm_ops hsw_pcm_ops = { .page = snd_pcm_sgbuf_ops_page, }; +/* static mappings between PCMs and modules - may be dynamic in future */ +static struct hsw_pcm_module_map mod_map[] = { + {HSW_PCM_DAI_ID_SYSTEM, SST_HSW_MODULE_PCM_SYSTEM}, + {HSW_PCM_DAI_ID_OFFLOAD0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_OFFLOAD1, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_LOOPBACK, SST_HSW_MODULE_PCM_REFERENCE}, + {HSW_PCM_DAI_ID_CAPTURE, SST_HSW_MODULE_PCM_CAPTURE}, +}; + +static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[i]; + + /* create new runtime module, use same offset if recreated */ + pcm_data->runtime = sst_hsw_runtime_module_create(hsw, + mod_map[i].mod_id, pcm_data->persistent_offset); + if (pcm_data->runtime == NULL) + goto err; + pcm_data->persistent_offset = + pcm_data->runtime->persistent_offset; + } + + return 0; + +err: + for (--i; i >= 0; i--) { + pcm_data = &pdata->pcm[i]; + sst_hsw_runtime_module_free(pcm_data->runtime); + } + + return -ENODEV; +} + +static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) +{ + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[i]; + + sst_hsw_runtime_module_free(pcm_data->runtime); + } +} + static void hsw_pcm_free(struct snd_pcm *pcm) { snd_pcm_lib_preallocate_free_for_all(pcm); @@ -670,6 +745,7 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) struct snd_pcm *pcm = rtd->pcm; struct snd_soc_platform *platform = rtd->platform; struct sst_pdata *pdata = dev_get_platdata(platform->dev); + struct hsw_priv_data *priv_data = dev_get_drvdata(platform->dev); struct device *dev = pdata->dma_dev; int ret = 0; @@ -686,18 +762,18 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) return ret; } } + priv_data->pcm[rtd->cpu_dai->id].hsw_pcm = pcm; return ret; } #define HSW_FORMATS \ - (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S8) + (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) static struct snd_soc_dai_driver hsw_dais[] = { { .name = "System Pin", + .id = HSW_PCM_DAI_ID_SYSTEM, .playback = { .stream_name = "System Playback", .channels_min = 2, @@ -705,10 +781,18 @@ static struct snd_soc_dai_driver hsw_dais[] = { .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, }, { /* PCM */ .name = "Offload0 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD0, .playback = { .stream_name = "Offload0 Playback", .channels_min = 2, @@ -720,6 +804,7 @@ static struct snd_soc_dai_driver hsw_dais[] = { { /* PCM */ .name = "Offload1 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD1, .playback = { .stream_name = "Offload1 Playback", .channels_min = 2, @@ -730,6 +815,7 @@ static struct snd_soc_dai_driver hsw_dais[] = { }, { .name = "Loopback Pin", + .id = HSW_PCM_DAI_ID_LOOPBACK, .capture = { .stream_name = "Loopback Capture", .channels_min = 2, @@ -738,16 +824,6 @@ static struct snd_soc_dai_driver hsw_dais[] = { .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, }, }, - { - .name = "Capture Pin", - .capture = { - .stream_name = "Analog Capture", - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, - }, - }, }; static const struct snd_soc_dapm_widget widgets[] = { @@ -778,9 +854,20 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) { struct hsw_priv_data *priv_data = snd_soc_platform_get_drvdata(platform); struct sst_pdata *pdata = dev_get_platdata(platform->dev); - struct device *dma_dev = pdata->dma_dev; + struct device *dma_dev, *dev; int i, ret = 0; + if (!pdata) + return -ENODEV; + + dev = platform->dev; + dma_dev = pdata->dma_dev; + + priv_data->hsw = pdata->dsp; + priv_data->dev = platform->dev; + priv_data->pm_state = HSW_PM_STATE_D0; + priv_data->soc_card = platform->component.card; + /* allocate DSP buffer page tables */ for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { @@ -803,6 +890,16 @@ static int hsw_pcm_probe(struct snd_soc_platform *platform) } } + /* allocate runtime modules */ + hsw_pcm_create_modules(priv_data); + + /* enable runtime PM with auto suspend */ + pm_runtime_set_autosuspend_delay(platform->dev, + SST_RUNTIME_SUSPEND_DELAY); + pm_runtime_use_autosuspend(platform->dev); + pm_runtime_enable(platform->dev); + pm_runtime_idle(platform->dev); + return 0; err: @@ -821,6 +918,9 @@ static int hsw_pcm_remove(struct snd_soc_platform *platform) snd_soc_platform_get_drvdata(platform); int i; + pm_runtime_disable(platform->dev); + hsw_pcm_free_modules(priv_data); + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { if (hsw_dais[i].playback.channels_min) snd_dma_free_pages(&priv_data->dmab[i][0]); @@ -898,10 +998,181 @@ static int hsw_pcm_dev_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_RUNTIME + +static int hsw_pcm_runtime_idle(struct device *dev) +{ + return 0; +} + +static int hsw_pcm_runtime_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + + if (pdata->pm_state == HSW_PM_STATE_D3) + return 0; + + sst_hsw_dsp_runtime_suspend(hsw); + sst_hsw_dsp_runtime_sleep(hsw); + pdata->pm_state = HSW_PM_STATE_D3; + + return 0; +} + +static int hsw_pcm_runtime_resume(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state == HSW_PM_STATE_D0) + return 0; + + ret = sst_hsw_dsp_load(hsw); + if (ret < 0) { + dev_err(dev, "failed to reload %d\n", ret); + return ret; + } + + ret = hsw_pcm_create_modules(pdata); + if (ret < 0) { + dev_err(dev, "failed to create modules %d\n", ret); + return ret; + } + + ret = sst_hsw_dsp_runtime_resume(hsw); + if (ret < 0) + return ret; + else if (ret == 1) /* no action required */ + return 0; + + pdata->pm_state = HSW_PM_STATE_D0; + return ret; +} + +#else +#define hsw_pcm_runtime_idle NULL +#define hsw_pcm_runtime_suspend NULL +#define hsw_pcm_runtime_resume NULL +#endif + +#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_RUNTIME) + +static void hsw_pcm_complete(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state == HSW_PM_STATE_D0) + return; + + err = sst_hsw_dsp_load(hsw); + if (err < 0) { + dev_err(dev, "failed to reload %d\n", err); + return; + } + + err = hsw_pcm_create_modules(pdata); + if (err < 0) { + dev_err(dev, "failed to create modules %d\n", err); + return; + } + + for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) { + pcm_data = &pdata->pcm[i]; + + if (!pcm_data->substream) + continue; + + err = sst_module_runtime_restore(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to restore context for PCM %d\n", i); + } + + snd_soc_resume(pdata->soc_card->dev); + + err = sst_hsw_dsp_runtime_resume(hsw); + if (err < 0) + return; + else if (err == 1) /* no action required */ + return; + + pdata->pm_state = HSW_PM_STATE_D0; + return; +} + +static int hsw_pcm_prepare(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state == HSW_PM_STATE_D3) + return 0; + /* suspend all active streams */ + for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) { + pcm_data = &pdata->pcm[i]; + + if (!pcm_data->substream) + continue; + dev_dbg(dev, "suspending pcm %d\n", i); + snd_pcm_suspend_all(pcm_data->hsw_pcm); + + /* We need to wait until the DSP FW stops the streams */ + msleep(2); + } + + snd_soc_suspend(pdata->soc_card->dev); + snd_soc_poweroff(pdata->soc_card->dev); + + /* enter D3 state and stall */ + sst_hsw_dsp_runtime_suspend(hsw); + + /* preserve persistent memory */ + for (i = 0; i < HSW_PCM_DAI_ID_CAPTURE + 1; i++) { + pcm_data = &pdata->pcm[i]; + + if (!pcm_data->substream) + continue; + + dev_dbg(dev, "saving context pcm %d\n", i); + err = sst_module_runtime_save(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to save context for PCM %d\n", i); + } + + /* put the DSP to sleep */ + sst_hsw_dsp_runtime_sleep(hsw); + pdata->pm_state = HSW_PM_STATE_D3; + + return 0; +} + +#else +#define hsw_pcm_prepare NULL +#define hsw_pcm_complete NULL +#endif + +static const struct dev_pm_ops hsw_pcm_pm = { + .runtime_idle = hsw_pcm_runtime_idle, + .runtime_suspend = hsw_pcm_runtime_suspend, + .runtime_resume = hsw_pcm_runtime_resume, + .prepare = hsw_pcm_prepare, + .complete = hsw_pcm_complete, +}; + static struct platform_driver hsw_pcm_driver = { .driver = { .name = "haswell-pcm-audio", .owner = THIS_MODULE, + .pm = &hsw_pcm_pm, + }, .probe = hsw_pcm_dev_probe, diff --git a/sound/soc/intel/sst-mfld-platform-compress.c b/sound/soc/intel/sst-mfld-platform-compress.c index 59467775c9b..39516898646 100644 --- a/sound/soc/intel/sst-mfld-platform-compress.c +++ b/sound/soc/intel/sst-mfld-platform-compress.c @@ -67,8 +67,11 @@ static int sst_platform_compr_open(struct snd_compr_stream *cstream) goto out_ops; } stream->compr_ops = sst->compr_ops; - stream->id = 0; + + /* Turn on LPE */ + sst->compr_ops->power(sst->dev, true); + sst_set_stream_status(stream, SST_PLATFORM_INIT); runtime->private_data = stream; return 0; @@ -83,6 +86,9 @@ static int sst_platform_compr_free(struct snd_compr_stream *cstream) int ret_val = 0, str_id; stream = cstream->runtime->private_data; + /* Turn off LPE */ + sst->compr_ops->power(sst->dev, false); + /*need to check*/ str_id = stream->id; if (str_id) diff --git a/sound/soc/intel/sst-mfld-platform-pcm.c b/sound/soc/intel/sst-mfld-platform-pcm.c index aa9b600dfc9..6032f18693b 100644 --- a/sound/soc/intel/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/sst-mfld-platform-pcm.c @@ -101,35 +101,11 @@ static struct sst_dev_stream_map dpcm_strm_map[] = { {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, }; -/* MFLD - MSIC */ -static struct snd_soc_dai_driver sst_platform_dai[] = { +static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) { - .name = "Headset-cpu-dai", - .id = 0, - .playback = { - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE, - }, - .capture = { - .channels_min = 1, - .channels_max = 5, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S24_LE, - }, -}, -{ - .name = "Compress-cpu-dai", - .compress_dai = 1, - .playback = { - .channels_min = SST_STEREO, - .channels_max = SST_STEREO, - .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, -}, -}; + + return sst_send_pipe_gains(dai, stream, mute); +} /* helper functions */ void sst_set_stream_status(struct sst_runtime_stream *stream, @@ -451,12 +427,133 @@ static int sst_media_hw_free(struct snd_pcm_substream *substream, return snd_pcm_lib_free_pages(substream); } +static int sst_enable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (!dai->active) { + ret = sst_handle_vb_timer(dai, true); + if (ret) + return ret; + ret = send_ssp_cmd(dai, dai->name, 1); + } + return ret; +} + +static void sst_disable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!dai->active) { + send_ssp_cmd(dai, dai->name, 0); + sst_handle_vb_timer(dai, false); + } +} + static struct snd_soc_dai_ops sst_media_dai_ops = { .startup = sst_media_open, .shutdown = sst_media_close, .prepare = sst_media_prepare, .hw_params = sst_media_hw_params, .hw_free = sst_media_hw_free, + .mute_stream = sst_media_digital_mute, +}; + +static struct snd_soc_dai_ops sst_compr_dai_ops = { + .mute_stream = sst_media_digital_mute, +}; + +static struct snd_soc_dai_ops sst_be_dai_ops = { + .startup = sst_enable_ssp, + .shutdown = sst_disable_ssp, +}; + +static struct snd_soc_dai_driver sst_platform_dai[] = { +{ + .name = "media-cpu-dai", + .ops = &sst_media_dai_ops, + .playback = { + .stream_name = "Headset Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Headset Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "compress-cpu-dai", + .compress_dai = 1, + .ops = &sst_compr_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +/* BE CPU Dais */ +{ + .name = "ssp0-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp0 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp0 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp1-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp1 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp1 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp2-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp2 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp2 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, }; static int sst_platform_open(struct snd_pcm_substream *substream) @@ -609,6 +706,7 @@ static int sst_platform_probe(struct platform_device *pdev) pdata->pdev_strm_map = dpcm_strm_map; pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); drv->pdata = pdata; + drv->pdev = pdev; mutex_init(&drv->lock); dev_set_drvdata(&pdev->dev, drv); diff --git a/sound/soc/intel/sst-mfld-platform.h b/sound/soc/intel/sst-mfld-platform.h index 19f83ec5161..79c8d1246a8 100644 --- a/sound/soc/intel/sst-mfld-platform.h +++ b/sound/soc/intel/sst-mfld-platform.h @@ -117,6 +117,7 @@ struct compress_sst_ops { int (*get_codec_caps)(struct snd_compr_codec_caps *codec); int (*set_metadata)(struct device *dev, unsigned int str_id, struct snd_compr_metadata *mdata); + int (*power)(struct device *dev, bool state); }; struct sst_ops { @@ -153,6 +154,10 @@ struct sst_device { struct sst_data; int sst_dsp_init_v2_dpcm(struct snd_soc_platform *platform); +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute); +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable); +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable); + void sst_set_stream_status(struct sst_runtime_stream *stream, int state); int sst_fill_stream_params(void *substream, const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress); diff --git a/sound/soc/intel/sst/Makefile b/sound/soc/intel/sst/Makefile new file mode 100644 index 00000000000..fd21726361b --- /dev/null +++ b/sound/soc/intel/sst/Makefile @@ -0,0 +1,7 @@ +snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o +snd-intel-sst-pci-objs += sst_pci.o +snd-intel-sst-acpi-objs += sst_acpi.o + +obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o +obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o +obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c new file mode 100644 index 00000000000..8a8d56a146e --- /dev/null +++ b/sound/soc/intel/sst/sst.c @@ -0,0 +1,437 @@ +/* + * sst.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/async.h> +#include <linux/acpi.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); +MODULE_LICENSE("GPL v2"); + +static inline bool sst_is_process_reply(u32 msg_id) +{ + return ((msg_id & PROCESS_MSG) ? true : false); +} + +static inline bool sst_validate_mailbox_size(unsigned int size) +{ + return ((size <= SST_MAILBOX_SIZE) ? true : false); +} + +static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) +{ + union interrupt_reg_mrfld isr; + union ipc_header_mrfld header; + union sst_imr_reg_mrfld imr; + struct ipc_post *msg = NULL; + unsigned int size = 0; + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + irqreturn_t retval = IRQ_HANDLED; + + /* Interrupt arrived, check src */ + isr.full = sst_shim_read64(drv->shim, SST_ISRX); + + if (isr.part.done_interrupt) { + /* Clear done bit */ + spin_lock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, + drv->ipc_reg.ipcx); + header.p.header_high.part.done = 0; + sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); + + /* write 1 to clear status register */; + isr.part.done_interrupt = 1; + sst_shim_write64(drv->shim, SST_ISRX, isr.full); + spin_unlock(&drv->ipc_spin_lock); + + /* we can send more messages to DSP so trigger work */ + queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); + retval = IRQ_HANDLED; + } + + if (isr.part.busy_interrupt) { + /* message from dsp so copy that */ + spin_lock(&drv->ipc_spin_lock); + imr.full = sst_shim_read64(drv->shim, SST_IMRX); + imr.part.busy_interrupt = 1; + sst_shim_write64(drv->shim, SST_IMRX, imr.full); + spin_unlock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); + + if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { + drv->ops->clear_interrupt(drv); + return IRQ_HANDLED; + } + + if (header.p.header_high.part.large) { + size = header.p.header_low_payload; + if (sst_validate_mailbox_size(size)) { + memcpy_fromio(msg->mailbox_data, + drv->mailbox + drv->mailbox_recv_offset, size); + } else { + dev_err(drv->dev, + "Mailbox not copied, payload size is: %u\n", size); + header.p.header_low_payload = 0; + } + } + + msg->mrfld_header = header; + msg->is_process_reply = + sst_is_process_reply(header.p.header_high.part.msg_id); + spin_lock(&drv->rx_msg_lock); + list_add_tail(&msg->node, &drv->rx_list); + spin_unlock(&drv->rx_msg_lock); + drv->ops->clear_interrupt(drv); + retval = IRQ_WAKE_THREAD; + } + return retval; +} + +static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{ + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + struct ipc_post *__msg, *msg = NULL; + unsigned long irq_flags; + + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + if (list_empty(&drv->rx_list)) { + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; + } + + list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { + list_del(&msg->node); + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + if (msg->is_process_reply) + drv->ops->process_message(msg); + else + drv->ops->process_reply(drv, msg); + + if (msg->is_large) + kfree(msg->mailbox_data); + kfree(msg); + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + } + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; +} + +static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) +{ + int ret = 0; + + ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, + IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, + true, true, false, true); + + if (ret < 0) { + dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); + return -EIO; + } + + return 0; +} + + +static struct intel_sst_ops mrfld_ops = { + .interrupt = intel_sst_interrupt_mrfld, + .irq_thread = intel_sst_irq_thread_mrfld, + .clear_interrupt = intel_sst_clear_intr_mrfld, + .start = sst_start_mrfld, + .reset = intel_sst_reset_dsp_mrfld, + .post_message = sst_post_message_mrfld, + .process_reply = sst_process_reply_mrfld, + .save_dsp_context = sst_save_dsp_context_v2, + .alloc_stream = sst_alloc_stream_mrfld, + .post_download = sst_post_download_mrfld, +}; + +int sst_driver_ops(struct intel_sst_drv *sst) +{ + + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + case SST_CHV_ACPI_ID: + sst->tstamp = SST_TIME_STAMP_MRFLD; + sst->ops = &mrfld_ops; + return 0; + + default: + dev_err(sst->dev, + "SST Driver capablities missing for dev_id: %x", sst->dev_id); + return -EINVAL; + }; +} + +void sst_process_pending_msg(struct work_struct *work) +{ + struct intel_sst_drv *ctx = container_of(work, + struct intel_sst_drv, ipc_post_msg_wq); + + ctx->ops->post_message(ctx, NULL, false); +} + +static int sst_workqueue_init(struct intel_sst_drv *ctx) +{ + INIT_LIST_HEAD(&ctx->memcpy_list); + INIT_LIST_HEAD(&ctx->rx_list); + INIT_LIST_HEAD(&ctx->ipc_dispatch_list); + INIT_LIST_HEAD(&ctx->block_list); + INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg); + init_waitqueue_head(&ctx->wait_queue); + + ctx->post_msg_wq = + create_singlethread_workqueue("sst_post_msg_wq"); + if (!ctx->post_msg_wq) + return -EBUSY; + return 0; +} + +static void sst_init_locks(struct intel_sst_drv *ctx) +{ + mutex_init(&ctx->sst_lock); + spin_lock_init(&ctx->rx_msg_lock); + spin_lock_init(&ctx->ipc_spin_lock); + spin_lock_init(&ctx->block_lock); +} + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id) +{ + *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL); + if (!(*ctx)) + return -ENOMEM; + + (*ctx)->dev = dev; + (*ctx)->dev_id = dev_id; + + return 0; +} +EXPORT_SYMBOL_GPL(sst_alloc_drv_context); + +int sst_context_init(struct intel_sst_drv *ctx) +{ + int ret = 0, i; + + if (!ctx->pdata) + return -EINVAL; + + if (!ctx->pdata->probe_data) + return -EINVAL; + + memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info)); + + ret = sst_driver_ops(ctx); + if (ret != 0) + return -EINVAL; + + sst_init_locks(ctx); + sst_set_fw_state_locked(ctx, SST_RESET); + + /* pvt_id 0 reserved for async messages */ + ctx->pvt_id = 1; + ctx->stream_cnt = 0; + ctx->fw_in_mem = NULL; + /* we use memcpy, so set to 0 */ + ctx->use_dma = 0; + ctx->use_lli = 0; + + if (sst_workqueue_init(ctx)) + return -EINVAL; + + ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off; + ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset; + ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset; + + dev_info(ctx->dev, "Got drv data max stream %d\n", + ctx->info.max_streams); + + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + memset(stream, 0, sizeof(*stream)); + stream->pipe_id = PIPE_RSVD; + mutex_init(&stream->lock); + } + + /* Register the ISR */ + ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt, + ctx->ops->irq_thread, 0, SST_DRV_NAME, + ctx); + if (ret) + goto do_free_mem; + + dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num); + + /* default intr are unmasked so set this as masked */ + sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038); + + ctx->qos = devm_kzalloc(ctx->dev, + sizeof(struct pm_qos_request), GFP_KERNEL); + if (!ctx->qos) { + ret = -ENOMEM; + goto do_free_mem; + } + pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); + ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, + ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); + if (ret) { + dev_err(ctx->dev, "Firmware download failed:%d\n", ret); + goto do_free_mem; + } + sst_register(ctx->dev); + return 0; + +do_free_mem: + destroy_workqueue(ctx->post_msg_wq); + return ret; +} +EXPORT_SYMBOL_GPL(sst_context_init); + +void sst_context_cleanup(struct intel_sst_drv *ctx) +{ + pm_runtime_get_noresume(ctx->dev); + pm_runtime_disable(ctx->dev); + sst_unregister(ctx->dev); + sst_set_fw_state_locked(ctx, SST_SHUTDOWN); + flush_scheduled_work(); + destroy_workqueue(ctx->post_msg_wq); + pm_qos_remove_request(ctx->qos); + kfree(ctx->fw_sg_list.src); + kfree(ctx->fw_sg_list.dst); + ctx->fw_sg_list.list_len = 0; + kfree(ctx->fw_in_mem); + ctx->fw_in_mem = NULL; + sst_memcpy_free_resources(ctx); + ctx = NULL; +} +EXPORT_SYMBOL_GPL(sst_context_cleanup); + +static inline void sst_save_shim64(struct intel_sst_drv *ctx, + void __iomem *shim, + struct sst_shim_regs64 *shim_regs) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); + + shim_regs->imrx = sst_shim_read64(shim, SST_IMRX), + + spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); +} + +static inline void sst_restore_shim64(struct intel_sst_drv *ctx, + void __iomem *shim, + struct sst_shim_regs64 *shim_regs) +{ + unsigned long irq_flags; + + /* + * we only need to restore IMRX for this case, rest will be + * initialize by FW or driver when firmware is loaded + */ + spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); + sst_shim_write64(shim, SST_IMRX, shim_regs->imrx), + spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); +} + +void sst_configure_runtime_pm(struct intel_sst_drv *ctx) +{ + pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY); + pm_runtime_use_autosuspend(ctx->dev); + /* + * For acpi devices, the actual physical device state is + * initially active. So change the state to active before + * enabling the pm + */ + pm_runtime_enable(ctx->dev); + + if (acpi_disabled) + pm_runtime_set_active(ctx->dev); + else + pm_runtime_put_noidle(ctx->dev); + + sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); +} +EXPORT_SYMBOL_GPL(sst_configure_runtime_pm); + +static int intel_sst_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state == SST_RESET) { + dev_dbg(dev, "LPE is already in RESET state, No action\n"); + return 0; + } + /* save fw context */ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + synchronize_irq(ctx->irq_num); + flush_workqueue(ctx->post_msg_wq); + + /* save the shim registers because PMC doesn't save state */ + sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); + + return ret; +} + +static int intel_sst_runtime_resume(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state == SST_RESET) { + ret = sst_load_fw(ctx); + if (ret) { + dev_err(dev, "FW download fail %d\n", ret); + sst_set_fw_state_locked(ctx, SST_RESET); + } + } + return ret; +} + +const struct dev_pm_ops intel_sst_pm = { + .runtime_suspend = intel_sst_runtime_suspend, + .runtime_resume = intel_sst_runtime_resume, +}; +EXPORT_SYMBOL_GPL(intel_sst_pm); diff --git a/sound/soc/intel/sst/sst.h b/sound/soc/intel/sst/sst.h new file mode 100644 index 00000000000..7f4bbfcbc6f --- /dev/null +++ b/sound/soc/intel/sst/sst.h @@ -0,0 +1,546 @@ +/* + * sst.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Common private declarations for SST + */ +#ifndef __SST_H__ +#define __SST_H__ + +#include <linux/firmware.h> + +/* driver names */ +#define SST_DRV_NAME "intel_sst_driver" +#define SST_MRFLD_PCI_ID 0x119A +#define SST_BYT_ACPI_ID 0x80860F28 +#define SST_CHV_ACPI_ID 0x808622A8 + +#define SST_SUSPEND_DELAY 2000 +#define FW_CONTEXT_MEM (64*1024) +#define SST_ICCM_BOUNDARY 4 +#define SST_CONFIG_SSP_SIGN 0x7ffe8001 + +#define MRFLD_FW_VIRTUAL_BASE 0xC0000000 +#define MRFLD_FW_DDR_BASE_OFFSET 0x0 +#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 +#define MRFLD_FW_BSS_RESET_BIT 0 + +extern const struct dev_pm_ops intel_sst_pm; +enum sst_states { + SST_FW_LOADING = 1, + SST_FW_RUNNING, + SST_RESET, + SST_SHUTDOWN, +}; + +enum sst_algo_ops { + SST_SET_ALGO = 0, + SST_GET_ALGO = 1, +}; + +#define SST_BLOCK_TIMEOUT 1000 + +#define FW_SIGNATURE_SIZE 4 + +/* stream states */ +enum sst_stream_states { + STREAM_UN_INIT = 0, /* Freed/Not used stream */ + STREAM_RUNNING = 1, /* Running */ + STREAM_PAUSED = 2, /* Paused stream */ + STREAM_DECODE = 3, /* stream is in decoding only state */ + STREAM_INIT = 4, /* stream init, waiting for data */ + STREAM_RESET = 5, /* force reset on recovery */ +}; + +enum sst_ram_type { + SST_IRAM = 1, + SST_DRAM = 2, + SST_DDR = 5, + SST_CUSTOM_INFO = 7, /* consists of FW binary information */ +}; + +/* SST shim registers to structure mapping */ +union interrupt_reg { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_pisr_reg { + struct { + u32 pssp0:1; + u32 pssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:26; + } part; + u32 full; +}; + +union sst_pimr_reg { + struct { + u32 ssp0:1; + u32 ssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:10; + u32 ssp0_sc:1; + u32 ssp1_sc:1; + u32 rsvd2:3; + u32 dmac_sc:1; + u32 rsvd3:10; + } part; + u32 full; +}; + +union config_status_reg_mrfld { + struct { + u64 lpe_reset:1; + u64 lpe_reset_vector:1; + u64 runstall:1; + u64 pwaitmode:1; + u64 clk_sel:3; + u64 rsvd2:1; + u64 sst_clk:3; + u64 xt_snoop:1; + u64 rsvd3:4; + u64 clk_sel1:6; + u64 clk_enable:3; + u64 rsvd4:6; + u64 slim0baseclk:1; + u64 rsvd:32; + } part; + u64 full; +}; + +union interrupt_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_imr_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +/** + * struct sst_block - This structure is used to block a user/fw data call to another + * fw/user call + * + * @condition: condition for blocking check + * @ret_code: ret code when block is released + * @data: data ptr + * @size: size of data + * @on: block condition + * @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL + * @drv_id: str_id in mfld/ctp, = drv_id in mrfld + * @node: list head node + */ +struct sst_block { + bool condition; + int ret_code; + void *data; + u32 size; + bool on; + u32 msg_id; + u32 drv_id; + struct list_head node; +}; + +/** + * struct stream_info - structure that holds the stream information + * + * @status : stream current state + * @prev : stream prev state + * @ops : stream operation pb/cp/drm... + * @bufs: stream buffer list + * @lock : stream mutex for protecting state + * @pcm_substream : PCM substream + * @period_elapsed : PCM period elapsed callback + * @sfreq : stream sampling freq + * @str_type : stream type + * @cumm_bytes : cummulative bytes decoded + * @str_type : stream type + * @src : stream source + */ +struct stream_info { + unsigned int status; + unsigned int prev; + unsigned int ops; + struct mutex lock; + + void *pcm_substream; + void (*period_elapsed)(void *pcm_substream); + + unsigned int sfreq; + u32 cumm_bytes; + + void *compr_cb_param; + void (*compr_cb)(void *compr_cb_param); + + void *drain_cb_param; + void (*drain_notify)(void *drain_cb_param); + + unsigned int num_ch; + unsigned int pipe_id; + unsigned int str_id; + unsigned int task_id; +}; + +#define SST_FW_SIGN "$SST" +#define SST_FW_LIB_SIGN "$LIB" + +/** + * struct sst_fw_header - FW file headers + * + * @signature : FW signature + * @file_size: size of fw image + * @modules : # of modules + * @file_format : version of header format + * @reserved : reserved fields + */ +struct sst_fw_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 file_size; + u32 modules; + u32 file_format; + u32 reserved[4]; +}; + +/** + * struct fw_module_header - module header in FW + * + * @signature: module signature + * @mod_size: size of module + * @blocks: block count + * @type: block type + * @entry_point: module netry point + */ +struct fw_module_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; + u32 type; + u32 entry_point; +}; + +/** + * struct fw_block_info - block header for FW + * + * @type: block ram type I/D + * @size: size of block + * @ram_offset: offset in ram + */ +struct fw_block_info { + enum sst_ram_type type; + u32 size; + u32 ram_offset; + u32 rsvd; +}; + +struct sst_runtime_param { + struct snd_sst_runtime_params param; +}; + +struct sst_sg_list { + struct scatterlist *src; + struct scatterlist *dst; + int list_len; + unsigned int sg_idx; +}; + +struct sst_memcpy_list { + struct list_head memcpylist; + void *dstn; + const void *src; + u32 size; + bool is_io; +}; + +/*Firmware Module Information*/ +enum sst_lib_dwnld_status { + SST_LIB_NOT_FOUND = 0, + SST_LIB_FOUND, + SST_LIB_DOWNLOADED, +}; + +struct sst_module_info { + const char *name; /*Library name*/ + u32 id; /*Module ID*/ + u32 entry_pt; /*Module entry point*/ + u8 status; /*module status*/ + u8 rsvd1; + u16 rsvd2; +}; + +/* + * Structure for managing the Library Region(1.5MB) + * in DDR in Merrifield + */ +struct sst_mem_mgr { + phys_addr_t current_base; + int avail; + unsigned int count; +}; + +struct sst_ipc_reg { + int ipcx; + int ipcd; +}; + +struct sst_shim_regs64 { + u64 csr; + u64 pisr; + u64 pimr; + u64 isrx; + u64 isrd; + u64 imrx; + u64 imrd; + u64 ipcx; + u64 ipcd; + u64 isrsc; + u64 isrlpesc; + u64 imrsc; + u64 imrlpesc; + u64 ipcsc; + u64 ipclpesc; + u64 clkctl; + u64 csr2; +}; + +/** + * struct intel_sst_drv - driver ops + * + * @sst_state : current sst device state + * @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi + * devices + * @shim : SST shim pointer + * @mailbox : SST mailbox pointer + * @iram : SST IRAM pointer + * @dram : SST DRAM pointer + * @pdata : SST info passed as a part of pci platform data + * @shim_phy_add : SST shim phy addr + * @shim_regs64: Struct to save shim registers + * @ipc_dispatch_list : ipc messages dispatched + * @rx_list : to copy the process_reply/process_msg from DSP + * @ipc_post_msg_wq : wq to post IPC messages context + * @mad_ops : MAD driver operations registered + * @mad_wq : MAD driver wq + * @post_msg_wq : wq to post IPC messages + * @streams : sst stream contexts + * @list_lock : sst driver list lock (deprecated) + * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue + * @block_lock : spin lock to add block to block_list and assign pvt_id + * @rx_msg_lock : spin lock to handle the rx messages from the DSP + * @scard_ops : sst card ops + * @pci : sst pci device struture + * @dev : pointer to current device struct + * @sst_lock : sst device lock + * @pvt_id : sst private id + * @stream_cnt : total sst active stream count + * @pb_streams : total active pb streams + * @cp_streams : total active cp streams + * @audio_start : audio status + * @qos : PM Qos struct + * firmware_name : Firmware / Library name + */ +struct intel_sst_drv { + int sst_state; + int irq_num; + unsigned int dev_id; + void __iomem *ddr; + void __iomem *shim; + void __iomem *mailbox; + void __iomem *iram; + void __iomem *dram; + unsigned int mailbox_add; + unsigned int iram_base; + unsigned int dram_base; + unsigned int shim_phy_add; + unsigned int iram_end; + unsigned int dram_end; + unsigned int ddr_end; + unsigned int ddr_base; + unsigned int mailbox_recv_offset; + struct sst_shim_regs64 *shim_regs64; + struct list_head block_list; + struct list_head ipc_dispatch_list; + struct sst_platform_info *pdata; + struct list_head rx_list; + struct work_struct ipc_post_msg_wq; + wait_queue_head_t wait_queue; + struct workqueue_struct *post_msg_wq; + unsigned int tstamp; + /* str_id 0 is not used */ + struct stream_info streams[MAX_NUM_STREAMS+1]; + spinlock_t ipc_spin_lock; + spinlock_t block_lock; + spinlock_t rx_msg_lock; + struct pci_dev *pci; + struct device *dev; + volatile long unsigned pvt_id; + struct mutex sst_lock; + unsigned int stream_cnt; + unsigned int csr_value; + void *fw_in_mem; + struct sst_sg_list fw_sg_list, library_list; + struct intel_sst_ops *ops; + struct sst_info info; + struct pm_qos_request *qos; + unsigned int use_dma; + unsigned int use_lli; + atomic_t fw_clear_context; + bool lib_dwnld_reqd; + struct list_head memcpy_list; + struct sst_ipc_reg ipc_reg; + struct sst_mem_mgr lib_mem_mgr; + /* + * Holder for firmware name. Due to async call it needs to be + * persistent till worker thread gets called + */ + char firmware_name[20]; +}; + +/* misc definitions */ +#define FW_DWNL_ID 0x01 + +struct intel_sst_ops { + irqreturn_t (*interrupt)(int, void *); + irqreturn_t (*irq_thread)(int, void *); + void (*clear_interrupt)(struct intel_sst_drv *ctx); + int (*start)(struct intel_sst_drv *ctx); + int (*reset)(struct intel_sst_drv *ctx); + void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg); + int (*post_message)(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); + void (*process_message)(struct ipc_post *msg); + void (*set_bypass)(bool set); + int (*save_dsp_context)(struct intel_sst_drv *sst); + void (*restore_dsp_context)(void); + int (*alloc_stream)(struct intel_sst_drv *ctx, void *params); + void (*post_download)(struct intel_sst_drv *sst); +}; + +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx, + struct snd_sst_bytes_v2 *sbytes); +int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); +int sst_set_metadata(int str_id, char *params); +int sst_get_stream(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_params *str_param); +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld); +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain); +int sst_post_message_mrfld(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); +void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg); +int sst_start_mrfld(struct intel_sst_drv *ctx); +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx); +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx); + +int sst_load_fw(struct intel_sst_drv *ctx); +int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); +void sst_post_download_mrfld(struct intel_sst_drv *ctx); +int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); +void sst_memcpy_free_resources(struct intel_sst_drv *ctx); + +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_create_ipc_msg(struct ipc_post **arg, bool large); +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id); +void sst_clean_stream(struct stream_info *stream); +int intel_sst_register_compress(struct intel_sst_drv *sst); +int intel_sst_remove_compress(struct intel_sst_drv *sst); +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id); +int sst_send_sync_msg(int ipc, int str_id); +int sst_get_num_channel(struct snd_sst_params *str_param); +int sst_get_sfreq(struct snd_sst_params *str_param); +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params); +void sst_restore_fw_context(void); +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id); +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id); +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed); +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size); +int sst_request_firmware_async(struct intel_sst_drv *ctx); +int sst_driver_ops(struct intel_sst_drv *sst); +struct sst_platform_info *sst_get_acpi_driver_data(const char *hid); +void sst_firmware_load_cb(const struct firmware *fw, void *context); +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response); + +void sst_process_pending_msg(struct work_struct *work); +int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx); +void sst_init_stream(struct stream_info *stream, + int codec, int sst_id, int ops, u8 slot); +int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id); +struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx, + int str_id); +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id); +u32 relocate_imr_addr_mrfld(u32 base_addr); +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg); +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv); +int sst_shim_write(void __iomem *addr, int offset, int value); +u32 sst_shim_read(void __iomem *addr, int offset); +u64 sst_reg_read64(void __iomem *addr, int offset); +int sst_shim_write64(void __iomem *addr, int offset, u64 value); +u64 sst_shim_read64(void __iomem *addr, int offset); +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state); +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id); +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len); + +int sst_register(struct device *); +int sst_unregister(struct device *); + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id); +int sst_context_init(struct intel_sst_drv *ctx); +void sst_context_cleanup(struct intel_sst_drv *ctx); +void sst_configure_runtime_pm(struct intel_sst_drv *ctx); +#endif diff --git a/sound/soc/intel/sst/sst_acpi.c b/sound/soc/intel/sst/sst_acpi.c new file mode 100644 index 00000000000..31124aa4434 --- /dev/null +++ b/sound/soc/intel/sst/sst_acpi.c @@ -0,0 +1,383 @@ +/* + * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration. + * + * Copyright (c) 2013, Intel Corporation. + * + * Authors: Ramesh Babu K V <Ramesh.Babu@intel.com> + * Authors: Omair Mohammed Abdullah <omair.m.abdullah@intel.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 <linux/module.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/acpi.h> +#include <asm/platform_sst_audio.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <acpi/acbuffer.h> +#include <acpi/platform/acenv.h> +#include <acpi/platform/aclinux.h> +#include <acpi/actypes.h> +#include <acpi/acpi_bus.h> +#include "../sst-mfld-platform.h" +#include "../sst-dsp.h" +#include "sst.h" + +struct sst_machines { + char codec_id[32]; + char board[32]; + char machine[32]; + void (*machine_quirk)(void); + char firmware[32]; + struct sst_platform_info *pdata; + +}; + +/* LPE viewpoint addresses */ +#define SST_BYT_IRAM_PHY_START 0xff2c0000 +#define SST_BYT_IRAM_PHY_END 0xff2d4000 +#define SST_BYT_DRAM_PHY_START 0xff300000 +#define SST_BYT_DRAM_PHY_END 0xff320000 +#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */ +#define SST_BYT_IMR_VIRT_END 0xc01fffff +#define SST_BYT_SHIM_PHY_ADDR 0xff340000 +#define SST_BYT_MBOX_PHY_ADDR 0xff344000 +#define SST_BYT_DMA0_PHY_ADDR 0xff298000 +#define SST_BYT_DMA1_PHY_ADDR 0xff29c000 +#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000 +#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000 + +#define BYT_FW_MOD_TABLE_OFFSET 0x80000 +#define BYT_FW_MOD_TABLE_SIZE 0x100 +#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE) + +static const struct sst_info byt_fwparse_info = { + .use_elf = false, + .max_streams = 25, + .iram_start = SST_BYT_IRAM_PHY_START, + .iram_end = SST_BYT_IRAM_PHY_END, + .iram_use = true, + .dram_start = SST_BYT_DRAM_PHY_START, + .dram_end = SST_BYT_DRAM_PHY_END, + .dram_use = true, + .imr_start = SST_BYT_IMR_VIRT_START, + .imr_end = SST_BYT_IMR_VIRT_END, + .imr_use = true, + .mailbox_start = SST_BYT_MBOX_PHY_ADDR, + .num_probes = 0, + .lpe_viewpt_rqd = true, +}; + +static const struct sst_ipc_info byt_ipc_info = { + .ipc_offset = 0, + .mbox_recv_off = 0x400, +}; + +static const struct sst_lib_dnld_info byt_lib_dnld_info = { + .mod_base = SST_BYT_IMR_VIRT_START, + .mod_end = SST_BYT_IMR_VIRT_END, + .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET, + .mod_table_size = BYT_FW_MOD_TABLE_SIZE, + .mod_ddr_dnld = false, +}; + +static const struct sst_res_info byt_rvp_res_info = { + .shim_offset = 0x140000, + .shim_size = 0x000100, + .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR, + .ssp0_offset = 0xa0000, + .ssp0_size = 0x1000, + .dma0_offset = 0x98000, + .dma0_size = 0x4000, + .dma1_offset = 0x9c000, + .dma1_size = 0x4000, + .iram_offset = 0x0c0000, + .iram_size = 0x14000, + .dram_offset = 0x100000, + .dram_size = 0x28000, + .mbox_offset = 0x144000, + .mbox_size = 0x1000, + .acpi_lpe_res_index = 0, + .acpi_ddr_index = 2, + .acpi_ipc_irq_index = 5, +}; + +static struct sst_platform_info byt_rvp_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", +}; + +/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail, + * so pdata is same as Baytrail. + */ +static struct sst_platform_info chv_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", +}; + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + struct resource *rsrc; + struct platform_device *pdev = to_platform_device(ctx->dev); + + /* All ACPI resource request here */ + /* Get Shim addr */ + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_lpe_res_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid SHIM base from IFWI"); + return -EIO; + } + dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start, + (unsigned int)resource_size(rsrc)); + + ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset; + ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1; + dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base); + ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base, + ctx->pdata->res_info->iram_size); + if (!ctx->iram) { + dev_err(ctx->dev, "unable to map IRAM"); + return -EIO; + } + + ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset; + ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1; + dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base); + ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base, + ctx->pdata->res_info->dram_size); + if (!ctx->dram) { + dev_err(ctx->dev, "unable to map DRAM"); + return -EIO; + } + + ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset; + dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add); + ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add, + ctx->pdata->res_info->shim_size); + if (!ctx->shim) { + dev_err(ctx->dev, "unable to map SHIM"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr; + + /* Get mailbox addr */ + ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset; + dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add); + ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add, + ctx->pdata->res_info->mbox_size); + if (!ctx->mailbox) { + dev_err(ctx->dev, "unable to map mailbox"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->mailbox_add = ctx->info.mailbox_start; + + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_ddr_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid DDR base from IFWI"); + return -EIO; + } + ctx->ddr_base = rsrc->start; + ctx->ddr_end = rsrc->end; + dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base); + ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base, + resource_size(rsrc)); + if (!ctx->ddr) { + dev_err(ctx->dev, "unable to map DDR"); + return -EIO; + } + + /* Find the IRQ */ + ctx->irq_num = platform_get_irq(pdev, + ctx->pdata->res_info->acpi_ipc_irq_index); + return 0; +} + +static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level, + void *context, void **ret) +{ + *(bool *)context = true; + return AE_OK; +} + +static struct sst_machines *sst_acpi_find_machine( + struct sst_machines *machines) +{ + struct sst_machines *mach; + bool found = false; + + for (mach = machines; mach->codec_id; mach++) + if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id, + sst_acpi_mach_match, + &found, NULL)) && found) + return mach; + + return NULL; +} + +int sst_acpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0; + struct intel_sst_drv *ctx; + const struct acpi_device_id *id; + struct sst_machines *mach; + struct platform_device *mdev; + struct platform_device *plat_dev; + unsigned int dev_id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + dev_dbg(dev, "for %s", id->id); + + mach = (struct sst_machines *)id->driver_data; + mach = sst_acpi_find_machine(mach); + if (mach == NULL) { + dev_err(dev, "No matching machine driver found\n"); + return -ENODEV; + } + + ret = kstrtouint(id->id, 16, &dev_id); + if (ret < 0) { + dev_err(dev, "Unique device id conversion error: %d\n", ret); + return ret; + } + + dev_dbg(dev, "ACPI device id: %x\n", dev_id); + + plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0); + if (plat_dev == NULL) { + dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform); + return -ENODEV; + } + + /* Create platform device for sst machine driver */ + mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0); + if (mdev == NULL) { + dev_err(dev, "Failed to create machine device: %s\n", mach->machine); + return -ENODEV; + } + + ret = sst_alloc_drv_context(&ctx, dev, dev_id); + if (ret < 0) + return ret; + + /* Fill sst platform data */ + ctx->pdata = mach->pdata; + strcpy(ctx->firmware_name, mach->firmware); + + ret = sst_platform_get_resources(ctx); + if (ret) + return ret; + + ret = sst_context_init(ctx); + if (ret < 0) + return ret; + + /* need to save shim registers in BYT */ + ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64), + GFP_KERNEL); + if (!ctx->shim_regs64) { + return -ENOMEM; + goto do_sst_cleanup; + } + + sst_configure_runtime_pm(ctx); + platform_set_drvdata(pdev, ctx); + return ret; + +do_sst_cleanup: + sst_context_cleanup(ctx); + platform_set_drvdata(pdev, NULL); + dev_err(ctx->dev, "failed with %d\n", ret); + return ret; +} + +/** +* intel_sst_remove - remove function +* +* @pdev: platform device structure +* +* This function is called by OS when a device is unloaded +* This frees the interrupt etc +*/ +int sst_acpi_remove(struct platform_device *pdev) +{ + struct intel_sst_drv *ctx; + + ctx = platform_get_drvdata(pdev); + sst_context_cleanup(ctx); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct sst_machines sst_acpi_bytcr[] = { + {"10EC5640", "T100", "bytt100_rt5640", NULL, "fw_sst_0f28.bin", + &byt_rvp_platform_data }, + {}, +}; + +/* Cherryview-based platforms: CherryTrail and Braswell */ +static struct sst_machines sst_acpi_chv[] = { + {"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "fw_sst_22a8.bin", + &chv_platform_data }, + {}, +}; + +static const struct acpi_device_id sst_acpi_ids[] = { + { "80860F28", (unsigned long)&sst_acpi_bytcr}, + { "808622A8", (unsigned long) &sst_acpi_chv}, + { }, +}; + +MODULE_DEVICE_TABLE(acpi, sst_acpi_ids); + +static struct platform_driver sst_acpi_driver = { + .driver = { + .name = "intel_sst_acpi", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(sst_acpi_ids), + .pm = &intel_sst_pm, + }, + .probe = sst_acpi_probe, + .remove = sst_acpi_remove, +}; + +module_platform_driver(sst_acpi_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver"); +MODULE_AUTHOR("Ramesh Babu K V"); +MODULE_AUTHOR("Omair Mohammed Abdullah"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/sst/sst_drv_interface.c b/sound/soc/intel/sst/sst_drv_interface.c new file mode 100644 index 00000000000..5f75ef3cdd2 --- /dev/null +++ b/sound/soc/intel/sst/sst_drv_interface.c @@ -0,0 +1,686 @@ +/* + * sst_drv_interface.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/math64.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + + + +#define NUM_CODEC 2 +#define MIN_FRAGMENT 2 +#define MAX_FRAGMENT 4 +#define MIN_FRAGMENT_SIZE (50 * 1024) +#define MAX_FRAGMENT_SIZE (1024 * 1024) +#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1) + +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id) +{ + struct stream_info *stream; + int ret = 0; + + stream = get_stream_info(ctx, str_id); + if (stream) { + /* str_id is valid, so stream is alloacted */ + ret = sst_free_stream(ctx, str_id); + if (ret) + sst_clean_stream(&ctx->streams[str_id]); + return ret; + } else { + dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id); + } + return ret; +} + +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld) +{ + int retval; + + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval > 0) + dev_dbg(ctx->dev, "Stream allocated %d\n", retval); + return retval; + +} + +/* + * sst_get_sfreq - this function returns the frequency of the stream + * + * @str_param : stream params + */ +int sst_get_sfreq(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.sfreq; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.externalsr; + case SST_CODEC_TYPE_MP3: + return 0; + default: + return -EINVAL; + } +} + +/* + * sst_get_num_channel - get number of channels for the stream + * + * @str_param : stream params + */ +int sst_get_num_channel(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.num_chan; + case SST_CODEC_TYPE_MP3: + return str_param->sparams.uc.mp3_params.num_chan; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.num_chan; + default: + return -EINVAL; + } +} + +/* + * sst_get_stream - this function prepares for stream allocation + * + * @str_param : stream param + */ +int sst_get_stream(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param) +{ + int retval; + struct stream_info *str_info; + + /* stream is not allocated, we are allocating */ + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval <= 0) { + return -EIO; + } + /* store sampling freq */ + str_info = &ctx->streams[retval]; + str_info->sfreq = sst_get_sfreq(str_param); + + return retval; +} + +static int sst_power_control(struct device *dev, bool state) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + dev_dbg(ctx->dev, "state:%d", state); + if (state == true) + return pm_runtime_get_sync(dev); + else + return sst_pm_runtime_put(ctx); +} + +/* + * sst_open_pcm_stream - Open PCM interface + * + * @str_param: parameters of pcm stream + * + * This function is called by MID sound card driver to open + * a new pcm interface + */ +static int sst_open_pcm_stream(struct device *dev, + struct snd_sst_params *str_param) +{ + int retval; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (!str_param) + return -EINVAL; + + retval = sst_get_stream(ctx, str_param); + if (retval > 0) + ctx->stream_cnt++; + else + dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval); + + return retval; +} + +static int sst_cdev_open(struct device *dev, + struct snd_sst_params *str_params, struct sst_compress_cb *cb) +{ + int str_id, retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + retval = pm_runtime_get_sync(ctx->dev); + if (retval < 0) + return retval; + + str_id = sst_get_stream(ctx, str_params); + if (str_id > 0) { + dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id); + stream = &ctx->streams[str_id]; + stream->compr_cb = cb->compr_cb; + stream->compr_cb_param = cb->param; + stream->drain_notify = cb->drain_notify; + stream->drain_cb_param = cb->drain_cb_param; + } else { + dev_err(dev, "stream encountered error during alloc %d\n", str_id); + str_id = -EINVAL; + sst_pm_runtime_put(ctx); + } + return str_id; +} + +static int sst_cdev_close(struct device *dev, unsigned int str_id) +{ + int retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + dev_dbg(dev, "stream in reset state...\n"); + stream->status = STREAM_UN_INIT; + + retval = 0; + goto put; + } + + retval = sst_free_stream(ctx, str_id); +put: + stream->compr_cb_param = NULL; + stream->compr_cb = NULL; + + if (retval) + dev_err(dev, "free stream returned err %d\n", retval); + + dev_dbg(dev, "End\n"); + return retval; + +} + +static int sst_cdev_ack(struct device *dev, unsigned int str_id, + unsigned long bytes) +{ + struct stream_info *stream; + struct snd_sst_tstamp fw_tstamp = {0,}; + int offset; + void __iomem *addr; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + /* update bytes sent */ + stream->cumm_bytes += bytes; + dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + fw_tstamp.bytes_copied = stream->cumm_bytes; + dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n", + fw_tstamp.bytes_copied, bytes); + + addr = ((void *)(ctx->mailbox + ctx->tstamp)) + + (str_id * sizeof(fw_tstamp)); + offset = offsetof(struct snd_sst_tstamp, bytes_copied); + sst_shim_write(addr, offset, fw_tstamp.bytes_copied); + return 0; +} + +static int sst_cdev_set_metadata(struct device *dev, + unsigned int str_id, struct snd_compr_metadata *metadata) +{ + int retval = 0; + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + dev_dbg(dev, "set metadata for stream %d\n", str_id); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + + dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id); + retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD, + IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id, + sizeof(*metadata), metadata, NULL, + true, true, true, false); + + return retval; +} + +static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_pause_stream(ctx, str_id); +} + +static int sst_cdev_stream_pause_release(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_resume_stream(ctx, str_id); +} + +static int sst_cdev_stream_start(struct device *dev, unsigned int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + return sst_start_stream(ctx, str_id); +} + +static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drop_stream(ctx, str_id); +} + +static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, false); +} + +static int sst_cdev_stream_partial_drain(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, true); +} + +static int sst_cdev_tstamp(struct device *dev, unsigned int str_id, + struct snd_compr_tstamp *tstamp) +{ + struct snd_sst_tstamp fw_tstamp = {0,}; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter); + + tstamp->copied_total = fw_tstamp.ring_buffer_counter; + tstamp->pcm_frames = fw_tstamp.frames_decoded; + tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, + (u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24))); + tstamp->sampling_rate = fw_tstamp.sampling_frequency; + + dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames); + dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n", + str_id, tstamp->copied_total, tstamp->pcm_frames); + dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames); + + return 0; +} + +static int sst_cdev_caps(struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + return 0; +} + +static struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | + SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else + return -EINVAL; + + return 0; +} + +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id) +{ + struct stream_info *stream; + + dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n", + str_id); + stream = &ctx->streams[str_id]; + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); +} + +/* + * sst_close_pcm_stream - Close PCM interface + * + * @str_id: stream id to be closed + * + * This function is called by MID sound card driver to close + * an existing pcm interface + */ +static int sst_close_pcm_stream(struct device *dev, unsigned int str_id) +{ + struct stream_info *stream; + int retval = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + /* silently fail here as we have cleaned the stream earlier */ + dev_dbg(ctx->dev, "stream in reset state...\n"); + + retval = 0; + goto put; + } + + retval = free_stream_context(ctx, str_id); +put: + stream->pcm_substream = NULL; + stream->status = STREAM_UN_INIT; + stream->period_elapsed = NULL; + ctx->stream_cnt--; + + if (retval) + dev_err(ctx->dev, "free stream returned err %d\n", retval); + + dev_dbg(ctx->dev, "Exit\n"); + return 0; +} + +static inline int sst_calc_tstamp(struct intel_sst_drv *ctx, + struct pcm_stream_info *info, + struct snd_pcm_substream *substream, + struct snd_sst_tstamp *fw_tstamp) +{ + size_t delay_bytes, delay_frames; + size_t buffer_sz; + u32 pointer_bytes, pointer_samples; + + dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n", + fw_tstamp->ring_buffer_counter); + dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n", + fw_tstamp->hardware_counter); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter - + fw_tstamp->hardware_counter); + else + delay_bytes = (size_t) (fw_tstamp->hardware_counter - + fw_tstamp->ring_buffer_counter); + delay_frames = bytes_to_frames(substream->runtime, delay_bytes); + buffer_sz = snd_pcm_lib_buffer_bytes(substream); + div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes); + pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes); + + dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes); + + info->buffer_ptr = pointer_samples / substream->runtime->channels; + + info->pcm_delay = delay_frames / substream->runtime->channels; + dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n", + info->buffer_ptr, info->pcm_delay); + return 0; +} + +static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info) +{ + struct stream_info *stream; + struct snd_pcm_substream *substream; + struct snd_sst_tstamp fw_tstamp; + unsigned int str_id; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_id = info->str_id; + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + if (!stream->pcm_substream) + return -EINVAL; + substream = stream->pcm_substream; + + memcpy_fromio(&fw_tstamp, + ((void *)(ctx->mailbox + ctx->tstamp) + + (str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + return sst_calc_tstamp(ctx, info, substream, &fw_tstamp); +} + +static int sst_stream_start(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + sst_start_stream(ctx, str_id); + + return 0; +} + +static int sst_stream_drop(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + return sst_drop_stream(ctx, str_id); +} + +static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info) +{ + int str_id = 0; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_id = str_info->str_id; + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + dev_dbg(ctx->dev, "setting the period ptrs\n"); + stream->pcm_substream = str_info->arg; + stream->period_elapsed = str_info->period_elapsed; + stream->sfreq = str_info->sfreq; + stream->prev = stream->status; + stream->status = STREAM_INIT; + dev_dbg(ctx->dev, + "pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n", + stream->pcm_substream, stream->period_elapsed, + stream->sfreq, stream->status); + + return 0; +} + +/* + * sst_set_byte_stream - Set generic params + * + * @cmd: control cmd to be set + * @arg: command argument + * + * This function is called by MID sound card driver to configure + * SST runtime params. + */ +static int sst_send_byte_stream(struct device *dev, + struct snd_sst_bytes_v2 *bytes) +{ + int ret_val = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (NULL == bytes) + return -EINVAL; + ret_val = pm_runtime_get_sync(ctx->dev); + if (ret_val < 0) + return ret_val; + + ret_val = sst_send_byte_stream_mrfld(ctx, bytes); + sst_pm_runtime_put(ctx); + + return ret_val; +} + +static struct sst_ops pcm_ops = { + .open = sst_open_pcm_stream, + .stream_init = sst_stream_init, + .stream_start = sst_stream_start, + .stream_drop = sst_stream_drop, + .stream_read_tstamp = sst_read_timestamp, + .send_byte_stream = sst_send_byte_stream, + .close = sst_close_pcm_stream, + .power = sst_power_control, +}; + +static struct compress_sst_ops compr_ops = { + .open = sst_cdev_open, + .close = sst_cdev_close, + .stream_pause = sst_cdev_stream_pause, + .stream_pause_release = sst_cdev_stream_pause_release, + .stream_start = sst_cdev_stream_start, + .stream_drop = sst_cdev_stream_drop, + .stream_drain = sst_cdev_stream_drain, + .stream_partial_drain = sst_cdev_stream_partial_drain, + .tstamp = sst_cdev_tstamp, + .ack = sst_cdev_ack, + .get_caps = sst_cdev_caps, + .get_codec_caps = sst_cdev_codec_caps, + .set_metadata = sst_cdev_set_metadata, + .power = sst_power_control, +}; + +static struct sst_device sst_dsp_device = { + .name = "Intel(R) SST LPE", + .dev = NULL, + .ops = &pcm_ops, + .compr_ops = &compr_ops, +}; + +/* + * sst_register - function to register DSP + * + * This functions registers DSP with the platform driver + */ +int sst_register(struct device *dev) +{ + int ret_val; + + sst_dsp_device.dev = dev; + ret_val = sst_register_dsp(&sst_dsp_device); + if (ret_val) + dev_err(dev, "Unable to register DSP with platform driver\n"); + + return ret_val; +} + +int sst_unregister(struct device *dev) +{ + return sst_unregister_dsp(&sst_dsp_device); +} diff --git a/sound/soc/intel/sst/sst_ipc.c b/sound/soc/intel/sst/sst_ipc.c new file mode 100644 index 00000000000..484e6097847 --- /dev/null +++ b/sound/soc/intel/sst/sst_ipc.c @@ -0,0 +1,373 @@ +/* + * sst_ipc.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/pci.h> +#include <linux/firmware.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/intel-mid.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id) +{ + struct sst_block *msg = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + msg->condition = false; + msg->on = true; + msg->msg_id = msg_id; + msg->drv_id = drv_id; + spin_lock_bh(&ctx->block_lock); + list_add_tail(&msg->node, &ctx->block_list); + spin_unlock_bh(&ctx->block_lock); + + return msg; +} + +/* + * while handling the interrupts, we need to check for message status and + * then if we are blocking for a message + * + * here we are unblocking the blocked ones, this is based on id we have + * passed and search that for block threads. + * We will not find block in two cases + * a) when its small message and block in not there, so silently ignore + * them + * b) when we are actually not able to find the block (bug perhaps) + * + * Since we have bit of small messages we can spam kernel log with err + * print on above so need to keep as debug prints which should be enabled + * via dynamic debug while debugging IPC issues + */ +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size) +{ + struct sst_block *block = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + + spin_lock_bh(&ctx->block_lock); + list_for_each_entry(block, &ctx->block_list, node) { + dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, + block->drv_id); + if (block->msg_id == ipc && block->drv_id == drv_id) { + dev_dbg(ctx->dev, "free up the block\n"); + block->ret_code = result; + block->data = data; + block->size = size; + block->condition = true; + spin_unlock_bh(&ctx->block_lock); + wake_up(&ctx->wait_queue); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_dbg(ctx->dev, + "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", + ipc, drv_id); + return -EINVAL; +} + +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) +{ + struct sst_block *block = NULL, *__block; + + dev_dbg(ctx->dev, "Enter\n"); + spin_lock_bh(&ctx->block_lock); + list_for_each_entry_safe(block, __block, &ctx->block_list, node) { + if (block == freed) { + pr_debug("pvt_id freed --> %d\n", freed->drv_id); + /* toggle the index position of pvt_id */ + list_del(&freed->node); + spin_unlock_bh(&ctx->block_lock); + kfree(freed->data); + freed->data = NULL; + kfree(freed); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_err(ctx->dev, "block is already freed!!!\n"); + return -EINVAL; +} + +int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *ipc_msg, bool sync) +{ + struct ipc_post *msg = ipc_msg; + union ipc_header_mrfld header; + unsigned int loop_count = 0; + int retval = 0; + unsigned long irq_flags; + + dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + if (sync) { + while (header.p.header_high.part.busy) { + if (loop_count > 25) { + dev_err(sst_drv_ctx->dev, + "sst: Busy wait failed, cant send this msg\n"); + retval = -EBUSY; + goto out; + } + cpu_relax(); + loop_count++; + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + } + } else { + if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { + /* queue is empty, nothing to send */ + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, + "Empty msg queue... NO Action\n"); + return 0; + } + + if (header.p.header_high.part.busy) { + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); + return 0; + } + + /* copy msg from list */ + msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, + struct ipc_post, node); + list_del(&msg->node); + } + dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", + msg->mrfld_header.p.header_low_payload); + + if (msg->mrfld_header.p.header_high.part.large) + memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, + msg->mailbox_data, + msg->mrfld_header.p.header_low_payload); + + sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); + +out: + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + kfree(msg->mailbox_data); + kfree(msg); + return retval; +} + +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; + unsigned long irq_flags; + + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + + /* write 1 to clear*/ + isr.part.busy_interrupt = 1; + sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); + + /* Set IA done bit */ + clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); + + clear_ipc.p.header_high.part.busy = 0; + clear_ipc.p.header_high.part.done = 1; + clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; + sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); +} + + +/* + * process_fw_init - process the FW init msg + * + * @msg: IPC message mailbox data from FW + * + * This function processes the FW init msg from FW + * marks FW state and prints debug info of loaded FW + */ +static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, + void *msg) +{ + struct ipc_header_fw_init *init = + (struct ipc_header_fw_init *)msg; + int retval = 0; + + dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); + if (init->result) { + sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); + dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", + init->result); + retval = init->result; + goto ret; + } + +ret: + sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); +} + +static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + u32 msg_id; + int str_id; + u32 data_size, i; + void *data_offset; + struct stream_info *stream; + union ipc_header_high msg_high; + u32 msg_low, pipe_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; + data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); + data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + dev_dbg(sst_drv_ctx->dev, + "Period elapsed rcvd for pipe id 0x%x\n", + pipe_id); + stream = &sst_drv_ctx->streams[str_id]; + if (stream->period_elapsed) + stream->period_elapsed(stream->pcm_substream); + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); + } + break; + + case IPC_IA_DRAIN_STREAM_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + stream = &sst_drv_ctx->streams[str_id]; + if (stream->drain_notify) + stream->drain_notify(stream->drain_cb_param); + } + break; + + case IPC_IA_FW_ASYNC_ERR_MRFLD: + dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); + for (i = 0; i < (data_size/4); i++) + print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, + 16, 4, data_offset, data_size, false); + break; + + case IPC_IA_FW_INIT_CMPLT_MRFLD: + process_fw_init(sst_drv_ctx, data_offset); + break; + + case IPC_IA_BUF_UNDER_RUN_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) + dev_err(sst_drv_ctx->dev, + "Buffer under-run for pipe:%#x str_id:%d\n", + pipe_id, str_id); + break; + + default: + dev_err(sst_drv_ctx->dev, + "Unrecognized async msg from FW msg_id %#x\n", msg_id); + } +} + +void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + unsigned int drv_id; + void *data; + union ipc_header_high msg_high; + u32 msg_low; + struct ipc_dsp_hdr *dsp_hdr; + unsigned int cmd_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + + dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", + msg->mrfld_header.p.header_high.full, + msg->mrfld_header.p.header_low_payload); + + drv_id = msg_high.part.drv_id; + + /* Check for async messages first */ + if (drv_id == SST_ASYNC_DRV_ID) { + /*FW sent async large message*/ + process_fw_async_msg(sst_drv_ctx, msg); + return; + } + + /* FW sent short error response for an IPC */ + if (msg_high.part.result && drv_id && !msg_high.part.large) { + /* 32-bit FW error code in msg_low */ + dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + return; + } + + /* + * Process all valid responses + * if it is a large message, the payload contains the size to + * copy from mailbox + **/ + if (msg_high.part.large) { + data = kzalloc(msg_low, GFP_KERNEL); + if (!data) + return; + memcpy(data, (void *) msg->mailbox_data, msg_low); + /* Copy command id so that we can use to put sst to reset */ + dsp_hdr = (struct ipc_dsp_hdr *)data; + cmd_id = dsp_hdr->cmd_id; + dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); + if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, data, msg_low)) + kfree(data); + } else { + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + } + +} diff --git a/sound/soc/intel/sst/sst_loader.c b/sound/soc/intel/sst/sst_loader.c new file mode 100644 index 00000000000..b580f96e25e --- /dev/null +++ b/sound/soc/intel/sst/sst_loader.c @@ -0,0 +1,456 @@ +/* + * sst_dsp.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains all dsp controlling functions like firmware download, + * setting/resetting dsp cores, etc + */ +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/firmware.h> +#include <linux/dmaengine.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +static inline void memcpy32_toio(void __iomem *dst, const void *src, int count) +{ + /* __iowrite32_copy uses 32-bit count values so divide by 4 for + * right count in words + */ + __iowrite32_copy(dst, src, count/4); +} + +/** + * intel_sst_reset_dsp_mrfld - Resetting SST DSP + * + * This resets DSP in case of MRFLD platfroms + */ +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full &= ~(0x1); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + return 0; +} + +/** + * sst_start_merrifield - Start the SST DSP processor + * + * This starts the DSP in MERRIFIELD platfroms + */ +int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.part.xt_snoop = 1; + csr.full &= ~(0x5); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n", + csr.full); + return 0; +} + +static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size, + struct fw_module_header **module, u32 *num_modules) +{ + struct sst_fw_header *header; + const void *sst_fw_in_mem = ctx->fw_in_mem; + + dev_dbg(ctx->dev, "Enter\n"); + + /* Read the header information from the data pointer */ + header = (struct sst_fw_header *)sst_fw_in_mem; + dev_dbg(ctx->dev, + "header sign=%s size=%x modules=%x fmt=%x size=%zx\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* verify FW */ + if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || + (size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n"); + return -EINVAL; + } + *num_modules = header->modules; + *module = (void *)sst_fw_in_mem + sizeof(*header); + + return 0; +} + +/* + * sst_fill_memcpy_list - Fill the memcpy list + * + * @memcpy_list: List to be filled + * @destn: Destination addr to be filled in the list + * @src: Source addr to be filled in the list + * @size: Size to be filled in the list + * + * Adds the node to the list after required fields + * are populated in the node + */ +static int sst_fill_memcpy_list(struct list_head *memcpy_list, + void *destn, const void *src, u32 size, bool is_io) +{ + struct sst_memcpy_list *listnode; + + listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); + if (listnode == NULL) + return -ENOMEM; + listnode->dstn = destn; + listnode->src = src; + listnode->size = size; + listnode->is_io = is_io; + list_add_tail(&listnode->memcpylist, memcpy_list); + + return 0; +} + +/** + * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list + * + * @sst_drv_ctx : driver context + * @module : FW module header + * @memcpy_list : Pointer to the list to be populated + * Create the memcpy list as the number of block to be copied + * returns error or 0 if module sizes are proper + */ +static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx, + struct fw_module_header *module, struct list_head *memcpy_list) +{ + struct fw_block_info *block; + u32 count; + int ret_val = 0; + void __iomem *ram_iomem; + + dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n", + module->signature, module->mod_size, + module->blocks, module->type); + dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + if (block->size <= 0) { + dev_err(sst_drv_ctx->dev, "block size invalid\n"); + return -EINVAL; + } + switch (block->type) { + case SST_IRAM: + ram_iomem = sst_drv_ctx->iram; + break; + case SST_DRAM: + ram_iomem = sst_drv_ctx->dram; + break; + case SST_DDR: + ram_iomem = sst_drv_ctx->ddr; + break; + case SST_CUSTOM_INFO: + block = (void *)block + sizeof(*block) + block->size; + continue; + default: + dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + ret_val = sst_fill_memcpy_list(memcpy_list, + ram_iomem + block->ram_offset, + (void *)block + sizeof(*block), block->size, 1); + if (ret_val) + return ret_val; + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +/** + * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy + * + * @ctx : pointer to drv context + * @size : size of the firmware + * @fw_list : pointer to list_head to be populated + * This function parses the FW image and saves the parsed image in the list + * for memcpy + */ +static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size, + struct list_head *fw_list) +{ + struct fw_module_header *module; + u32 count, num_modules; + int ret_val; + + ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules); + if (ret_val) + return ret_val; + + for (count = 0; count < num_modules; count++) { + ret_val = sst_parse_module_memcpy(ctx, module, fw_list); + if (ret_val) + return ret_val; + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +/** + * sst_do_memcpy - function initiates the memcpy + * + * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated + * + * Triggers the memcpy + */ +static void sst_do_memcpy(struct list_head *memcpy_list) +{ + struct sst_memcpy_list *listnode; + + list_for_each_entry(listnode, memcpy_list, memcpylist) { + if (listnode->is_io == true) + memcpy32_toio((void __iomem *)listnode->dstn, + listnode->src, listnode->size); + else + memcpy(listnode->dstn, listnode->src, listnode->size); + } +} + +void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) +{ + struct sst_memcpy_list *listnode, *tmplistnode; + + /* Free the list */ + if (!list_empty(&sst_drv_ctx->memcpy_list)) { + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->memcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); + } + } +} + +static int sst_cache_and_parse_fw(struct intel_sst_drv *sst, + const struct firmware *fw) +{ + int retval = 0; + + sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); + if (!sst->fw_in_mem) { + retval = -ENOMEM; + goto end_release; + } + dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem); + dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); + memcpy(sst->fw_in_mem, fw->data, fw->size); + retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list); + if (retval) { + dev_err(sst->dev, "Failed to parse fw\n"); + kfree(sst->fw_in_mem); + sst->fw_in_mem = NULL; + } + +end_release: + release_firmware(fw); + return retval; + +} + +void sst_firmware_load_cb(const struct firmware *fw, void *context) +{ + struct intel_sst_drv *ctx = context; + + dev_dbg(ctx->dev, "Enter\n"); + + if (fw == NULL) { + dev_err(ctx->dev, "request fw failed\n"); + return; + } + + mutex_lock(&ctx->sst_lock); + + if (ctx->sst_state != SST_RESET || + ctx->fw_in_mem != NULL) { + if (fw != NULL) + release_firmware(fw); + mutex_unlock(&ctx->sst_lock); + return; + } + + dev_dbg(ctx->dev, "Request Fw completed\n"); + sst_cache_and_parse_fw(ctx, fw); + mutex_unlock(&ctx->sst_lock); +} + +/* + * sst_request_fw - requests audio fw from kernel and saves a copy + * + * This function requests the SST FW from the kernel, parses it and + * saves a copy in the driver context + */ +static int sst_request_fw(struct intel_sst_drv *sst) +{ + int retval = 0; + const struct firmware *fw; + + retval = request_firmware(&fw, sst->firmware_name, sst->dev); + if (fw == NULL) { + dev_err(sst->dev, "fw is returning as null\n"); + return -EINVAL; + } + if (retval) { + dev_err(sst->dev, "request fw failed %d\n", retval); + return retval; + } + mutex_lock(&sst->sst_lock); + retval = sst_cache_and_parse_fw(sst, fw); + mutex_unlock(&sst->sst_lock); + + return retval; +} + +/* + * Writing the DDR physical base to DCCM offset + * so that FW can use it to setup TLB + */ +static void sst_dccm_config_write(void __iomem *dram_base, + unsigned int ddr_base) +{ + void __iomem *addr; + u32 bss_reset = 0; + + addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); + memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32)); + bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); + addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); + memcpy32_toio(addr, &bss_reset, sizeof(u32)); + +} + +void sst_post_download_mrfld(struct intel_sst_drv *ctx) +{ + sst_dccm_config_write(ctx->dram, ctx->ddr_base); + dev_dbg(ctx->dev, "config written to DCCM\n"); +} + +/** + * sst_load_fw - function to load FW into DSP + * Transfers the FW to DSP using dma/memcpy + */ +int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) +{ + int ret_val = 0; + struct sst_block *block; + + dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); + + if (sst_drv_ctx->sst_state != SST_RESET || + sst_drv_ctx->sst_state == SST_SHUTDOWN) + return -EAGAIN; + + if (!sst_drv_ctx->fw_in_mem) { + dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n"); + ret_val = sst_request_fw(sst_drv_ctx); + if (ret_val) + return ret_val; + } + + BUG_ON(!sst_drv_ctx->fw_in_mem); + block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + /* Prevent C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, 0); + + sst_drv_ctx->sst_state = SST_FW_LOADING; + + ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx); + if (ret_val) + goto restore; + + sst_do_memcpy(&sst_drv_ctx->memcpy_list); + + /* Write the DRAM/DCCM config before enabling FW */ + if (sst_drv_ctx->ops->post_download) + sst_drv_ctx->ops->post_download(sst_drv_ctx); + + /* bring sst out of reset */ + ret_val = sst_drv_ctx->ops->start(sst_drv_ctx); + if (ret_val) + goto restore; + + ret_val = sst_wait_timeout(sst_drv_ctx, block); + if (ret_val) { + dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val); + /* FW download failed due to timeout */ + ret_val = -EBUSY; + + } + + +restore: + /* Re-enable Deeper C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); + sst_free_block(sst_drv_ctx, block); + dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); + + if (sst_drv_ctx->ops->restore_dsp_context) + sst_drv_ctx->ops->restore_dsp_context(); + sst_drv_ctx->sst_state = SST_FW_RUNNING; + return ret_val; +} + diff --git a/sound/soc/intel/sst/sst_pci.c b/sound/soc/intel/sst/sst_pci.c new file mode 100644 index 00000000000..3a0b3bf0af9 --- /dev/null +++ b/sound/soc/intel/sst/sst_pci.c @@ -0,0 +1,209 @@ +/* + * sst_pci.c - SST (LPE) driver init file for pci enumeration. + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + int ddr_base, ret = 0; + struct pci_dev *pci = ctx->pci; + + ret = pci_request_regions(pci, SST_DRV_NAME); + if (ret) + return ret; + + /* map registers */ + /* DDR base */ + if (ctx->dev_id == SST_MRFLD_PCI_ID) { + ctx->ddr_base = pci_resource_start(pci, 0); + /* check that the relocated IMR base matches with FW Binary */ + ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base); + if (!ctx->pdata->lib_info) { + dev_err(ctx->dev, "lib_info pointer NULL\n"); + ret = -EINVAL; + goto do_release_regions; + } + if (ddr_base != ctx->pdata->lib_info->mod_base) { + dev_err(ctx->dev, + "FW LSP DDR BASE does not match with IFWI\n"); + ret = -EINVAL; + goto do_release_regions; + } + ctx->ddr_end = pci_resource_end(pci, 0); + + ctx->ddr = pcim_iomap(pci, 0, + pci_resource_len(pci, 0)); + if (!ctx->ddr) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr); + } else { + ctx->ddr = NULL; + } + /* SHIM */ + ctx->shim_phy_add = pci_resource_start(pci, 1); + ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1)); + if (!ctx->shim) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim); + + /* Shared SRAM */ + ctx->mailbox_add = pci_resource_start(pci, 2); + ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2)); + if (!ctx->mailbox) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox); + + /* IRAM */ + ctx->iram_end = pci_resource_end(pci, 3); + ctx->iram_base = pci_resource_start(pci, 3); + ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3)); + if (!ctx->iram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram); + + /* DRAM */ + ctx->dram_end = pci_resource_end(pci, 4); + ctx->dram_base = pci_resource_start(pci, 4); + ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4)); + if (!ctx->dram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram); +do_release_regions: + pci_release_regions(pci); + return 0; +} + +/* + * intel_sst_probe - PCI probe function + * + * @pci: PCI device structure + * @pci_id: PCI device ID structure + * + */ +static int intel_sst_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + int ret = 0; + struct intel_sst_drv *sst_drv_ctx; + struct sst_platform_info *sst_pdata = pci->dev.platform_data; + + dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device); + ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device); + if (ret < 0) + return ret; + + sst_drv_ctx->pdata = sst_pdata; + sst_drv_ctx->irq_num = pci->irq; + snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name), + "%s%04x%s", "fw_sst_", + sst_drv_ctx->dev_id, ".bin"); + + ret = sst_context_init(sst_drv_ctx); + if (ret < 0) + return ret; + + /* Init the device */ + ret = pcim_enable_device(pci); + if (ret) { + dev_err(sst_drv_ctx->dev, + "device can't be enabled. Returned err: %d\n", ret); + goto do_free_drv_ctx; + } + sst_drv_ctx->pci = pci_dev_get(pci); + ret = sst_platform_get_resources(sst_drv_ctx); + if (ret < 0) + goto do_free_drv_ctx; + + pci_set_drvdata(pci, sst_drv_ctx); + sst_configure_runtime_pm(sst_drv_ctx); + + return ret; + +do_free_drv_ctx: + sst_context_cleanup(sst_drv_ctx); + dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret); + return ret; +} + +/** + * intel_sst_remove - PCI remove function + * + * @pci: PCI device structure + * + * This function is called by OS when a device is unloaded + * This frees the interrupt etc + */ +static void intel_sst_remove(struct pci_dev *pci) +{ + struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); + + sst_context_cleanup(sst_drv_ctx); + pci_dev_put(sst_drv_ctx->pci); + pci_release_regions(pci); + pci_set_drvdata(pci, NULL); +} + +/* PCI Routines */ +static struct pci_device_id intel_sst_ids[] = { + { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, + { 0, } +}; + +static struct pci_driver sst_driver = { + .name = SST_DRV_NAME, + .id_table = intel_sst_ids, + .probe = intel_sst_probe, + .remove = intel_sst_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &intel_sst_pm, + }, +#endif +}; + +module_pci_driver(sst_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_AUTHOR("Dharageswari R <dharageswari.r@intel.com>"); +MODULE_AUTHOR("KP Jeeja <jeeja.kp@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/sst/sst_pvt.c b/sound/soc/intel/sst/sst_pvt.c new file mode 100644 index 00000000000..4b772086449 --- /dev/null +++ b/sound/soc/intel/sst/sst_pvt.c @@ -0,0 +1,449 @@ +/* + * sst_pvt.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/kobject.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <sound/asound.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +int sst_shim_write(void __iomem *addr, int offset, int value) +{ + writel(value, addr + offset); + return 0; +} + +u32 sst_shim_read(void __iomem *addr, int offset) +{ + return readl(addr + offset); +} + +u64 sst_reg_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + + return val; +} + +int sst_shim_write64(void __iomem *addr, int offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); + return 0; +} + +u64 sst_shim_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} + +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) +{ + mutex_lock(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; + mutex_unlock(&sst_drv_ctx->sst_lock); +} + +/* + * sst_wait_interruptible - wait on event + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits without a timeout (and is interruptable) for a + * given block event + */ +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block) +{ + int retval = 0; + + if (!wait_event_interruptible(sst_drv_ctx->wait_queue, + block->condition)) { + /* event wake */ + if (block->ret_code < 0) { + dev_err(sst_drv_ctx->dev, + "stream failed %d\n", block->ret_code); + retval = -EBUSY; + } else { + dev_dbg(sst_drv_ctx->dev, "event up\n"); + retval = 0; + } + } else { + dev_err(sst_drv_ctx->dev, "signal interrupted\n"); + retval = -EINTR; + } + return retval; + +} + +unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) +{ + unsigned long long val = 0; + + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + val = sst_shim_read64(sst->shim, addr); + break; + } + return val; +} + +void write_shim_data(struct intel_sst_drv *sst, int addr, + unsigned long long data) +{ + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + sst_shim_write64(sst->shim, addr, (u64) data); + break; + } +} + +/* + * sst_wait_timeout - wait on event for timeout + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits with a timeout value (and is not interruptible) on a + * given block event + */ +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) +{ + int retval = 0; + + /* + * NOTE: + * Observed that FW processes the alloc msg and replies even + * before the alloc thread has finished execution + */ + dev_dbg(sst_drv_ctx->dev, + "waiting for condition %x ipc %d drv_id %d\n", + block->condition, block->msg_id, block->drv_id); + if (wait_event_timeout(sst_drv_ctx->wait_queue, + block->condition, + msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { + /* event wake */ + dev_dbg(sst_drv_ctx->dev, "Event wake %x\n", + block->condition); + dev_dbg(sst_drv_ctx->dev, "message ret: %d\n", + block->ret_code); + retval = -block->ret_code; + } else { + block->on = false; + dev_err(sst_drv_ctx->dev, + "Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n", + block->condition, block->msg_id, sst_drv_ctx->sst_state); + sst_drv_ctx->sst_state = SST_RESET; + + retval = -EBUSY; + } + return retval; +} + +/* + * sst_create_ipc_msg - create a IPC message + * + * @arg: ipc message + * @large: large or short message + * + * this function allocates structures to send a large or short + * message to the firmware + */ +int sst_create_ipc_msg(struct ipc_post **arg, bool large) +{ + struct ipc_post *msg; + + msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + if (large) { + msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); + if (!msg->mailbox_data) { + kfree(msg); + return -ENOMEM; + } + } else { + msg->mailbox_data = NULL; + } + msg->is_large = large; + *arg = msg; + return 0; +} + +/* + * sst_create_block_and_ipc_msg - Creates IPC message and sst block + * @arg: passed to sst_create_ipc_message API + * @large: large or short message + * @sst_drv_ctx: sst driver context + * @block: return block allocated + * @msg_id: IPC + * @drv_id: stream id or private id + */ +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id) +{ + int retval = 0; + + retval = sst_create_ipc_msg(arg, large); + if (retval) + return retval; + *block = sst_create_block(sst_drv_ctx, msg_id, drv_id); + if (*block == NULL) { + kfree(*arg); + return -ENOMEM; + } + return retval; +} + +/* + * sst_clean_stream - clean the stream context + * + * @stream: stream structure + * + * this function resets the stream contexts + * should be called in free + */ +void sst_clean_stream(struct stream_info *stream) +{ + stream->status = STREAM_UN_INIT; + stream->prev = STREAM_UN_INIT; + mutex_lock(&stream->lock); + stream->cumm_bytes = 0; + mutex_unlock(&stream->lock); +} + +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response) +{ + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + struct sst_block *block; + int ret = 0, pvt_id; + + pvt_id = sst_assign_pvt_id(sst); + if (pvt_id < 0) + return pvt_id; + + if (response) + ret = sst_create_block_and_ipc_msg( + &msg, large, sst, &block, ipc_msg, pvt_id); + else + ret = sst_create_ipc_msg(&msg, large); + + if (ret < 0) { + test_and_clear_bit(pvt_id, &sst->pvt_id); + return -ENOMEM; + } + + dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n", + pvt_id, pipe_id, task_id, ipc_msg); + sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg, + task_id, large, pvt_id); + msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len; + msg->mrfld_header.p.header_high.part.res_rqd = !sync; + dev_dbg(sst->dev, "header:%x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst->dev, "response rqd: %x", + msg->mrfld_header.p.header_high.part.res_rqd); + dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + if (fill_dsp) { + sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + if (mbox_data_len) { + memcpy(msg->mailbox_data + sizeof(dsp_hdr), + mbox_data, mbox_data_len); + } + } + + if (sync) + sst->ops->post_message(sst, msg, true); + else + sst_add_to_dispatch_list_and_post(sst, msg); + + if (response) { + ret = sst_wait_timeout(sst, block); + if (ret < 0) { + goto out; + } else if(block->data) { + if (!data) + goto out; + *data = kzalloc(block->size, GFP_KERNEL); + if (!(*data)) { + ret = -ENOMEM; + goto out; + } else + memcpy(data, (void *) block->data, block->size); + } + } +out: + if (response) + sst_free_block(sst, block); + test_and_clear_bit(pvt_id, &sst->pvt_id); + return ret; +} + +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{ + int ret; + + pm_runtime_mark_last_busy(sst_drv->dev); + ret = pm_runtime_put_autosuspend(sst_drv->dev); + if (ret < 0) + return ret; + return 0; +} + +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id) +{ + header->full = 0; + header->p.header_high.part.msg_id = msg; + header->p.header_high.part.task_id = task_id; + header->p.header_high.part.large = large; + header->p.header_high.part.drv_id = drv_id; + header->p.header_high.part.done = 0; + header->p.header_high.part.busy = 1; + header->p.header_high.part.res_rqd = 1; +} + +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len) +{ + dsp->cmd_id = msg; + dsp->mod_index_id = 0xff; + dsp->pipe_id = pipe_id; + dsp->length = len; + dsp->mod_id = 0; +} + +#define SST_MAX_BLOCKS 15 +/* + * sst_assign_pvt_id - assign a pvt id for stream + * + * @sst_drv_ctx : driver context + * + * this function assigns a private id for calls that dont have stream + * context yet, should be called with lock held + * uses bits for the id, and finds first free bits and assigns that + */ +int sst_assign_pvt_id(struct intel_sst_drv *drv) +{ + int local; + + spin_lock(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ + spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); + spin_unlock(&drv->block_lock); + return local; +} + +void sst_init_stream(struct stream_info *stream, + int codec, int sst_id, int ops, u8 slot) +{ + stream->status = STREAM_INIT; + stream->prev = STREAM_UN_INIT; + stream->ops = ops; +} + +int sst_validate_strid( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) { + dev_err(sst_drv_ctx->dev, + "SST ERR: invalid stream id : %d, max %d\n", + str_id, sst_drv_ctx->info.max_streams); + return -EINVAL; + } + + return 0; +} + +struct stream_info *get_stream_info( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (sst_validate_strid(sst_drv_ctx, str_id)) + return NULL; + return &sst_drv_ctx->streams[str_id]; +} + +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id) +{ + int i; + + for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) + if (pipe_id == sst_drv_ctx->streams[i].pipe_id) + return i; + + dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id); + return -1; +} + +u32 relocate_imr_addr_mrfld(u32 base_addr) +{ + /* Get the difference from 512MB aligned base addr */ + /* relocate the base */ + base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024)); + return base_addr; +} +EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld); + +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags); + list_add_tail(&msg->node, &sst->ipc_dispatch_list); + spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags); + sst->ops->post_message(sst, NULL, false); +} diff --git a/sound/soc/intel/sst/sst_stream.c b/sound/soc/intel/sst/sst_stream.c new file mode 100644 index 00000000000..dae2a41997a --- /dev/null +++ b/sound/soc/intel/sst/sst_stream.c @@ -0,0 +1,437 @@ +/* + * sst_stream.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul <vinod.koul@intel.com> + * Harsha Priya <priya.harsha@intel.com> + * Dharageswari R <dharageswari.r@intel.com> + * KP Jeeja <jeeja.kp@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/pci.h> +#include <linux/firmware.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) +{ + struct snd_sst_alloc_mrfld alloc_param; + struct snd_sst_params *str_params; + struct snd_sst_tstamp fw_tstamp; + struct stream_info *str_info; + struct snd_sst_alloc_response *response; + unsigned int str_id, pipe_id, task_id; + int i, num_ch, ret = 0; + void *data = NULL; + + dev_dbg(sst_drv_ctx->dev, "Enter\n"); + BUG_ON(!params); + + str_params = (struct snd_sst_params *)params; + memset(&alloc_param, 0, sizeof(alloc_param)); + alloc_param.operation = str_params->ops; + alloc_param.codec_type = str_params->codec; + alloc_param.sg_count = str_params->aparams.sg_count; + alloc_param.ring_buf_info[0].addr = + str_params->aparams.ring_buf_info[0].addr; + alloc_param.ring_buf_info[0].size = + str_params->aparams.ring_buf_info[0].size; + alloc_param.frag_size = str_params->aparams.frag_size; + + memcpy(&alloc_param.codec_params, &str_params->sparams, + sizeof(struct snd_sst_stream_params)); + + /* + * fill channel map params for multichannel support. + * Ideally channel map should be received from upper layers + * for multichannel support. + * Currently hardcoding as per FW reqm. + */ + num_ch = sst_get_num_channel(str_params); + for (i = 0; i < 8; i++) { + if (i < num_ch) + alloc_param.codec_params.uc.pcm_params.channel_map[i] = i; + else + alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF; + } + + str_id = str_params->stream_id; + str_info = get_stream_info(sst_drv_ctx, str_id); + if (str_info == NULL) { + dev_err(sst_drv_ctx->dev, "get stream info returned null\n"); + return -EINVAL; + } + + pipe_id = str_params->device_type; + task_id = str_params->task; + sst_drv_ctx->streams[str_id].pipe_id = pipe_id; + sst_drv_ctx->streams[str_id].task_id = task_id; + sst_drv_ctx->streams[str_id].num_ch = num_ch; + + if (sst_drv_ctx->info.lpe_viewpt_rqd) + alloc_param.ts = sst_drv_ctx->info.mailbox_start + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + else + alloc_param.ts = sst_drv_ctx->mailbox_add + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + + dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n", + alloc_param.ts); + dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n", + pipe_id, task_id); + + /* allocate device type context */ + sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type, + str_id, alloc_param.operation, 0); + + dev_info(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n", + str_id, pipe_id); + ret = sst_prepare_and_post_msg(sst_drv_ctx, task_id, IPC_CMD, + IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param), + &alloc_param, data, true, true, false, true); + + if (ret < 0) { + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + /* alloc failed, so reset the state to uninit */ + str_info->status = STREAM_UN_INIT; + str_id = ret; + } else if (data) { + response = (struct snd_sst_alloc_response *)data; + ret = response->str_type.result; + if (!ret) + goto out; + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + if (ret == SST_ERR_STREAM_IN_USE) { + dev_err(sst_drv_ctx->dev, + "FW not in clean state, send free for:%d\n", str_id); + sst_free_stream(sst_drv_ctx, str_id); + } + str_id = -ret; + } +out: + kfree(data); + return str_id; +} + +/** +* sst_start_stream - Send msg for a starting stream +* @str_id: stream ID +* +* This function is called by any function which wants to start +* a stream. +*/ +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + u16 data = 0; + + dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id, + sizeof(u16), &data, NULL, true, true, true, false); + + return retval; +} + +int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_bytes_v2 *bytes) +{ struct ipc_post *msg = NULL; + u32 length; + int pvt_id, ret = 0; + struct sst_block *block = NULL; + + dev_dbg(sst_drv_ctx->dev, + "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", + bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id, + bytes->pipe_id, bytes->len); + + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, + bytes->task_id, 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; + length = bytes->len; + msg->mrfld_header.p.header_low_payload = length; + dev_dbg(sst_drv_ctx->dev, "length is %d\n", length); + memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); + if (bytes->block) { + block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); + if (block == NULL) { + kfree(msg); + ret = -ENOMEM; + goto out; + } + } + + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + + if (bytes->block) { + ret = sst_wait_timeout(sst_drv_ctx, block); + if (ret) { + dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret); + sst_free_block(sst_drv_ctx, block); + goto out; + } + } + if (bytes->type == SND_SST_BYTES_GET) { + /* + * copy the reply and send back + * we need to update only sz and payload + */ + if (bytes->block) { + unsigned char *r = block->data; + + dev_dbg(sst_drv_ctx->dev, "read back %d bytes", + bytes->len); + memcpy(bytes->bytes, r, bytes->len); + } + } + if (bytes->block) + sst_free_block(sst_drv_ctx, block); +out: + test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id); + return 0; +} + +/* + * sst_pause_stream - Send msg for a pausing stream + * @str_id: stream ID + * + * This function is called by any function which wants to pause + * an already running stream. + */ +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_PAUSED) + return 0; + if (str_info->status == STREAM_RUNNING || + str_info->status == STREAM_INIT) { + if (str_info->prev == STREAM_UN_INIT) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id, + 0, NULL, NULL, true, true, false, true); + + if (retval == 0) { + str_info->prev = str_info->status; + str_info->status = STREAM_PAUSED; + } else if (retval == SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n "); + } + + return retval; +} + +/** + * sst_resume_stream - Send msg for resuming stream + * @str_id: stream ID + * + * This function is called by any function which wants to resume + * an already paused stream. + */ +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_RUNNING) + return 0; + if (str_info->status == STREAM_PAUSED) { + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, false, true); + + if (!retval) { + if (str_info->prev == STREAM_RUNNING) + str_info->status = STREAM_RUNNING; + else + str_info->status = STREAM_INIT; + str_info->prev = STREAM_PAUSED; + } else if (retval == -SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n"); + } + + return retval; +} + + +/** + * sst_drop_stream - Send msg for stopping stream + * @str_id: stream ID + * + * This function is called by any function which wants to stop + * a stream. + */ +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + str_info->cumm_bytes = 0; + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_DROP_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, true, false); + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n", + str_info->status); + } + return retval; +} + +/** +* sst_drain_stream - Send msg for draining stream +* @str_id: stream ID +* +* This function is called by any function which wants to drain +* a stream. +*/ +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING && + str_info->status != STREAM_INIT && + str_info->status != STREAM_PAUSED) { + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n", + str_info->status); + return -EBADRQC; + } + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id, + sizeof(u8), &partial_drain, NULL, true, true, false, false); + /* + * with new non blocked drain implementation in core we dont need to + * wait for respsonse, and need to only invoke callback for drain + * complete + */ + + return retval; +} + +/** + * sst_free_stream - Frees a stream + * @str_id: stream ID + * + * This function is called by any function which wants to free + * a stream. + */ +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + struct intel_sst_ops *ops; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id); + + mutex_lock(&sst_drv_ctx->sst_lock); + if (sst_drv_ctx->sst_state == SST_RESET) { + mutex_unlock(&sst_drv_ctx->sst_lock); + return -ENODEV; + } + mutex_unlock(&sst_drv_ctx->sst_lock); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + + mutex_lock(&str_info->lock); + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = str_info->status; + str_info->status = STREAM_UN_INIT; + mutex_unlock(&str_info->lock); + + dev_info(sst_drv_ctx->dev, "Free for str %d pipe %#x\n", + str_id, str_info->pipe_id); + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0, + NULL, NULL, true, true, false, true); + + dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n", + retval); + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n"); + } else { + mutex_unlock(&str_info->lock); + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n"); + } + + return retval; +} diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c index 5cb91f9e862..0fb7d2a91c3 100644 --- a/sound/soc/jz4740/qi_lb60.c +++ b/sound/soc/jz4740/qi_lb60.c @@ -77,25 +77,18 @@ static int qi_lb60_probe(struct platform_device *pdev) { struct qi_lb60 *qi_lb60; struct snd_soc_card *card = &qi_lb60_card; - int ret; qi_lb60 = devm_kzalloc(&pdev->dev, sizeof(*qi_lb60), GFP_KERNEL); if (!qi_lb60) return -ENOMEM; - qi_lb60->snd_gpio = devm_gpiod_get(&pdev->dev, "snd"); + qi_lb60->snd_gpio = devm_gpiod_get(&pdev->dev, "snd", GPIOD_OUT_LOW); if (IS_ERR(qi_lb60->snd_gpio)) return PTR_ERR(qi_lb60->snd_gpio); - ret = gpiod_direction_output(qi_lb60->snd_gpio, 0); - if (ret) - return ret; - qi_lb60->amp_gpio = devm_gpiod_get(&pdev->dev, "amp"); + qi_lb60->amp_gpio = devm_gpiod_get(&pdev->dev, "amp", GPIOD_OUT_LOW); if (IS_ERR(qi_lb60->amp_gpio)) return PTR_ERR(qi_lb60->amp_gpio); - ret = gpiod_direction_output(qi_lb60->amp_gpio, 0); - if (ret) - return ret; card->dev = &pdev->dev; diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index 231d7e7b071..83b2fea0921 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -773,7 +773,7 @@ static int mxs_saif_probe(struct platform_device *pdev) saif->dev = &pdev->dev; ret = devm_request_irq(&pdev->dev, saif->irq, mxs_saif_irq, 0, - "mxs-saif", saif); + dev_name(&pdev->dev), saif); if (ret) { dev_err(&pdev->dev, "failed to request irq\n"); return ret; diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c index 61822cc53bd..3bba6cfe4f2 100644 --- a/sound/soc/mxs/mxs-sgtl5000.c +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -49,13 +49,6 @@ static int mxs_sgtl5000_hw_params(struct snd_pcm_substream *substream, break; } - /* Sgtl5000 sysclk should be >= 8MHz and <= 27M */ - if (mclk < 8000000 || mclk > 27000000) { - dev_err(codec_dai->dev, "Invalid mclk frequency: %u.%03uMHz\n", - mclk / 1000000, mclk / 1000 % 1000); - return -EINVAL; - } - /* Set SGTL5000's SYSCLK (provided by SAIF MCLK) */ ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, mclk, 0); if (ret) { diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c index f2f67942b22..dff443e4b65 100644 --- a/sound/soc/nuc900/nuc900-ac97.c +++ b/sound/soc/nuc900/nuc900-ac97.c @@ -298,7 +298,7 @@ static const struct snd_soc_dai_ops nuc900_ac97_dai_ops = { static struct snd_soc_dai_driver nuc900_ac97_dai = { .probe = nuc900_ac97_probe, .remove = nuc900_ac97_remove, - .ac97_control = 1, + .bus_control = true, .playback = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index d44463a7b0f..2738b198441 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -25,15 +25,15 @@ config SND_OMAP_SOC_N810 Say Y if you want to add support for SoC audio on Nokia N810. config SND_OMAP_SOC_RX51 - tristate "SoC Audio support for Nokia RX-51" - depends on SND_OMAP_SOC && ARM && (MACH_NOKIA_RX51 || COMPILE_TEST) && I2C + tristate "SoC Audio support for Nokia N900 (RX-51)" + depends on SND_OMAP_SOC && ARM && I2C select SND_OMAP_SOC_MCBSP select SND_SOC_TLV320AIC3X select SND_SOC_TPA6130A2 depends on GPIOLIB help - Say Y if you want to add support for SoC audio on Nokia RX-51 - hardware. This is also known as Nokia N900 product. + Say Y if you want to add support for SoC audio on Nokia N900 + cellphone. config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c index 86c75384c3c..68a12520537 100644 --- a/sound/soc/omap/mcbsp.c +++ b/sound/soc/omap/mcbsp.c @@ -621,8 +621,7 @@ void omap_mcbsp_free(struct omap_mcbsp *mcbsp) mcbsp->reg_cache = NULL; spin_unlock(&mcbsp->lock); - if (reg_cache) - kfree(reg_cache); + kfree(reg_cache); } /* diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index 595eee341e9..a6b2be20cc0 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -127,15 +127,12 @@ static const struct snd_soc_dapm_route audio_map[] = { static int mioa701_wm9713_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; - unsigned short reg; /* Prepare GPIO8 for rear speaker amplifier */ - reg = codec->driver->read(codec, AC97_GPIO_CFG); - codec->driver->write(codec, AC97_GPIO_CFG, reg | 0x0100); + snd_soc_update_bits(codec, AC97_GPIO_CFG, 0x100, 0x100); /* Prepare MIC input */ - reg = codec->driver->read(codec, AC97_3D_CONTROL); - codec->driver->write(codec, AC97_3D_CONTROL, reg | 0xc000); + snd_soc_update_bits(codec, AC97_3D_CONTROL, 0xc000, 0xc000); return 0; } diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index a8e09743307..cbba063a721 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -97,7 +97,7 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, int ret = 0; if (!cpu_dai->active) { - clk_enable(ssp->clk); + clk_prepare_enable(ssp->clk); pxa_ssp_disable(ssp); } @@ -121,7 +121,7 @@ static void pxa_ssp_shutdown(struct snd_pcm_substream *substream, if (!cpu_dai->active) { pxa_ssp_disable(ssp); - clk_disable(ssp->clk); + clk_disable_unprepare(ssp->clk); } kfree(snd_soc_dai_get_dma_data(cpu_dai, substream)); @@ -136,7 +136,7 @@ static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai) struct ssp_device *ssp = priv->ssp; if (!cpu_dai->active) - clk_enable(ssp->clk); + clk_prepare_enable(ssp->clk); priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0); priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1); @@ -144,7 +144,7 @@ static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai) priv->psp = __raw_readl(ssp->mmio_base + SSPSP); pxa_ssp_disable(ssp); - clk_disable(ssp->clk); + clk_disable_unprepare(ssp->clk); return 0; } @@ -154,7 +154,7 @@ static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai) struct ssp_device *ssp = priv->ssp; uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE; - clk_enable(ssp->clk); + clk_prepare_enable(ssp->clk); __raw_writel(sssr, ssp->mmio_base + SSSR); __raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0); @@ -165,7 +165,7 @@ static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai) if (cpu_dai->active) pxa_ssp_enable(ssp); else - clk_disable(ssp->clk); + clk_disable_unprepare(ssp->clk); return 0; } @@ -256,11 +256,11 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, /* The SSP clock must be disabled when changing SSP clock mode * on PXA2xx. On PXA3xx it must be enabled when doing so. */ if (ssp->type != PXA3xx_SSP) - clk_disable(ssp->clk); + clk_disable_unprepare(ssp->clk); val = pxa_ssp_read_reg(ssp, SSCR0) | sscr0; pxa_ssp_write_reg(ssp, SSCR0, val); if (ssp->type != PXA3xx_SSP) - clk_enable(ssp->clk); + clk_prepare_enable(ssp->clk); return 0; } diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index ae956e3f4b9..73ca2820c08 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -157,7 +157,7 @@ static const struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = { static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = { { .name = "pxa2xx-ac97", - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 Playback", .channels_min = 2, @@ -174,7 +174,7 @@ static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = { }, { .name = "pxa2xx-ac97-aux", - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 Aux Playback", .channels_min = 1, @@ -191,7 +191,7 @@ static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = { }, { .name = "pxa2xx-ac97-mic", - .ac97_control = 1, + .bus_control = true, .capture = { .stream_name = "AC97 Mic Capture", .channels_min = 1, diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index 1373b017a95..d7d5fb20ea6 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -305,19 +305,15 @@ static struct snd_soc_card snd_soc_spitz = { .num_dapm_routes = ARRAY_SIZE(spitz_audio_map), }; -static struct platform_device *spitz_snd_device; - -static int __init spitz_init(void) +static int spitz_probe(struct platform_device *pdev) { + struct snd_soc_card *card = &snd_soc_spitz; int ret; - if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita())) - return -ENODEV; - - if (machine_is_borzoi() || machine_is_spitz()) - spitz_mic_gpio = SPITZ_GPIO_MIC_BIAS; - else + if (machine_is_akita()) spitz_mic_gpio = AKITA_GPIO_MIC_BIAS; + else + spitz_mic_gpio = SPITZ_GPIO_MIC_BIAS; ret = gpio_request(spitz_mic_gpio, "MIC GPIO"); if (ret) @@ -327,37 +323,45 @@ static int __init spitz_init(void) if (ret) goto err2; - spitz_snd_device = platform_device_alloc("soc-audio", -1); - if (!spitz_snd_device) { - ret = -ENOMEM; + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); goto err2; } - platform_set_drvdata(spitz_snd_device, &snd_soc_spitz); - - ret = platform_device_add(spitz_snd_device); - if (ret) - goto err3; - return 0; -err3: - platform_device_put(spitz_snd_device); err2: gpio_free(spitz_mic_gpio); err1: return ret; } -static void __exit spitz_exit(void) +static int spitz_remove(struct platform_device *pdev) { - platform_device_unregister(spitz_snd_device); + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); gpio_free(spitz_mic_gpio); + return 0; } -module_init(spitz_init); -module_exit(spitz_exit); +static struct platform_driver spitz_driver = { + .driver = { + .name = "spitz-audio", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = spitz_probe, + .remove = spitz_remove, +}; + +module_platform_driver(spitz_driver); MODULE_AUTHOR("Richard Purdie"); MODULE_DESCRIPTION("ALSA SoC Spitz"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spitz-audio"); diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index 78fc159559b..e18182699d8 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -1,11 +1,16 @@ config SND_SOC_ROCKCHIP tristate "ASoC support for Rockchip" depends on COMPILE_TEST || ARCH_ROCKCHIP - select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for codecs attached to the Rockchip SoCs' Audio interfaces. You will also need to select the audio interfaces to support below. config SND_SOC_ROCKCHIP_I2S - tristate + tristate "Rockchip I2S Device Driver" + depends on CLKDEV_LOOKUP && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S driver for + Rockchip I2S device. The device supports upto maximum of + 8 channels each for play and record. diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index f373e37f830..c74ba37f862 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -154,8 +154,10 @@ static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on) while (val) { regmap_read(i2s->regmap, I2S_CLR, &val); retry--; - if (!retry) + if (!retry) { dev_warn(i2s->dev, "fail to clear\n"); + break; + } } } } diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig deleted file mode 100644 index f244a2566f2..00000000000 --- a/sound/soc/s6000/Kconfig +++ /dev/null @@ -1,26 +0,0 @@ -config SND_S6000_SOC - tristate "SoC Audio for the Stretch s6000 family" - depends on XTENSA_VARIANT_S6000 || COMPILE_TEST - depends on HAS_IOMEM - select SND_S6000_SOC_PCM if XTENSA_VARIANT_S6000 - help - Say Y or M if you want to add support for codecs attached to - s6000 family chips. You will also need to select the platform - to support below. - -config SND_S6000_SOC_PCM - tristate - -config SND_S6000_SOC_I2S - tristate - -config SND_S6000_SOC_S6IPCAM - bool "SoC Audio support for Stretch 6105 IP Camera" - depends on SND_S6000_SOC=y - depends on I2C=y - depends on XTENSA_PLATFORM_S6105 || COMPILE_TEST - select SND_S6000_SOC_I2S - select SND_SOC_TLV320AIC3X - help - Say Y if you want to add support for SoC audio on the - Stretch s6105 IP Camera Reference Design. diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile deleted file mode 100644 index 0f0ae2a012a..00000000000 --- a/sound/soc/s6000/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# s6000 Platform Support -snd-soc-s6000-objs := s6000-pcm.o -snd-soc-s6000-i2s-objs := s6000-i2s.o - -obj-$(CONFIG_SND_S6000_SOC_PCM) += snd-soc-s6000.o -obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o - -# s6105 Machine Support -snd-soc-s6ipcam-objs := s6105-ipcam.o - -obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c deleted file mode 100644 index 1c8d01166e5..00000000000 --- a/sound/soc/s6000/s6000-i2s.c +++ /dev/null @@ -1,617 +0,0 @@ -/* - * ALSA SoC I2S Audio Layer for the Stretch S6000 family - * - * Author: Daniel Gloeckner, <dg@emlix.com> - * Copyright: (C) 2009 emlix GmbH <info@emlix.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> -#include <linux/delay.h> -#include <linux/clk.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/slab.h> - -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> -#include <sound/soc.h> - -#include "s6000-i2s.h" -#include "s6000-pcm.h" - -struct s6000_i2s_dev { - dma_addr_t sifbase; - u8 __iomem *scbbase; - unsigned int wide; - unsigned int channel_in; - unsigned int channel_out; - unsigned int lines_in; - unsigned int lines_out; - struct s6000_pcm_dma_params dma_params; -}; - -#define S6_I2S_INTERRUPT_STATUS 0x00 -#define S6_I2S_INT_OVERRUN 1 -#define S6_I2S_INT_UNDERRUN 2 -#define S6_I2S_INT_ALIGNMENT 4 -#define S6_I2S_INTERRUPT_ENABLE 0x04 -#define S6_I2S_INTERRUPT_RAW 0x08 -#define S6_I2S_INTERRUPT_CLEAR 0x0C -#define S6_I2S_INTERRUPT_SET 0x10 -#define S6_I2S_MODE 0x20 -#define S6_I2S_DUAL 0 -#define S6_I2S_WIDE 1 -#define S6_I2S_TX_DEFAULT 0x24 -#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c)) -#define S6_I2S_IN 0 -#define S6_I2S_OUT 1 -#define S6_I2S_UNUSED 2 -#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c)) -#define S6_I2S_DIV_MASK 0x001fff -#define S6_I2S_16BIT 0x000000 -#define S6_I2S_20BIT 0x002000 -#define S6_I2S_24BIT 0x004000 -#define S6_I2S_32BIT 0x006000 -#define S6_I2S_BITS_MASK 0x006000 -#define S6_I2S_MEM_16BIT 0x000000 -#define S6_I2S_MEM_32BIT 0x008000 -#define S6_I2S_MEM_MASK 0x008000 -#define S6_I2S_CHANNELS_SHIFT 16 -#define S6_I2S_CHANNELS_MASK 0x030000 -#define S6_I2S_SCK_IN 0x000000 -#define S6_I2S_SCK_OUT 0x040000 -#define S6_I2S_SCK_DIR 0x040000 -#define S6_I2S_WS_IN 0x000000 -#define S6_I2S_WS_OUT 0x080000 -#define S6_I2S_WS_DIR 0x080000 -#define S6_I2S_LEFT_FIRST 0x000000 -#define S6_I2S_RIGHT_FIRST 0x100000 -#define S6_I2S_FIRST 0x100000 -#define S6_I2S_CUR_SCK 0x200000 -#define S6_I2S_CUR_WS 0x400000 -#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c)) -#define S6_I2S_DISABLE_IF 0x02 -#define S6_I2S_ENABLE_IF 0x03 -#define S6_I2S_IS_BUSY 0x04 -#define S6_I2S_DMA_ACTIVE 0x08 -#define S6_I2S_IS_ENABLED 0x10 - -#define S6_I2S_NUM_LINES 4 - -#define S6_I2S_SIF_PORT0 0x0000000 -#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */ - -static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val) -{ - writel(val, dev->scbbase + reg); -} - -static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg) -{ - return readl(dev->scbbase + reg); -} - -static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg, - u32 mask, u32 val) -{ - val ^= s6_i2s_read_reg(dev, reg) & ~mask; - s6_i2s_write_reg(dev, reg, val); -} - -static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel) -{ - int i, j, cur, prev; - - /* - * Wait for WCLK to toggle 5 times before enabling the channel - * s6000 Family Datasheet 3.6.4: - * "At least two cycles of WS must occur between commands - * to disable or enable the interface" - */ - j = 0; - prev = ~S6_I2S_CUR_WS; - for (i = 1000000; --i && j < 6; ) { - cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel)) - & S6_I2S_CUR_WS; - if (prev != cur) { - prev = cur; - j++; - } - } - if (j < 6) - printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n"); - - s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF); -} - -static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel) -{ - s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF); -} - -static void s6000_i2s_start(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); - int channel; - - channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - dev->channel_out : dev->channel_in; - - s6000_i2s_start_channel(dev, channel); -} - -static void s6000_i2s_stop(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); - int channel; - - channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - dev->channel_out : dev->channel_in; - - s6000_i2s_stop_channel(dev, channel); -} - -static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - int after) -{ - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after) - s6000_i2s_start(substream); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (!after) - s6000_i2s_stop(substream); - } - return 0; -} - -static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev) -{ - unsigned int pending; - pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW); - pending &= S6_I2S_INT_ALIGNMENT | - S6_I2S_INT_UNDERRUN | - S6_I2S_INT_OVERRUN; - s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending); - - return pending; -} - -static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai) -{ - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int errors; - unsigned int ret; - - errors = s6000_i2s_int_sources(dev); - if (likely(!errors)) - return 0; - - ret = 0; - if (errors & S6_I2S_INT_ALIGNMENT) - printk(KERN_ERR "s6000-i2s: WCLK misaligned\n"); - if (errors & S6_I2S_INT_UNDERRUN) - ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK; - if (errors & S6_I2S_INT_OVERRUN) - ret |= 1 << SNDRV_PCM_STREAM_CAPTURE; - return ret; -} - -static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev) -{ - int channel; - int n = 50; - for (channel = 0; channel < 2; channel++) { - while (--n >= 0) { - int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel)); - if ((v & S6_I2S_IS_ENABLED) - || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY))) - break; - udelay(20); - } - } - if (n < 0) - printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces"); -} - -static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, - unsigned int fmt) -{ - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); - u32 w; - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - w = S6_I2S_SCK_IN | S6_I2S_WS_IN; - break; - case SND_SOC_DAIFMT_CBS_CFM: - w = S6_I2S_SCK_OUT | S6_I2S_WS_IN; - break; - case SND_SOC_DAIFMT_CBM_CFS: - w = S6_I2S_SCK_IN | S6_I2S_WS_OUT; - break; - case SND_SOC_DAIFMT_CBS_CFS: - w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - w |= S6_I2S_LEFT_FIRST; - break; - case SND_SOC_DAIFMT_NB_IF: - w |= S6_I2S_RIGHT_FIRST; - break; - default: - return -EINVAL; - } - - s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0), - S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); - s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1), - S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); - - return 0; -} - -static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) -{ - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); - - if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2) - return -EINVAL; - - s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id), - S6_I2S_DIV_MASK, div / 2 - 1); - return 0; -} - -static int s6000_i2s_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); - int interf; - u32 w = 0; - - if (dev->wide) - interf = 0; - else { - w |= (((params_channels(params) - 2) / 2) - << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK; - interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - ? dev->channel_out : dev->channel_in; - } - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT; - break; - case SNDRV_PCM_FORMAT_S32_LE: - w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT; - break; - default: - printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n", - params_format(params)); - return -EINVAL; - } - - if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf)) - & S6_I2S_IS_ENABLED) { - printk(KERN_ERR "s6000-i2s: interface already enabled\n"); - return -EBUSY; - } - - s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf), - S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK, - w); - - return 0; -} - -static int s6000_i2s_dai_probe(struct snd_soc_dai *dai) -{ - struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); - struct s6000_snd_platform_data *pdata = dai->dev->platform_data; - - if (!pdata) - return -EINVAL; - - dai->capture_dma_data = &dev->dma_params; - dai->playback_dma_data = &dev->dma_params; - - dev->wide = pdata->wide; - dev->channel_in = pdata->channel_in; - dev->channel_out = pdata->channel_out; - dev->lines_in = pdata->lines_in; - dev->lines_out = pdata->lines_out; - - s6_i2s_write_reg(dev, S6_I2S_MODE, - dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL); - - if (dev->wide) { - int i; - - if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES) - return -EINVAL; - - dev->channel_in = 0; - dev->channel_out = 1; - dai->driver->capture.channels_min = 2 * dev->lines_in; - dai->driver->capture.channels_max = dai->driver->capture.channels_min; - dai->driver->playback.channels_min = 2 * dev->lines_out; - dai->driver->playback.channels_max = dai->driver->playback.channels_min; - - for (i = 0; i < dev->lines_out; i++) - s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT); - - for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++) - s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), - S6_I2S_UNUSED); - - for (; i < S6_I2S_NUM_LINES; i++) - s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN); - } else { - unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED}; - - if (dev->lines_in > 1 || dev->lines_out > 1) - return -EINVAL; - - dai->driver->capture.channels_min = 2 * dev->lines_in; - dai->driver->capture.channels_max = 8 * dev->lines_in; - dai->driver->playback.channels_min = 2 * dev->lines_out; - dai->driver->playback.channels_max = 8 * dev->lines_out; - - if (dev->lines_in) - cfg[dev->channel_in] = S6_I2S_IN; - if (dev->lines_out) - cfg[dev->channel_out] = S6_I2S_OUT; - - s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]); - s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]); - } - - if (dev->lines_out) { - if (dev->lines_in) { - if (!dev->dma_params.dma_out) - return -ENODEV; - } else { - dev->dma_params.dma_out = dev->dma_params.dma_in; - dev->dma_params.dma_in = 0; - } - } - dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ? - S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); - dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ? - S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); - dev->dma_params.same_rate = pdata->same_rate | pdata->wide; - return 0; -} - -#define S6000_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS -#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) - -static const struct snd_soc_dai_ops s6000_i2s_dai_ops = { - .set_fmt = s6000_i2s_set_dai_fmt, - .set_clkdiv = s6000_i2s_set_clkdiv, - .hw_params = s6000_i2s_hw_params, -}; - -static struct snd_soc_dai_driver s6000_i2s_dai = { - .probe = s6000_i2s_dai_probe, - .playback = { - .channels_min = 2, - .channels_max = 8, - .formats = S6000_I2S_FORMATS, - .rates = S6000_I2S_RATES, - .rate_min = 0, - .rate_max = 1562500, - }, - .capture = { - .channels_min = 2, - .channels_max = 8, - .formats = S6000_I2S_FORMATS, - .rates = S6000_I2S_RATES, - .rate_min = 0, - .rate_max = 1562500, - }, - .ops = &s6000_i2s_dai_ops, -}; - -static const struct snd_soc_component_driver s6000_i2s_component = { - .name = "s6000-i2s", -}; - -static int s6000_i2s_probe(struct platform_device *pdev) -{ - struct s6000_i2s_dev *dev; - struct resource *scbmem, *sifmem, *region, *dma1, *dma2; - u8 __iomem *mmio; - int ret; - - scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!scbmem) { - dev_err(&pdev->dev, "no mem resource?\n"); - ret = -ENODEV; - goto err_release_none; - } - - region = request_mem_region(scbmem->start, resource_size(scbmem), - pdev->name); - if (!region) { - dev_err(&pdev->dev, "I2S SCB region already claimed\n"); - ret = -EBUSY; - goto err_release_none; - } - - mmio = ioremap(scbmem->start, resource_size(scbmem)); - if (!mmio) { - dev_err(&pdev->dev, "can't ioremap SCB region\n"); - ret = -ENOMEM; - goto err_release_scb; - } - - sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!sifmem) { - dev_err(&pdev->dev, "no second mem resource?\n"); - ret = -ENODEV; - goto err_release_map; - } - - region = request_mem_region(sifmem->start, resource_size(sifmem), - pdev->name); - if (!region) { - dev_err(&pdev->dev, "I2S SIF region already claimed\n"); - ret = -EBUSY; - goto err_release_map; - } - - dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!dma1) { - dev_err(&pdev->dev, "no dma resource?\n"); - ret = -ENODEV; - goto err_release_sif; - } - - region = request_mem_region(dma1->start, resource_size(dma1), - pdev->name); - if (!region) { - dev_err(&pdev->dev, "I2S DMA region already claimed\n"); - ret = -EBUSY; - goto err_release_sif; - } - - dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (dma2) { - region = request_mem_region(dma2->start, resource_size(dma2), - pdev->name); - if (!region) { - dev_err(&pdev->dev, - "I2S DMA region already claimed\n"); - ret = -EBUSY; - goto err_release_dma1; - } - } - - dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL); - if (!dev) { - ret = -ENOMEM; - goto err_release_dma2; - } - dev_set_drvdata(&pdev->dev, dev); - - dev->sifbase = sifmem->start; - dev->scbbase = mmio; - - s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); - s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, - S6_I2S_INT_ALIGNMENT | - S6_I2S_INT_UNDERRUN | - S6_I2S_INT_OVERRUN); - - s6000_i2s_stop_channel(dev, 0); - s6000_i2s_stop_channel(dev, 1); - s6000_i2s_wait_disabled(dev); - - dev->dma_params.check_xrun = s6000_i2s_check_xrun; - dev->dma_params.trigger = s6000_i2s_trigger; - dev->dma_params.dma_in = dma1->start; - dev->dma_params.dma_out = dma2 ? dma2->start : 0; - dev->dma_params.irq = platform_get_irq(pdev, 0); - if (dev->dma_params.irq < 0) { - dev_err(&pdev->dev, "no irq resource?\n"); - ret = -ENODEV; - goto err_release_dev; - } - - s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, - S6_I2S_INT_ALIGNMENT | - S6_I2S_INT_UNDERRUN | - S6_I2S_INT_OVERRUN); - - ret = snd_soc_register_component(&pdev->dev, &s6000_i2s_component, - &s6000_i2s_dai, 1); - if (ret) - goto err_release_dev; - - return 0; - -err_release_dev: - kfree(dev); -err_release_dma2: - if (dma2) - release_mem_region(dma2->start, resource_size(dma2)); -err_release_dma1: - release_mem_region(dma1->start, resource_size(dma1)); -err_release_sif: - release_mem_region(sifmem->start, resource_size(sifmem)); -err_release_map: - iounmap(mmio); -err_release_scb: - release_mem_region(scbmem->start, resource_size(scbmem)); -err_release_none: - return ret; -} - -static int s6000_i2s_remove(struct platform_device *pdev) -{ - struct s6000_i2s_dev *dev = dev_get_drvdata(&pdev->dev); - struct resource *region; - void __iomem *mmio = dev->scbbase; - - snd_soc_unregister_component(&pdev->dev); - - s6000_i2s_stop_channel(dev, 0); - s6000_i2s_stop_channel(dev, 1); - - s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); - kfree(dev); - - region = platform_get_resource(pdev, IORESOURCE_DMA, 0); - release_mem_region(region->start, resource_size(region)); - - region = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (region) - release_mem_region(region->start, resource_size(region)); - - region = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(region->start, resource_size(region)); - - iounmap(mmio); - region = platform_get_resource(pdev, IORESOURCE_IO, 0); - release_mem_region(region->start, resource_size(region)); - - return 0; -} - -static struct platform_driver s6000_i2s_driver = { - .probe = s6000_i2s_probe, - .remove = s6000_i2s_remove, - .driver = { - .name = "s6000-i2s", - .owner = THIS_MODULE, - }, -}; - -module_platform_driver(s6000_i2s_driver); - -MODULE_AUTHOR("Daniel Gloeckner"); -MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.h deleted file mode 100644 index 86aa1921c89..00000000000 --- a/sound/soc/s6000/s6000-i2s.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * ALSA SoC I2S Audio Layer for the Stretch s6000 family - * - * Author: Daniel Gloeckner, <dg@emlix.com> - * Copyright: (C) 2009 emlix GmbH <info@emlix.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef _S6000_I2S_H -#define _S6000_I2S_H - -struct s6000_snd_platform_data { - int lines_in; - int lines_out; - int channel_in; - int channel_out; - int wide; - int same_rate; -}; -#endif diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c deleted file mode 100644 index fb8461e1b1f..00000000000 --- a/sound/soc/s6000/s6000-pcm.c +++ /dev/null @@ -1,521 +0,0 @@ -/* - * ALSA PCM interface for the Stetch s6000 family - * - * Author: Daniel Gloeckner, <dg@emlix.com> - * Copyright: (C) 2009 emlix GmbH <info@emlix.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/dma-mapping.h> -#include <linux/interrupt.h> - -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> - -#include <asm/dma.h> -#include <variant/dmac.h> - -#include "s6000-pcm.h" - -#define S6_PCM_PREALLOCATE_SIZE (96 * 1024) -#define S6_PCM_PREALLOCATE_MAX (2048 * 1024) - -static struct snd_pcm_hardware s6000_pcm_hardware = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX), - .buffer_bytes_max = 0x7ffffff0, - .period_bytes_min = 16, - .period_bytes_max = 0xfffff0, - .periods_min = 2, - .periods_max = 1024, /* no limit */ - .fifo_size = 0, -}; - -struct s6000_runtime_data { - spinlock_t lock; - int period; /* current DMA period */ -}; - -static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct s6000_runtime_data *prtd = runtime->private_data; - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - int channel; - unsigned int period_size; - unsigned int dma_offset; - dma_addr_t dma_pos; - dma_addr_t src, dst; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - period_size = snd_pcm_lib_period_bytes(substream); - dma_offset = prtd->period * period_size; - dma_pos = runtime->dma_addr + dma_offset; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - src = dma_pos; - dst = par->sif_out; - channel = par->dma_out; - } else { - src = par->sif_in; - dst = dma_pos; - channel = par->dma_in; - } - - if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel), - DMA_INDEX_CHNL(channel))) - return; - - if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) { - printk(KERN_ERR "s6000-pcm: fifo full\n"); - return; - } - - if (WARN_ON(period_size & 15)) - return; - s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel), - src, dst, period_size); - - prtd->period++; - if (unlikely(prtd->period >= runtime->periods)) - prtd->period = 0; -} - -static irqreturn_t s6000_pcm_irq(int irq, void *data) -{ - struct snd_pcm *pcm = data; - struct snd_soc_pcm_runtime *runtime = pcm->private_data; - struct s6000_runtime_data *prtd; - unsigned int has_xrun; - int i, ret = IRQ_NONE; - - for (i = 0; i < 2; ++i) { - struct snd_pcm_substream *substream = pcm->streams[i].substream; - struct s6000_pcm_dma_params *params = - snd_soc_dai_get_dma_data(runtime->cpu_dai, substream); - u32 channel; - unsigned int pending; - - if (substream == SNDRV_PCM_STREAM_PLAYBACK) - channel = params->dma_out; - else - channel = params->dma_in; - - has_xrun = params->check_xrun(runtime->cpu_dai); - - if (!channel) - continue; - - if (unlikely(has_xrun & (1 << i)) && - substream->runtime && - snd_pcm_running(substream)) { - dev_dbg(pcm->dev, "xrun\n"); - snd_pcm_stream_lock(substream); - snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - snd_pcm_stream_unlock(substream); - ret = IRQ_HANDLED; - } - - pending = s6dmac_int_sources(DMA_MASK_DMAC(channel), - DMA_INDEX_CHNL(channel)); - - if (pending & 1) { - ret = IRQ_HANDLED; - if (likely(substream->runtime && - snd_pcm_running(substream))) { - snd_pcm_period_elapsed(substream); - dev_dbg(pcm->dev, "period elapsed %x %x\n", - s6dmac_cur_src(DMA_MASK_DMAC(channel), - DMA_INDEX_CHNL(channel)), - s6dmac_cur_dst(DMA_MASK_DMAC(channel), - DMA_INDEX_CHNL(channel))); - prtd = substream->runtime->private_data; - spin_lock(&prtd->lock); - s6000_pcm_enqueue_dma(substream); - spin_unlock(&prtd->lock); - } - } - - if (unlikely(pending & ~7)) { - if (pending & (1 << 3)) - printk(KERN_WARNING - "s6000-pcm: DMA %x Underflow\n", - channel); - if (pending & (1 << 4)) - printk(KERN_WARNING - "s6000-pcm: DMA %x Overflow\n", - channel); - if (pending & 0x1e0) - printk(KERN_WARNING - "s6000-pcm: DMA %x Master Error " - "(mask %x)\n", - channel, pending >> 5); - - } - } - - return ret; -} - -static int s6000_pcm_start(struct snd_pcm_substream *substream) -{ - struct s6000_runtime_data *prtd = substream->runtime->private_data; - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - unsigned long flags; - int srcinc; - u32 dma; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - spin_lock_irqsave(&prtd->lock, flags); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - srcinc = 1; - dma = par->dma_out; - } else { - srcinc = 0; - dma = par->dma_in; - } - s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma), - 1 /* priority 1 (0 is max) */, - 0 /* peripheral requests w/o xfer length mode */, - srcinc /* source address increment */, - srcinc^1 /* destination address increment */, - 0 /* chunksize 0 (skip impossible on this dma) */, - 0 /* source skip after chunk (impossible) */, - 0 /* destination skip after chunk (impossible) */, - 4 /* 16 byte burst size */, - -1 /* don't conserve bandwidth */, - 0 /* low watermark irq descriptor threshold */, - 0 /* disable hardware timestamps */, - 1 /* enable channel */); - - s6000_pcm_enqueue_dma(substream); - s6000_pcm_enqueue_dma(substream); - - spin_unlock_irqrestore(&prtd->lock, flags); - - return 0; -} - -static int s6000_pcm_stop(struct snd_pcm_substream *substream) -{ - struct s6000_runtime_data *prtd = substream->runtime->private_data; - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - unsigned long flags; - u32 channel; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - channel = par->dma_out; - else - channel = par->dma_in; - - s6dmac_set_terminal_count(DMA_MASK_DMAC(channel), - DMA_INDEX_CHNL(channel), 0); - - spin_lock_irqsave(&prtd->lock, flags); - - s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel)); - - spin_unlock_irqrestore(&prtd->lock, flags); - - return 0; -} - -static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - int ret; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - ret = par->trigger(substream, cmd, 0); - if (ret < 0) - return ret; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - ret = s6000_pcm_start(substream); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - ret = s6000_pcm_stop(substream); - break; - default: - ret = -EINVAL; - } - if (ret < 0) - return ret; - - return par->trigger(substream, cmd, 1); -} - -static int s6000_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct s6000_runtime_data *prtd = substream->runtime->private_data; - - prtd->period = 0; - - return 0; -} - -static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - struct snd_pcm_runtime *runtime = substream->runtime; - struct s6000_runtime_data *prtd = runtime->private_data; - unsigned long flags; - unsigned int offset; - dma_addr_t count; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - spin_lock_irqsave(&prtd->lock, flags); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out), - DMA_INDEX_CHNL(par->dma_out)); - else - count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in), - DMA_INDEX_CHNL(par->dma_in)); - - count -= runtime->dma_addr; - - spin_unlock_irqrestore(&prtd->lock, flags); - - offset = bytes_to_frames(runtime, count); - if (unlikely(offset >= runtime->buffer_size)) - offset = 0; - - return offset; -} - -static int s6000_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - struct snd_pcm_runtime *runtime = substream->runtime; - struct s6000_runtime_data *prtd; - int ret; - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware); - - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16); - if (ret < 0) - return ret; - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); - if (ret < 0) - return ret; - ret = snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - return ret; - - if (par->same_rate) { - int rate; - spin_lock(&par->lock); /* needed? */ - rate = par->rate; - spin_unlock(&par->lock); - if (rate != -1) { - ret = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_RATE, - rate, rate); - if (ret < 0) - return ret; - } - } - - prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL); - if (prtd == NULL) - return -ENOMEM; - - spin_lock_init(&prtd->lock); - - runtime->private_data = prtd; - - return 0; -} - -static int s6000_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct s6000_runtime_data *prtd = runtime->private_data; - - kfree(prtd); - - return 0; -} - -static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par; - int ret; - ret = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) { - printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n"); - return ret; - } - - par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - if (par->same_rate) { - spin_lock(&par->lock); - if (par->rate == -1 || - !(par->in_use & ~(1 << substream->stream))) { - par->rate = params_rate(hw_params); - par->in_use |= 1 << substream->stream; - } else if (params_rate(hw_params) != par->rate) { - snd_pcm_lib_free_pages(substream); - par->in_use &= ~(1 << substream->stream); - ret = -EBUSY; - } - spin_unlock(&par->lock); - } - return ret; -} - -static int s6000_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; - struct s6000_pcm_dma_params *par = - snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); - - spin_lock(&par->lock); - par->in_use &= ~(1 << substream->stream); - if (!par->in_use) - par->rate = -1; - spin_unlock(&par->lock); - - return snd_pcm_lib_free_pages(substream); -} - -static struct snd_pcm_ops s6000_pcm_ops = { - .open = s6000_pcm_open, - .close = s6000_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = s6000_pcm_hw_params, - .hw_free = s6000_pcm_hw_free, - .trigger = s6000_pcm_trigger, - .prepare = s6000_pcm_prepare, - .pointer = s6000_pcm_pointer, -}; - -static void s6000_pcm_free(struct snd_pcm *pcm) -{ - struct snd_soc_pcm_runtime *runtime = pcm->private_data; - struct s6000_pcm_dma_params *params = - snd_soc_dai_get_dma_data(runtime->cpu_dai, - pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream); - - free_irq(params->irq, pcm); - snd_pcm_lib_preallocate_free_for_all(pcm); -} - -static int s6000_pcm_new(struct snd_soc_pcm_runtime *runtime) -{ - struct snd_card *card = runtime->card->snd_card; - struct snd_pcm *pcm = runtime->pcm; - struct s6000_pcm_dma_params *params; - int res; - - params = snd_soc_dai_get_dma_data(runtime->cpu_dai, - pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream); - - res = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); - if (res) - return res; - - if (params->dma_in) { - s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in), - DMA_INDEX_CHNL(params->dma_in)); - s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in), - DMA_INDEX_CHNL(params->dma_in)); - } - - if (params->dma_out) { - s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out), - DMA_INDEX_CHNL(params->dma_out)); - s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out), - DMA_INDEX_CHNL(params->dma_out)); - } - - res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED, - "s6000-audio", pcm); - if (res) { - printk(KERN_ERR "s6000-pcm couldn't get IRQ\n"); - return res; - } - - res = snd_pcm_lib_preallocate_pages_for_all(pcm, - SNDRV_DMA_TYPE_DEV, - card->dev, - S6_PCM_PREALLOCATE_SIZE, - S6_PCM_PREALLOCATE_MAX); - if (res) - printk(KERN_WARNING "s6000-pcm: preallocation failed\n"); - - spin_lock_init(¶ms->lock); - params->in_use = 0; - params->rate = -1; - return 0; -} - -static struct snd_soc_platform_driver s6000_soc_platform = { - .ops = &s6000_pcm_ops, - .pcm_new = s6000_pcm_new, - .pcm_free = s6000_pcm_free, -}; - -static int s6000_soc_platform_probe(struct platform_device *pdev) -{ - return snd_soc_register_platform(&pdev->dev, &s6000_soc_platform); -} - -static int s6000_soc_platform_remove(struct platform_device *pdev) -{ - snd_soc_unregister_platform(&pdev->dev); - return 0; -} - -static struct platform_driver s6000_pcm_driver = { - .driver = { - .name = "s6000-pcm-audio", - .owner = THIS_MODULE, - }, - - .probe = s6000_soc_platform_probe, - .remove = s6000_soc_platform_remove, -}; - -module_platform_driver(s6000_pcm_driver); - -MODULE_AUTHOR("Daniel Gloeckner"); -MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.h deleted file mode 100644 index 09d9b883e58..00000000000 --- a/sound/soc/s6000/s6000-pcm.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * ALSA PCM interface for the Stretch s6000 family - * - * Author: Daniel Gloeckner, <dg@emlix.com> - * Copyright: (C) 2009 emlix GmbH <info@emlix.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef _S6000_PCM_H -#define _S6000_PCM_H - -struct snd_soc_dai; -struct snd_pcm_substream; - -struct s6000_pcm_dma_params { - unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai); - int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after); - dma_addr_t sif_in; - dma_addr_t sif_out; - u32 dma_in; - u32 dma_out; - int irq; - int same_rate; - - spinlock_t lock; - int in_use; - int rate; -}; - -#endif diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c deleted file mode 100644 index 3510c01f8a6..00000000000 --- a/sound/soc/s6000/s6105-ipcam.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * ASoC driver for Stretch s6105 IP camera platform - * - * Author: Daniel Gloeckner, <dg@emlix.com> - * Copyright: (C) 2009 emlix GmbH <info@emlix.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/i2c.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> - -#include "s6000-pcm.h" -#include "s6000-i2s.h" - -#define S6105_CAM_CODEC_CLOCK 12288000 - -static int s6105_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - int ret = 0; - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | - SND_SOC_DAIFMT_NB_NF); - if (ret < 0) - return ret; - - /* set the codec system clock */ - ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK, - SND_SOC_CLOCK_OUT); - if (ret < 0) - return ret; - - return 0; -} - -static struct snd_soc_ops s6105_ops = { - .hw_params = s6105_hw_params, -}; - -/* s6105 machine dapm widgets */ -static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { - SND_SOC_DAPM_LINE("Audio Out Differential", NULL), - SND_SOC_DAPM_LINE("Audio Out Stereo", NULL), - SND_SOC_DAPM_LINE("Audio In", NULL), -}; - -/* s6105 machine audio_mapnections to the codec pins */ -static const struct snd_soc_dapm_route audio_map[] = { - /* Audio Out connected to HPLOUT, HPLCOM, HPROUT */ - {"Audio Out Differential", NULL, "HPLOUT"}, - {"Audio Out Differential", NULL, "HPLCOM"}, - {"Audio Out Stereo", NULL, "HPLOUT"}, - {"Audio Out Stereo", NULL, "HPROUT"}, - - /* Audio In connected to LINE1L, LINE1R */ - {"LINE1L", NULL, "Audio In"}, - {"LINE1R", NULL, "Audio In"}, -}; - -static int output_type_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = 2; - if (uinfo->value.enumerated.item) { - uinfo->value.enumerated.item = 1; - strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT"); - } else { - strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM"); - } - return 0; -} - -static int output_type_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.enumerated.item[0] = kcontrol->private_value; - return 0; -} - -static int output_type_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = kcontrol->private_data; - struct snd_soc_dapm_context *dapm = &card->dapm; - unsigned int val = (ucontrol->value.enumerated.item[0] != 0); - char *differential = "Audio Out Differential"; - char *stereo = "Audio Out Stereo"; - - if (kcontrol->private_value == val) - return 0; - kcontrol->private_value = val; - snd_soc_dapm_disable_pin(dapm, val ? differential : stereo); - snd_soc_dapm_sync(dapm); - snd_soc_dapm_enable_pin(dapm, val ? stereo : differential); - snd_soc_dapm_sync(dapm); - - return 1; -} - -static const struct snd_kcontrol_new audio_out_mux = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Master Output Mux", - .index = 0, - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, - .info = output_type_info, - .get = output_type_get, - .put = output_type_put, - .private_value = 1 /* default to stereo */ -}; - -/* Logic for a aic3x as connected on the s6105 ip camera ref design */ -static int s6105_aic3x_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_card *card = rtd->card; - - /* must correspond to audio_out_mux.private_value initializer */ - snd_soc_dapm_disable_pin(&card->dapm, "Audio Out Differential"); - - snd_ctl_add(card->snd_card, snd_ctl_new1(&audio_out_mux, card)); - - return 0; -} - -/* s6105 digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link s6105_dai = { - .name = "TLV320AIC31", - .stream_name = "AIC31", - .cpu_dai_name = "s6000-i2s", - .codec_dai_name = "tlv320aic3x-hifi", - .platform_name = "s6000-pcm-audio", - .codec_name = "tlv320aic3x-codec.0-001a", - .init = s6105_aic3x_init, - .ops = &s6105_ops, -}; - -/* s6105 audio machine driver */ -static struct snd_soc_card snd_soc_card_s6105 = { - .name = "Stretch IP Camera", - .owner = THIS_MODULE, - .dai_link = &s6105_dai, - .num_links = 1, - - .dapm_widgets = aic3x_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(aic3x_dapm_widgets), - .dapm_routes = audio_map, - .num_dapm_routes = ARRAY_SIZE(audio_map), - .fully_routed = true, -}; - -static struct s6000_snd_platform_data s6105_snd_data __initdata = { - .wide = 0, - .channel_in = 0, - .channel_out = 1, - .lines_in = 1, - .lines_out = 1, - .same_rate = 1, -}; - -static struct platform_device *s6105_snd_device; - -/* temporary i2c device creation until this can be moved into the machine - * support file. -*/ -static struct i2c_board_info i2c_device[] = { - { I2C_BOARD_INFO("tlv320aic33", 0x18), } -}; - -static int __init s6105_init(void) -{ - int ret; - - i2c_register_board_info(0, i2c_device, ARRAY_SIZE(i2c_device)); - - s6105_snd_device = platform_device_alloc("soc-audio", -1); - if (!s6105_snd_device) - return -ENOMEM; - - platform_set_drvdata(s6105_snd_device, &snd_soc_card_s6105); - platform_device_add_data(s6105_snd_device, &s6105_snd_data, - sizeof(s6105_snd_data)); - - ret = platform_device_add(s6105_snd_device); - if (ret) - platform_device_put(s6105_snd_device); - - return ret; -} - -static void __exit s6105_exit(void) -{ - platform_device_unregister(s6105_snd_device); -} - -module_init(s6105_init); -module_exit(s6105_exit); - -MODULE_AUTHOR("Daniel Gloeckner"); -MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 55a38697443..fc67f97f19f 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -1,6 +1,6 @@ config SND_SOC_SAMSUNG tristate "ASoC support for Samsung" - depends on PLAT_SAMSUNG + depends on (PLAT_SAMSUNG || ARCH_EXYNOS) depends on S3C64XX_PL080 || !ARCH_S3C64XX depends on S3C24XX_DMAC || !ARCH_S3C24XX select SND_SOC_GENERIC_DMAENGINE_PCM @@ -239,3 +239,9 @@ config SND_SOC_ODROIDX2 select SND_SAMSUNG_I2S help Say Y here to enable audio support for the Odroid-X2/U3. + +config SND_SOC_ARNDALE_RT5631_ALC5631 + tristate "Audio support for RT5631(ALC5631) on Arndale Board" + depends on SND_SOC_SAMSUNG + select SND_SAMSUNG_I2S + select SND_SOC_RT5631 diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 91505ddaaf9..31e3dba7e3b 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -45,6 +45,7 @@ snd-soc-lowland-objs := lowland.o snd-soc-littlemill-objs := littlemill.o snd-soc-bells-objs := bells.o snd-soc-odroidx2-max98090-objs := odroidx2_max98090.o +snd-soc-arndale-rt5631-objs := arndale_rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -71,3 +72,4 @@ obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o obj-$(CONFIG_SND_SOC_ODROIDX2) += snd-soc-odroidx2-max98090.o +obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c index e1615113fd8..7952a625669 100644 --- a/sound/soc/samsung/ac97.c +++ b/sound/soc/samsung/ac97.c @@ -288,7 +288,7 @@ static int s3c_ac97_mic_dai_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver s3c_ac97_dai[] = { [S3C_AC97_DAI_PCM] = { .name = "samsung-ac97", - .ac97_control = 1, + .bus_control = true, .playback = { .stream_name = "AC97 Playback", .channels_min = 2, @@ -306,7 +306,7 @@ static struct snd_soc_dai_driver s3c_ac97_dai[] = { }, [S3C_AC97_DAI_MIC] = { .name = "samsung-ac97-mic", - .ac97_control = 1, + .bus_control = true, .capture = { .stream_name = "AC97 Mic Capture", .channels_min = 1, diff --git a/sound/soc/samsung/arndale_rt5631.c b/sound/soc/samsung/arndale_rt5631.c new file mode 100644 index 00000000000..1e2b61ca8db --- /dev/null +++ b/sound/soc/samsung/arndale_rt5631.c @@ -0,0 +1,150 @@ +/* + * arndale_rt5631.c + * + * Copyright (c) 2014, Insignal Co., Ltd. + * + * Author: Claude <claude@insginal.co.kr> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "i2s.h" + +static int arndale_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int rfs, ret; + unsigned long rclk; + + rfs = 256; + + rclk = params_rate(params) * rfs; + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_OUT); + + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops arndale_ops = { + .hw_params = arndale_hw_params, +}; + +static struct snd_soc_dai_link arndale_rt5631_dai[] = { + { + .name = "RT5631 HiFi", + .stream_name = "Primary", + .codec_dai_name = "rt5631-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .ops = &arndale_ops, + }, +}; + +static struct snd_soc_card arndale_rt5631 = { + .name = "Arndale RT5631", + .dai_link = arndale_rt5631_dai, + .num_links = ARRAY_SIZE(arndale_rt5631_dai), +}; + +static int arndale_audio_probe(struct platform_device *pdev) +{ + int n, ret; + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &arndale_rt5631; + + card->dev = &pdev->dev; + + for (n = 0; np && n < ARRAY_SIZE(arndale_rt5631_dai); n++) { + if (!arndale_rt5631_dai[n].cpu_dai_name) { + arndale_rt5631_dai[n].cpu_of_node = of_parse_phandle(np, + "samsung,audio-cpu", n); + + if (!arndale_rt5631_dai[n].cpu_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,audio-cpu' missing or invalid\n"); + return -EINVAL; + } + } + if (!arndale_rt5631_dai[n].platform_name) + arndale_rt5631_dai[n].platform_of_node = + arndale_rt5631_dai[n].cpu_of_node; + + arndale_rt5631_dai[n].codec_name = NULL; + arndale_rt5631_dai[n].codec_of_node = of_parse_phandle(np, + "samsung,audio-codec", n); + if (!arndale_rt5631_dai[0].codec_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,audio-codec' missing or invalid\n"); + return -EINVAL; + } + } + + ret = devm_snd_soc_register_card(card->dev, card); + + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +static int arndale_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id samsung_arndale_rt5631_of_match[] __maybe_unused = { + { .compatible = "samsung,arndale-rt5631", }, + { .compatible = "samsung,arndale-alc5631", }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_arndale_rt5631_of_match); + +static struct platform_driver arndale_audio_driver = { + .driver = { + .name = "arndale-audio", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(samsung_arndale_rt5631_of_match), + }, + .probe = arndale_audio_probe, + .remove = arndale_audio_remove, +}; + +module_platform_driver(arndale_audio_driver); + +MODULE_AUTHOR("Claude <claude@insignal.co.kr>"); +MODULE_DESCRIPTION("ALSA SoC Driver for Arndale Board"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 821a5023100..9170c311d66 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -33,8 +33,9 @@ #define I2SLVL3ADDR 0x3c #define I2SSTR1 0x40 #define I2SVER 0x44 -#define I2SFIC2 0x48 +#define I2SFIC1 0x48 #define I2STDM 0x4c +#define I2SFSTA 0x50 #define CON_RSTCLR (1 << 31) #define CON_FRXOFSTATUS (1 << 26) @@ -93,8 +94,6 @@ #define MOD_BLC_24BIT (2 << 13) #define MOD_BLC_MASK (3 << 13) -#define MOD_IMS_SYSMUX (1 << 10) -#define MOD_SLAVE (1 << 11) #define MOD_TXONLY (0 << 8) #define MOD_RXONLY (1 << 8) #define MOD_TXRX (2 << 8) @@ -132,7 +131,10 @@ #define EXYNOS5420_MOD_BCLK_256FS 8 #define EXYNOS5420_MOD_BCLK_MASK 0xf -#define MOD_CDCLKCON (1 << 12) +#define EXYNOS7_MOD_RCLK_64FS 4 +#define EXYNOS7_MOD_RCLK_128FS 5 +#define EXYNOS7_MOD_RCLK_96FS 6 +#define EXYNOS7_MOD_RCLK_192FS 7 #define PSR_PSREN (1 << 15) diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 9d513473b30..c7aafcd95de 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -36,9 +36,24 @@ enum samsung_dai_type { TYPE_SEC, }; +struct samsung_i2s_variant_regs { + unsigned int bfs_off; + unsigned int rfs_off; + unsigned int sdf_off; + unsigned int txr_off; + unsigned int rclksrc_off; + unsigned int mss_off; + unsigned int cdclkcon_off; + unsigned int lrp_off; + unsigned int bfs_mask; + unsigned int rfs_mask; + unsigned int ftx0cnt_off; +}; + struct samsung_i2s_dai_data { int dai_type; u32 quirks; + const struct samsung_i2s_variant_regs *i2s_variant_regs; }; struct i2s_dai { @@ -81,6 +96,7 @@ struct i2s_dai { u32 suspend_i2scon; u32 suspend_i2spsr; unsigned long gpios[7]; /* i2s gpio line numbers */ + const struct samsung_i2s_variant_regs *variant_regs; }; /* Lock for cross i/f checks */ @@ -95,7 +111,8 @@ static inline bool is_secondary(struct i2s_dai *i2s) /* If operating in SoC-Slave mode */ static inline bool is_slave(struct i2s_dai *i2s) { - return (readl(i2s->addr + I2SMOD) & MOD_SLAVE) ? true : false; + u32 mod = readl(i2s->addr + I2SMOD); + return (mod & (1 << i2s->variant_regs->mss_off)) ? true : false; } /* If this interface of the controller is transmitting data */ @@ -200,14 +217,14 @@ static inline bool is_manager(struct i2s_dai *i2s) static inline unsigned get_rfs(struct i2s_dai *i2s) { u32 rfs; - - if (i2s->quirks & QUIRK_SUPPORTS_TDM) - rfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_RCLK_SHIFT; - else - rfs = (readl(i2s->addr + I2SMOD) >> MOD_RCLK_SHIFT); - rfs &= MOD_RCLK_MASK; + rfs = readl(i2s->addr + I2SMOD) >> i2s->variant_regs->rfs_off; + rfs &= i2s->variant_regs->rfs_mask; switch (rfs) { + case 7: return 192; + case 6: return 96; + case 5: return 128; + case 4: return 64; case 3: return 768; case 2: return 384; case 1: return 512; @@ -219,15 +236,23 @@ static inline unsigned get_rfs(struct i2s_dai *i2s) static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) { u32 mod = readl(i2s->addr + I2SMOD); - int rfs_shift; + int rfs_shift = i2s->variant_regs->rfs_off; - if (i2s->quirks & QUIRK_SUPPORTS_TDM) - rfs_shift = EXYNOS5420_MOD_RCLK_SHIFT; - else - rfs_shift = MOD_RCLK_SHIFT; - mod &= ~(MOD_RCLK_MASK << rfs_shift); + mod &= ~(i2s->variant_regs->rfs_mask << rfs_shift); switch (rfs) { + case 192: + mod |= (EXYNOS7_MOD_RCLK_192FS << rfs_shift); + break; + case 96: + mod |= (EXYNOS7_MOD_RCLK_96FS << rfs_shift); + break; + case 128: + mod |= (EXYNOS7_MOD_RCLK_128FS << rfs_shift); + break; + case 64: + mod |= (EXYNOS7_MOD_RCLK_64FS << rfs_shift); + break; case 768: mod |= (MOD_RCLK_768FS << rfs_shift); break; @@ -249,14 +274,8 @@ static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) static inline unsigned get_bfs(struct i2s_dai *i2s) { u32 bfs; - - if (i2s->quirks & QUIRK_SUPPORTS_TDM) { - bfs = readl(i2s->addr + I2SMOD) >> EXYNOS5420_MOD_BCLK_SHIFT; - bfs &= EXYNOS5420_MOD_BCLK_MASK; - } else { - bfs = readl(i2s->addr + I2SMOD) >> MOD_BCLK_SHIFT; - bfs &= MOD_BCLK_MASK; - } + bfs = readl(i2s->addr + I2SMOD) >> i2s->variant_regs->bfs_off; + bfs &= i2s->variant_regs->bfs_mask; switch (bfs) { case 8: return 256; @@ -275,16 +294,8 @@ static inline unsigned get_bfs(struct i2s_dai *i2s) static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) { u32 mod = readl(i2s->addr + I2SMOD); - int bfs_shift; int tdm = i2s->quirks & QUIRK_SUPPORTS_TDM; - - if (i2s->quirks & QUIRK_SUPPORTS_TDM) { - bfs_shift = EXYNOS5420_MOD_BCLK_SHIFT; - mod &= ~(EXYNOS5420_MOD_BCLK_MASK << bfs_shift); - } else { - bfs_shift = MOD_BCLK_SHIFT; - mod &= ~(MOD_BCLK_MASK << bfs_shift); - } + int bfs_shift = i2s->variant_regs->bfs_off; /* Non-TDM I2S controllers do not support BCLK > 48 * FS */ if (!tdm && bfs > 48) { @@ -292,6 +303,8 @@ static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) return; } + mod &= ~(i2s->variant_regs->bfs_mask << bfs_shift); + switch (bfs) { case 48: mod |= (MOD_BCLK_48FS << bfs_shift); @@ -346,8 +359,9 @@ static inline int get_blc(struct i2s_dai *i2s) static void i2s_txctrl(struct i2s_dai *i2s, int on) { void __iomem *addr = i2s->addr; + int txr_off = i2s->variant_regs->txr_off; u32 con = readl(addr + I2SCON); - u32 mod = readl(addr + I2SMOD) & ~MOD_MASK; + u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off); if (on) { con |= CON_ACTIVE; @@ -362,9 +376,9 @@ static void i2s_txctrl(struct i2s_dai *i2s, int on) } if (any_rx_active(i2s)) - mod |= MOD_TXRX; + mod |= 2 << txr_off; else - mod |= MOD_TXONLY; + mod |= 0 << txr_off; } else { if (is_secondary(i2s)) { con |= CON_TXSDMA_PAUSE; @@ -382,7 +396,7 @@ static void i2s_txctrl(struct i2s_dai *i2s, int on) con |= CON_TXCH_PAUSE; if (any_rx_active(i2s)) - mod |= MOD_RXONLY; + mod |= 1 << txr_off; else con &= ~CON_ACTIVE; } @@ -395,23 +409,24 @@ static void i2s_txctrl(struct i2s_dai *i2s, int on) static void i2s_rxctrl(struct i2s_dai *i2s, int on) { void __iomem *addr = i2s->addr; + int txr_off = i2s->variant_regs->txr_off; u32 con = readl(addr + I2SCON); - u32 mod = readl(addr + I2SMOD) & ~MOD_MASK; + u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off); if (on) { con |= CON_RXDMA_ACTIVE | CON_ACTIVE; con &= ~(CON_RXDMA_PAUSE | CON_RXCH_PAUSE); if (any_tx_active(i2s)) - mod |= MOD_TXRX; + mod |= 2 << txr_off; else - mod |= MOD_RXONLY; + mod |= 1 << txr_off; } else { con |= CON_RXDMA_PAUSE | CON_RXCH_PAUSE; con &= ~CON_RXDMA_ACTIVE; if (any_tx_active(i2s)) - mod |= MOD_TXONLY; + mod |= 0 << txr_off; else con &= ~CON_ACTIVE; } @@ -451,6 +466,9 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, struct i2s_dai *i2s = to_info(dai); struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; u32 mod = readl(i2s->addr + I2SMOD); + const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; + unsigned int cdcon_mask = 1 << i2s_regs->cdclkcon_off; + unsigned int rsrc_mask = 1 << i2s_regs->rclksrc_off; switch (clk_id) { case SAMSUNG_I2S_OPCLK: @@ -465,18 +483,18 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, if ((rfs && other && other->rfs && (other->rfs != rfs)) || (any_active(i2s) && (((dir == SND_SOC_CLOCK_IN) - && !(mod & MOD_CDCLKCON)) || + && !(mod & cdcon_mask)) || ((dir == SND_SOC_CLOCK_OUT) - && (mod & MOD_CDCLKCON))))) { + && (mod & cdcon_mask))))) { dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); return -EAGAIN; } if (dir == SND_SOC_CLOCK_IN) - mod |= MOD_CDCLKCON; + mod |= 1 << i2s_regs->cdclkcon_off; else - mod &= ~MOD_CDCLKCON; + mod &= ~(1 << i2s_regs->cdclkcon_off); i2s->rfs = rfs; break; @@ -491,8 +509,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, if (!any_active(i2s)) { if (i2s->op_clk && !IS_ERR(i2s->op_clk)) { - if ((clk_id && !(mod & MOD_IMS_SYSMUX)) || - (!clk_id && (mod & MOD_IMS_SYSMUX))) { + if ((clk_id && !(mod & rsrc_mask)) || + (!clk_id && (mod & rsrc_mask))) { clk_disable_unprepare(i2s->op_clk); clk_put(i2s->op_clk); } else { @@ -520,8 +538,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, other->op_clk = i2s->op_clk; other->rclk_srcrate = i2s->rclk_srcrate; } - } else if ((!clk_id && (mod & MOD_IMS_SYSMUX)) - || (clk_id && !(mod & MOD_IMS_SYSMUX))) { + } else if ((!clk_id && (mod & rsrc_mask)) + || (clk_id && !(mod & rsrc_mask))) { dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); return -EAGAIN; @@ -533,11 +551,11 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, } if (clk_id == 0) - mod &= ~MOD_IMS_SYSMUX; + mod &= ~(1 << i2s_regs->rclksrc_off); else - mod |= MOD_IMS_SYSMUX; - break; + mod |= 1 << i2s_regs->rclksrc_off; + break; default: dev_err(&i2s->pdev->dev, "We don't serve that!\n"); return -EINVAL; @@ -553,16 +571,12 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, { struct i2s_dai *i2s = to_info(dai); u32 mod = readl(i2s->addr + I2SMOD); - int lrp_shift, sdf_shift, sdf_mask, lrp_rlow; + int lrp_shift, sdf_shift, sdf_mask, lrp_rlow, mod_slave; u32 tmp = 0; - if (i2s->quirks & QUIRK_SUPPORTS_TDM) { - lrp_shift = EXYNOS5420_MOD_LRP_SHIFT; - sdf_shift = EXYNOS5420_MOD_SDF_SHIFT; - } else { - lrp_shift = MOD_LRP_SHIFT; - sdf_shift = MOD_SDF_SHIFT; - } + lrp_shift = i2s->variant_regs->lrp_off; + sdf_shift = i2s->variant_regs->sdf_off; + mod_slave = 1 << i2s->variant_regs->mss_off; sdf_mask = MOD_SDF_MASK << sdf_shift; lrp_rlow = MOD_LR_RLOW << lrp_shift; @@ -605,7 +619,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: - tmp |= MOD_SLAVE; + tmp |= mod_slave; break; case SND_SOC_DAIFMT_CBS_CFS: /* Set default source clock in Master mode */ @@ -623,13 +637,13 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, * channel. */ if (any_active(i2s) && - ((mod & (sdf_mask | lrp_rlow | MOD_SLAVE)) != tmp)) { + ((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) { dev_err(&i2s->pdev->dev, "%s:%d Other DAI busy\n", __func__, __LINE__); return -EAGAIN; } - mod &= ~(sdf_mask | lrp_rlow | MOD_SLAVE); + mod &= ~(sdf_mask | lrp_rlow | mod_slave); mod |= tmp; writel(mod, i2s->addr + I2SMOD); @@ -751,6 +765,7 @@ static void i2s_shutdown(struct snd_pcm_substream *substream, struct i2s_dai *i2s = to_info(dai); struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; unsigned long flags; + const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; spin_lock_irqsave(&lock, flags); @@ -761,7 +776,7 @@ static void i2s_shutdown(struct snd_pcm_substream *substream, other->mode |= DAI_MANAGER; } else { u32 mod = readl(i2s->addr + I2SMOD); - i2s->cdclk_out = !(mod & MOD_CDCLKCON); + i2s->cdclk_out = !(mod & (1 << i2s_regs->cdclkcon_off)); if (other) other->cdclk_out = i2s->cdclk_out; } @@ -914,13 +929,14 @@ i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) struct i2s_dai *i2s = to_info(dai); u32 reg = readl(i2s->addr + I2SFIC); snd_pcm_sframes_t delay; + const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) delay = FIC_RXCOUNT(reg); else if (is_secondary(i2s)) delay = FICS_TXCOUNT(readl(i2s->addr + I2SFICS)); else - delay = FIC_TXCOUNT(reg); + delay = (reg >> i2s_regs->ftx0cnt_off) & 0x7f; return delay; } @@ -956,6 +972,7 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) { struct i2s_dai *i2s = to_info(dai); struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + int ret; if (other && other->clk) { /* If this is probe on secondary */ samsung_asoc_init_dma_data(dai, &other->sec_dai->dma_playback, @@ -973,9 +990,14 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) if (IS_ERR(i2s->clk)) { dev_err(&i2s->pdev->dev, "failed to get i2s_clock\n"); iounmap(i2s->addr); - return -ENOENT; + return PTR_ERR(i2s->clk); + } + + ret = clk_prepare_enable(i2s->clk); + if (ret != 0) { + dev_err(&i2s->pdev->dev, "failed to enable clock: %d\n", ret); + return ret; } - clk_prepare_enable(i2s->clk); samsung_asoc_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture); @@ -987,7 +1009,7 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) if (i2s->quirks & QUIRK_NEED_RSTCLR) writel(CON_RSTCLR, i2s->addr + I2SCON); - if (i2s->quirks & QUIRK_SEC_DAI) + if (i2s->quirks & QUIRK_SUPPORTS_IDMA) idma_reg_addr_init(i2s->addr, i2s->sec_dai->idma_playback.dma_addr); @@ -1199,10 +1221,9 @@ static int samsung_i2s_probe(struct platform_device *pdev) quirks = i2s_dai_data->quirks; if (of_property_read_u32(np, "samsung,idma-addr", &idma_addr)) { - if (quirks & QUIRK_SEC_DAI) { - dev_err(&pdev->dev, "idma address is not"\ + if (quirks & QUIRK_SUPPORTS_IDMA) { + dev_info(&pdev->dev, "idma address is not"\ "specified"); - return -EINVAL; } } } @@ -1228,6 +1249,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) pri_dai->dma_capture.dma_size = 4; pri_dai->base = regs_base; pri_dai->quirks = quirks; + pri_dai->variant_regs = i2s_dai_data->i2s_variant_regs; if (quirks & QUIRK_PRI_6CHAN) pri_dai->i2s_dai_drv.playback.channels_max = 6; @@ -1302,20 +1324,93 @@ static int samsung_i2s_remove(struct platform_device *pdev) return 0; } +static const struct samsung_i2s_variant_regs i2sv3_regs = { + .bfs_off = 1, + .rfs_off = 3, + .sdf_off = 5, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 7, + .bfs_mask = 0x3, + .rfs_mask = 0x3, + .ftx0cnt_off = 8, +}; + +static const struct samsung_i2s_variant_regs i2sv6_regs = { + .bfs_off = 0, + .rfs_off = 4, + .sdf_off = 6, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 15, + .bfs_mask = 0xf, + .rfs_mask = 0x3, + .ftx0cnt_off = 8, +}; + +static const struct samsung_i2s_variant_regs i2sv7_regs = { + .bfs_off = 0, + .rfs_off = 4, + .sdf_off = 7, + .txr_off = 9, + .rclksrc_off = 11, + .mss_off = 12, + .cdclkcon_off = 22, + .lrp_off = 15, + .bfs_mask = 0xf, + .rfs_mask = 0x7, + .ftx0cnt_off = 0, +}; + +static const struct samsung_i2s_variant_regs i2sv5_i2s1_regs = { + .bfs_off = 0, + .rfs_off = 3, + .sdf_off = 6, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 15, + .bfs_mask = 0x7, + .rfs_mask = 0x7, + .ftx0cnt_off = 8, +}; + static const struct samsung_i2s_dai_data i2sv3_dai_type = { .dai_type = TYPE_PRI, .quirks = QUIRK_NO_MUXPSR, + .i2s_variant_regs = &i2sv3_regs, }; static const struct samsung_i2s_dai_data i2sv5_dai_type = { .dai_type = TYPE_PRI, - .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR, + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_IDMA, + .i2s_variant_regs = &i2sv3_regs, }; static const struct samsung_i2s_dai_data i2sv6_dai_type = { .dai_type = TYPE_PRI, .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_TDM | QUIRK_SUPPORTS_IDMA, + .i2s_variant_regs = &i2sv6_regs, +}; + +static const struct samsung_i2s_dai_data i2sv7_dai_type = { + .dai_type = TYPE_PRI, + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .i2s_variant_regs = &i2sv7_regs, +}; + +static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = { + .dai_type = TYPE_PRI, + .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR, + .i2s_variant_regs = &i2sv5_i2s1_regs, }; static const struct samsung_i2s_dai_data samsung_dai_type_pri = { @@ -1329,10 +1424,13 @@ static const struct samsung_i2s_dai_data samsung_dai_type_sec = { static struct platform_device_id samsung_i2s_driver_ids[] = { { .name = "samsung-i2s", - .driver_data = (kernel_ulong_t)&samsung_dai_type_pri, + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, }, { .name = "samsung-i2s-sec", .driver_data = (kernel_ulong_t)&samsung_dai_type_sec, + }, { + .name = "samsung-i2sv4", + .driver_data = (kernel_ulong_t)&i2sv5_dai_type, }, {}, }; @@ -1349,6 +1447,12 @@ static const struct of_device_id exynos_i2s_match[] = { }, { .compatible = "samsung,exynos5420-i2s", .data = &i2sv6_dai_type, + }, { + .compatible = "samsung,exynos7-i2s", + .data = &i2sv7_dai_type, + }, { + .compatible = "samsung,exynos7-i2s1", + .data = &i2sv5_dai_type_i2s1, }, {}, }; diff --git a/sound/soc/samsung/odroidx2_max98090.c b/sound/soc/samsung/odroidx2_max98090.c index 3c8f60423e8..d7640e72cb1 100644 --- a/sound/soc/samsung/odroidx2_max98090.c +++ b/sound/soc/samsung/odroidx2_max98090.c @@ -153,8 +153,8 @@ static int odroidx2_audio_remove(struct platform_device *pdev) snd_soc_unregister_card(card); - of_node_put((struct device_node *)odroidx2_dai[0].cpu_of_node); - of_node_put((struct device_node *)odroidx2_dai[0].codec_of_node); + of_node_put(odroidx2_dai[0].cpu_of_node); + of_node_put(odroidx2_dai[0].codec_of_node); return 0; } diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index 0acf5d0eed5..72118a77dd5 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -110,6 +110,7 @@ static const struct of_device_id snow_of_match[] = { { .compatible = "google,snow-audio-max98095", }, {}, }; +MODULE_DEVICE_TABLE(of, snow_of_match); static struct platform_driver snow_driver = { .driver = { diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 66fddec9543..8869971d788 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -842,12 +842,9 @@ static int fsi_clk_disable(struct device *dev, return -EINVAL; if (1 == clock->count--) { - if (clock->xck) - clk_disable(clock->xck); - if (clock->ick) - clk_disable(clock->ick); - if (clock->div) - clk_disable(clock->div); + clk_disable(clock->xck); + clk_disable(clock->ick); + clk_disable(clock->div); } return 0; @@ -1711,8 +1708,7 @@ static const struct snd_soc_dai_ops fsi_dai_ops = { static struct snd_pcm_hardware fsi_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE, + SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 32, .period_bytes_max = 8192, diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c index 0af2e4dfd13..d5f567e085f 100644 --- a/sound/soc/sh/hac.c +++ b/sound/soc/sh/hac.c @@ -272,7 +272,7 @@ static const struct snd_soc_dai_ops hac_dai_ops = { static struct snd_soc_dai_driver sh4_hac_dai[] = { { .name = "hac-dai.0", - .ac97_control = 1, + .bus_control = true, .playback = { .rates = AC97_RATES, .formats = AC97_FMTS, diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index fc41a0e8b09..14d1a719346 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -430,7 +430,7 @@ int rsnd_adg_probe(struct platform_device *pdev, adg->clk[CLKI] = devm_clk_get(dev, "clk_i"); for_each_rsnd_clk(clk, adg, i) - dev_dbg(dev, "clk %d : %p\n", i, clk); + dev_dbg(dev, "clk %d : %p : %ld\n", i, clk, clk_get_rate(clk)); rsnd_adg_ssi_clk_init(priv, adg); diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 1922ec57d10..75308bbc2ce 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -349,7 +349,7 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, dma_name); if (!dma->chan) { dev_err(dev, "can't get dma channel\n"); - return -EIO; + goto rsnd_dma_channel_err; } ret = dmaengine_slave_config(dma->chan, &cfg); @@ -363,8 +363,15 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, rsnd_dma_init_err: rsnd_dma_quit(priv, dma); +rsnd_dma_channel_err: - return ret; + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + return -EAGAIN; } void rsnd_dma_quit(struct rsnd_priv *priv, @@ -409,9 +416,16 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod) ({ \ struct rsnd_priv *priv = rsnd_mod_to_priv(mod); \ struct device *dev = rsnd_priv_to_dev(priv); \ - dev_dbg(dev, "%s [%d] %s\n", \ - rsnd_mod_name(mod), rsnd_mod_id(mod), #func); \ - (mod)->ops->func(mod, rdai); \ + u32 mask = 1 << __rsnd_mod_shift_##func; \ + u32 call = __rsnd_mod_call_##func << __rsnd_mod_shift_##func; \ + int ret = 0; \ + if ((mod->status & mask) == call) { \ + dev_dbg(dev, "%s[%d] %s\n", \ + rsnd_mod_name(mod), rsnd_mod_id(mod), #func); \ + ret = (mod)->ops->func(mod, rdai); \ + mod->status = (mod->status & ~mask) | (~call & mask); \ + } \ + ret; \ }) #define rsnd_mod_call(mod, func, rdai...) \ @@ -456,6 +470,13 @@ static int rsnd_dai_connect(struct rsnd_mod *mod, return 0; } +static void rsnd_dai_disconnect(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + mod->io = NULL; + io->mod[mod->type] = NULL; +} + int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai) { int id = rdai - priv->rdai; @@ -686,6 +707,20 @@ static const struct snd_soc_dai_ops rsnd_soc_dai_ops = { ret; \ }) +#define rsnd_path_break(priv, io, type) \ +{ \ + struct rsnd_mod *mod; \ + int id = -1; \ + \ + if (rsnd_is_enable_path(io, type)) { \ + id = rsnd_info_id(priv, io, type); \ + if (id >= 0) { \ + mod = rsnd_##type##_mod_get(priv, id); \ + rsnd_dai_disconnect(mod, io); \ + } \ + } \ +} + static int rsnd_path_init(struct rsnd_priv *priv, struct rsnd_dai *rdai, struct rsnd_dai_stream *io) @@ -886,8 +921,7 @@ static int rsnd_dai_probe(struct platform_device *pdev, static struct snd_pcm_hardware rsnd_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE, + SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 32, .period_bytes_max = 8192, @@ -935,6 +969,150 @@ static struct snd_pcm_ops rsnd_pcm_ops = { }; /* + * snd_kcontrol + */ +#define kcontrol_to_cfg(kctrl) ((struct rsnd_kctrl_cfg *)kctrl->private_value) +static int rsnd_kctrl_info(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_info *uinfo) +{ + struct rsnd_kctrl_cfg *cfg = kcontrol_to_cfg(kctrl); + + if (cfg->texts) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = cfg->size; + uinfo->value.enumerated.items = cfg->max; + if (uinfo->value.enumerated.item >= cfg->max) + uinfo->value.enumerated.item = cfg->max - 1; + strlcpy(uinfo->value.enumerated.name, + cfg->texts[uinfo->value.enumerated.item], + sizeof(uinfo->value.enumerated.name)); + } else { + uinfo->count = cfg->size; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = cfg->max; + uinfo->type = (cfg->max == 1) ? + SNDRV_CTL_ELEM_TYPE_BOOLEAN : + SNDRV_CTL_ELEM_TYPE_INTEGER; + } + + return 0; +} + +static int rsnd_kctrl_get(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *uc) +{ + struct rsnd_kctrl_cfg *cfg = kcontrol_to_cfg(kctrl); + int i; + + for (i = 0; i < cfg->size; i++) + if (cfg->texts) + uc->value.enumerated.item[i] = cfg->val[i]; + else + uc->value.integer.value[i] = cfg->val[i]; + + return 0; +} + +static int rsnd_kctrl_put(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *uc) +{ + struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); + struct rsnd_kctrl_cfg *cfg = kcontrol_to_cfg(kctrl); + int i, change = 0; + + for (i = 0; i < cfg->size; i++) { + if (cfg->texts) { + change |= (uc->value.enumerated.item[i] != cfg->val[i]); + cfg->val[i] = uc->value.enumerated.item[i]; + } else { + change |= (uc->value.integer.value[i] != cfg->val[i]); + cfg->val[i] = uc->value.integer.value[i]; + } + } + + if (change) + cfg->update(mod); + + return change; +} + +static int __rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + struct rsnd_kctrl_cfg *cfg, + void (*update)(struct rsnd_mod *mod)) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_kcontrol *kctrl; + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = name, + .info = rsnd_kctrl_info, + .get = rsnd_kctrl_get, + .put = rsnd_kctrl_put, + .private_value = (unsigned long)cfg, + }; + int ret; + + kctrl = snd_ctl_new1(&knew, mod); + if (!kctrl) + return -ENOMEM; + + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + return ret; + + cfg->update = update; + + return 0; +} + +int rsnd_kctrl_new_m(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_mod *mod), + struct rsnd_kctrl_cfg_m *_cfg, + u32 max) +{ + _cfg->cfg.max = max; + _cfg->cfg.size = RSND_DVC_CHANNELS; + _cfg->cfg.val = _cfg->val; + return __rsnd_kctrl_new(mod, rdai, rtd, name, &_cfg->cfg, update); +} + +int rsnd_kctrl_new_s(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_mod *mod), + struct rsnd_kctrl_cfg_s *_cfg, + u32 max) +{ + _cfg->cfg.max = max; + _cfg->cfg.size = 1; + _cfg->cfg.val = &_cfg->val; + return __rsnd_kctrl_new(mod, rdai, rtd, name, &_cfg->cfg, update); +} + +int rsnd_kctrl_new_e(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + struct rsnd_kctrl_cfg_s *_cfg, + void (*update)(struct rsnd_mod *mod), + const char * const *texts, + u32 max) +{ + _cfg->cfg.max = max; + _cfg->cfg.size = 1; + _cfg->cfg.val = &_cfg->val; + _cfg->cfg.texts = texts; + return __rsnd_kctrl_new(mod, rdai, rtd, name, &_cfg->cfg, update); +} + +/* * snd_soc_platform */ @@ -977,6 +1155,49 @@ static const struct snd_soc_component_driver rsnd_soc_component = { .name = "rsnd", }; +static int rsnd_rdai_continuance_probe(struct rsnd_priv *priv, + struct rsnd_dai *rdai, + int is_play) +{ + struct rsnd_dai_stream *io = is_play ? &rdai->playback : &rdai->capture; + int ret; + + ret = rsnd_dai_call(probe, io, rdai); + if (ret == -EAGAIN) { + /* + * Fallback to PIO mode + */ + + /* + * call "remove" for SSI/SRC/DVC + * SSI will be switch to PIO mode if it was DMA mode + * see + * rsnd_dma_init() + * rsnd_ssi_fallback() + */ + rsnd_dai_call(remove, io, rdai); + + /* + * remove SRC/DVC from DAI, + */ + rsnd_path_break(priv, io, src); + rsnd_path_break(priv, io, dvc); + + /* + * fallback + */ + rsnd_dai_call(fallback, io, rdai); + + /* + * retry to "probe". + * DAI has SSI which is PIO mode only now. + */ + ret = rsnd_dai_call(probe, io, rdai); + } + + return ret; +} + /* * rsnd probe */ @@ -1038,11 +1259,11 @@ static int rsnd_probe(struct platform_device *pdev) } for_each_rsnd_dai(rdai, priv, i) { - ret = rsnd_dai_call(probe, &rdai->playback, rdai); + ret = rsnd_rdai_continuance_probe(priv, rdai, 1); if (ret) goto exit_snd_probe; - ret = rsnd_dai_call(probe, &rdai->capture, rdai); + ret = rsnd_rdai_continuance_probe(priv, rdai, 0); if (ret) goto exit_snd_probe; } diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index 3f443930c2b..5380a4827ba 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -11,8 +11,6 @@ #include "rsnd.h" #define RSND_DVC_NAME_SIZE 16 -#define RSND_DVC_VOLUME_MAX 100 -#define RSND_DVC_VOLUME_NUM 2 #define DVC_NAME "dvc" @@ -20,8 +18,11 @@ struct rsnd_dvc { struct rsnd_dvc_platform_info *info; /* rcar_snd.h */ struct rsnd_mod mod; struct clk *clk; - u8 volume[RSND_DVC_VOLUME_NUM]; - u8 mute[RSND_DVC_VOLUME_NUM]; + struct rsnd_kctrl_cfg_m volume; + struct rsnd_kctrl_cfg_m mute; + struct rsnd_kctrl_cfg_s ren; /* Ramp Enable */ + struct rsnd_kctrl_cfg_s rup; /* Ramp Rate Up */ + struct rsnd_kctrl_cfg_s rdown; /* Ramp Rate Down */ }; #define rsnd_mod_to_dvc(_mod) \ @@ -33,23 +34,87 @@ struct rsnd_dvc { ((pos) = (struct rsnd_dvc *)(priv)->dvc + i); \ i++) +static const char const *dvc_ramp_rate[] = { + "128 dB/1 step", /* 00000 */ + "64 dB/1 step", /* 00001 */ + "32 dB/1 step", /* 00010 */ + "16 dB/1 step", /* 00011 */ + "8 dB/1 step", /* 00100 */ + "4 dB/1 step", /* 00101 */ + "2 dB/1 step", /* 00110 */ + "1 dB/1 step", /* 00111 */ + "0.5 dB/1 step", /* 01000 */ + "0.25 dB/1 step", /* 01001 */ + "0.125 dB/1 step", /* 01010 */ + "0.125 dB/2 steps", /* 01011 */ + "0.125 dB/4 steps", /* 01100 */ + "0.125 dB/8 steps", /* 01101 */ + "0.125 dB/16 steps", /* 01110 */ + "0.125 dB/32 steps", /* 01111 */ + "0.125 dB/64 steps", /* 10000 */ + "0.125 dB/128 steps", /* 10001 */ + "0.125 dB/256 steps", /* 10010 */ + "0.125 dB/512 steps", /* 10011 */ + "0.125 dB/1024 steps", /* 10100 */ + "0.125 dB/2048 steps", /* 10101 */ + "0.125 dB/4096 steps", /* 10110 */ + "0.125 dB/8192 steps", /* 10111 */ +}; + static void rsnd_dvc_volume_update(struct rsnd_mod *mod) { struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); - u32 max = (0x00800000 - 1); - u32 vol[RSND_DVC_VOLUME_NUM]; + u32 val[RSND_DVC_CHANNELS]; + u32 dvucr = 0; u32 mute = 0; int i; - for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) { - vol[i] = max / RSND_DVC_VOLUME_MAX * dvc->volume[i]; - mute |= (!!dvc->mute[i]) << i; + for (i = 0; i < dvc->mute.cfg.size; i++) + mute |= (!!dvc->mute.cfg.val[i]) << i; + + /* Disable DVC Register access */ + rsnd_mod_write(mod, DVC_DVUER, 0); + + /* Enable Ramp */ + if (dvc->ren.val) { + dvucr |= 0x10; + + /* Digital Volume Max */ + for (i = 0; i < RSND_DVC_CHANNELS; i++) + val[i] = dvc->volume.cfg.max; + + rsnd_mod_write(mod, DVC_VRCTR, 0xff); + rsnd_mod_write(mod, DVC_VRPDR, dvc->rup.val << 8 | + dvc->rdown.val); + /* + * FIXME !! + * use scale-downed Digital Volume + * as Volume Ramp + * 7F FFFF -> 3FF + */ + rsnd_mod_write(mod, DVC_VRDBR, + 0x3ff - (dvc->volume.val[0] >> 13)); + + } else { + for (i = 0; i < RSND_DVC_CHANNELS; i++) + val[i] = dvc->volume.val[i]; + } + + /* Enable Digital Volume */ + dvucr |= 0x100; + rsnd_mod_write(mod, DVC_VOL0R, val[0]); + rsnd_mod_write(mod, DVC_VOL1R, val[1]); + + /* Enable Mute */ + if (mute) { + dvucr |= 0x1; + rsnd_mod_write(mod, DVC_ZCMCR, mute); } - rsnd_mod_write(mod, DVC_VOL0R, vol[0]); - rsnd_mod_write(mod, DVC_VOL1R, vol[1]); + rsnd_mod_write(mod, DVC_DVUCR, dvucr); - rsnd_mod_write(mod, DVC_ZCMCR, mute); + /* Enable DVC Register access */ + rsnd_mod_write(mod, DVC_DVUER, 1); } static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod, @@ -58,7 +123,8 @@ static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod, struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct device *dev = rsnd_priv_to_dev(priv); - dev_dbg(dev, "%s (Gen2) is probed\n", rsnd_mod_name(mod)); + dev_dbg(dev, "%s[%d] (Gen2) is probed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); return 0; } @@ -102,16 +168,11 @@ static int rsnd_dvc_init(struct rsnd_mod *dvc_mod, rsnd_mod_write(dvc_mod, DVC_ADINR, rsnd_get_adinr(dvc_mod)); - /* enable Volume / Mute */ - rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x101); - /* ch0/ch1 Volume */ rsnd_dvc_volume_update(dvc_mod); rsnd_mod_write(dvc_mod, DVC_DVUIR, 0); - rsnd_mod_write(dvc_mod, DVC_DVUER, 1); - rsnd_adg_set_cmd_timsel_gen2(rdai, dvc_mod, io); return 0; @@ -143,86 +204,6 @@ static int rsnd_dvc_stop(struct rsnd_mod *mod, return 0; } -static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_info *uinfo) -{ - struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); - struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); - u8 *val = (u8 *)kctrl->private_value; - - uinfo->count = RSND_DVC_VOLUME_NUM; - uinfo->value.integer.min = 0; - - if (val == dvc->volume) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.max = RSND_DVC_VOLUME_MAX; - } else { - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->value.integer.max = 1; - } - - return 0; -} - -static int rsnd_dvc_volume_get(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) -{ - u8 *val = (u8 *)kctrl->private_value; - int i; - - for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) - ucontrol->value.integer.value[i] = val[i]; - - return 0; -} - -static int rsnd_dvc_volume_put(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) -{ - struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); - u8 *val = (u8 *)kctrl->private_value; - int i, change = 0; - - for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) { - change |= (ucontrol->value.integer.value[i] != val[i]); - val[i] = ucontrol->value.integer.value[i]; - } - - if (change) - rsnd_dvc_volume_update(mod); - - return change; -} - -static int __rsnd_dvc_pcm_new(struct rsnd_mod *mod, - struct rsnd_dai *rdai, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - u8 *private) -{ - struct snd_card *card = rtd->card->snd_card; - struct snd_kcontrol *kctrl; - struct snd_kcontrol_new knew = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = name, - .info = rsnd_dvc_volume_info, - .get = rsnd_dvc_volume_get, - .put = rsnd_dvc_volume_put, - .private_value = (unsigned long)private, - }; - int ret; - - kctrl = snd_ctl_new1(&knew, mod); - if (!kctrl) - return -ENOMEM; - - ret = snd_ctl_add(card, kctrl); - if (ret < 0) - return ret; - - return 0; -} - static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, struct rsnd_dai *rdai, struct snd_soc_pcm_runtime *rtd) @@ -232,18 +213,48 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, int ret; /* Volume */ - ret = __rsnd_dvc_pcm_new(mod, rdai, rtd, + ret = rsnd_kctrl_new_m(mod, rdai, rtd, rsnd_dai_is_play(rdai, io) ? "DVC Out Playback Volume" : "DVC In Capture Volume", - dvc->volume); + rsnd_dvc_volume_update, + &dvc->volume, 0x00800000 - 1); if (ret < 0) return ret; /* Mute */ - ret = __rsnd_dvc_pcm_new(mod, rdai, rtd, + ret = rsnd_kctrl_new_m(mod, rdai, rtd, rsnd_dai_is_play(rdai, io) ? "DVC Out Mute Switch" : "DVC In Mute Switch", - dvc->mute); + rsnd_dvc_volume_update, + &dvc->mute, 1); + if (ret < 0) + return ret; + + /* Ramp */ + ret = rsnd_kctrl_new_s(mod, rdai, rtd, + rsnd_dai_is_play(rdai, io) ? + "DVC Out Ramp Switch" : "DVC In Ramp Switch", + rsnd_dvc_volume_update, + &dvc->ren, 1); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, rdai, rtd, + rsnd_dai_is_play(rdai, io) ? + "DVC Out Ramp Up Rate" : "DVC In Ramp Up Rate", + &dvc->rup, + rsnd_dvc_volume_update, + dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, rdai, rtd, + rsnd_dai_is_play(rdai, io) ? + "DVC Out Ramp Down Rate" : "DVC In Ramp Down Rate", + &dvc->rdown, + rsnd_dvc_volume_update, + dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + if (ret < 0) return ret; diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index f95e7ab135e..87a6f2d6277 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -8,6 +8,17 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ + +/* + * #define DEBUG + * + * you can also add below in + * ${LINUX}/drivers/base/regmap/regmap.c + * for regmap debug + * + * #define LOG_DEVICE "xxxx.rcar_sound" + */ + #include "rsnd.h" struct rsnd_gen { @@ -67,9 +78,10 @@ u32 rsnd_read(struct rsnd_priv *priv, if (!rsnd_is_accessible_reg(priv, gen, reg)) return 0; - regmap_fields_read(gen->regs[reg], rsnd_mod_id(mod), &val); + dev_dbg(dev, "r %s[%d] - %4d : %08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), reg, val); - dev_dbg(dev, "r %s - 0x%04d : %08x\n", rsnd_mod_name(mod), reg, val); + regmap_fields_read(gen->regs[reg], rsnd_mod_id(mod), &val); return val; } @@ -84,9 +96,10 @@ void rsnd_write(struct rsnd_priv *priv, if (!rsnd_is_accessible_reg(priv, gen, reg)) return; - regmap_fields_write(gen->regs[reg], rsnd_mod_id(mod), data); + dev_dbg(dev, "w %s[%d] - %4d : %08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), reg, data); - dev_dbg(dev, "w %s - 0x%04d : %08x\n", rsnd_mod_name(mod), reg, data); + regmap_fields_write(gen->regs[reg], rsnd_mod_id(mod), data); } void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, @@ -98,11 +111,11 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, if (!rsnd_is_accessible_reg(priv, gen, reg)) return; + dev_dbg(dev, "b %s[%d] - %4d : %08x/%08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), reg, data, mask); + regmap_fields_update_bits(gen->regs[reg], rsnd_mod_id(mod), mask, data); - - dev_dbg(dev, "b %s - 0x%04d : %08x/%08x\n", - rsnd_mod_name(mod), reg, data, mask); } #define rsnd_gen_regmap_init(priv, id_size, reg_id, conf) \ @@ -311,6 +324,9 @@ static int rsnd_gen2_probe(struct platform_device *pdev, RSND_GEN_M_REG(DVC_ADINR, 0xe08, 0x100), RSND_GEN_M_REG(DVC_DVUCR, 0xe10, 0x100), RSND_GEN_M_REG(DVC_ZCMCR, 0xe14, 0x100), + RSND_GEN_M_REG(DVC_VRCTR, 0xe18, 0x100), + RSND_GEN_M_REG(DVC_VRPDR, 0xe1c, 0x100), + RSND_GEN_M_REG(DVC_VRDBR, 0xe20, 0x100), RSND_GEN_M_REG(DVC_VOL0R, 0xe28, 0x100), RSND_GEN_M_REG(DVC_VOL1R, 0xe2c, 0x100), RSND_GEN_M_REG(DVC_DVUER, 0xe48, 0x100), diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index d119adf97c9..5826c8abf79 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -91,6 +91,9 @@ enum rsnd_reg { RSND_REG_SHARE20, RSND_REG_SHARE21, RSND_REG_SHARE22, + RSND_REG_SHARE23, + RSND_REG_SHARE24, + RSND_REG_SHARE25, RSND_REG_MAX, }; @@ -129,6 +132,9 @@ enum rsnd_reg { #define RSND_REG_CMD_CTRL RSND_REG_SHARE20 #define RSND_REG_CMDOUT_TIMSEL RSND_REG_SHARE21 #define RSND_REG_BUSIF_DALIGN RSND_REG_SHARE22 +#define RSND_REG_DVC_VRCTR RSND_REG_SHARE23 +#define RSND_REG_DVC_VRPDR RSND_REG_SHARE24 +#define RSND_REG_DVC_VRDBR RSND_REG_SHARE25 struct rsnd_of_data; struct rsnd_priv; @@ -200,6 +206,8 @@ struct rsnd_mod_ops { int (*pcm_new)(struct rsnd_mod *mod, struct rsnd_dai *rdai, struct snd_soc_pcm_runtime *rtd); + int (*fallback)(struct rsnd_mod *mod, + struct rsnd_dai *rdai); }; struct rsnd_dai_stream; @@ -210,7 +218,35 @@ struct rsnd_mod { struct rsnd_mod_ops *ops; struct rsnd_dma dma; struct rsnd_dai_stream *io; + u32 status; }; +/* + * status + * + * bit + * 0 0: probe 1: remove + * 1 0: init 1: quit + * 2 0: start 1: stop + * 3 0: pcm_new + * 4 0: fallback + */ +#define __rsnd_mod_shift_probe 0 +#define __rsnd_mod_shift_remove 0 +#define __rsnd_mod_shift_init 1 +#define __rsnd_mod_shift_quit 1 +#define __rsnd_mod_shift_start 2 +#define __rsnd_mod_shift_stop 2 +#define __rsnd_mod_shift_pcm_new 3 +#define __rsnd_mod_shift_fallback 4 + +#define __rsnd_mod_call_probe 0 +#define __rsnd_mod_call_remove 1 +#define __rsnd_mod_call_init 0 +#define __rsnd_mod_call_quit 1 +#define __rsnd_mod_call_start 0 +#define __rsnd_mod_call_stop 1 +#define __rsnd_mod_call_pcm_new 0 +#define __rsnd_mod_call_fallback 0 #define rsnd_mod_to_priv(mod) ((mod)->priv) #define rsnd_mod_to_dma(mod) (&(mod)->dma) @@ -267,7 +303,8 @@ struct rsnd_dai *rsnd_dai_get(struct rsnd_priv *priv, int id); int rsnd_dai_is_play(struct rsnd_dai *rdai, struct rsnd_dai_stream *io); int rsnd_dai_id(struct rsnd_priv *priv, struct rsnd_dai *rdai); #define rsnd_dai_get_platform_info(rdai) ((rdai)->info) -#define rsnd_io_to_runtime(io) ((io)->substream->runtime) +#define rsnd_io_to_runtime(io) ((io)->substream ? \ + (io)->substream->runtime : NULL) void rsnd_dai_pointer_update(struct rsnd_dai_stream *io, int cnt); int rsnd_dai_pointer_offset(struct rsnd_dai_stream *io, int additional); @@ -382,6 +419,51 @@ struct rsnd_priv { }) /* + * rsnd_kctrl + */ +struct rsnd_kctrl_cfg { + unsigned int max; + unsigned int size; + u32 *val; + const char * const *texts; + void (*update)(struct rsnd_mod *mod); +}; + +#define RSND_DVC_CHANNELS 2 +struct rsnd_kctrl_cfg_m { + struct rsnd_kctrl_cfg cfg; + u32 val[RSND_DVC_CHANNELS]; +}; + +struct rsnd_kctrl_cfg_s { + struct rsnd_kctrl_cfg cfg; + u32 val; +}; + +int rsnd_kctrl_new_m(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_mod *mod), + struct rsnd_kctrl_cfg_m *_cfg, + u32 max); +int rsnd_kctrl_new_s(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_mod *mod), + struct rsnd_kctrl_cfg_s *_cfg, + u32 max); +int rsnd_kctrl_new_e(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + struct rsnd_kctrl_cfg_s *_cfg, + void (*update)(struct rsnd_mod *mod), + const char * const *texts, + u32 max); + +/* * R-Car SRC */ int rsnd_src_probe(struct platform_device *pdev, @@ -395,10 +477,11 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, struct rsnd_dai *rdai, int use_busif); int rsnd_src_ssiu_stop(struct rsnd_mod *ssi_mod, - struct rsnd_dai *rdai, - int use_busif); -int rsnd_src_enable_ssi_irq(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai); +int rsnd_src_ssi_irq_enable(struct rsnd_mod *ssi_mod, struct rsnd_dai *rdai); +int rsnd_src_ssi_irq_disable(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai); #define rsnd_src_nr(priv) ((priv)->src_nr) @@ -410,6 +493,7 @@ int rsnd_ssi_probe(struct platform_device *pdev, struct rsnd_priv *priv); struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id); int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod); +int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod); /* * R-Car DVC diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 9183e014550..eede3ac6eed 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -175,30 +175,47 @@ int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, } int rsnd_src_ssiu_stop(struct rsnd_mod *ssi_mod, - struct rsnd_dai *rdai, - int use_busif) + struct rsnd_dai *rdai) { /* * DMA settings for SSIU */ - if (use_busif) - rsnd_mod_write(ssi_mod, SSI_CTRL, 0); + rsnd_mod_write(ssi_mod, SSI_CTRL, 0); return 0; } -int rsnd_src_enable_ssi_irq(struct rsnd_mod *ssi_mod, +int rsnd_src_ssi_irq_enable(struct rsnd_mod *ssi_mod, struct rsnd_dai *rdai) { struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod); - /* enable PIO interrupt if Gen2 */ - if (rsnd_is_gen2(priv)) + if (rsnd_is_gen1(priv)) + return 0; + + /* enable SSI interrupt if Gen2 */ + if (rsnd_ssi_is_dma_mode(ssi_mod)) + rsnd_mod_write(ssi_mod, INT_ENABLE, 0x0e000000); + else rsnd_mod_write(ssi_mod, INT_ENABLE, 0x0f000000); return 0; } +int rsnd_src_ssi_irq_disable(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod); + + if (rsnd_is_gen1(priv)) + return 0; + + /* disable SSI interrupt if Gen2 */ + rsnd_mod_write(ssi_mod, INT_ENABLE, 0x00000000); + + return 0; +} + unsigned int rsnd_src_get_ssi_rate(struct rsnd_priv *priv, struct rsnd_dai_stream *io, struct snd_pcm_runtime *runtime) @@ -239,12 +256,6 @@ static int rsnd_src_set_convert_rate(struct rsnd_mod *mod, rsnd_mod_write(mod, SRC_SWRSR, 0); rsnd_mod_write(mod, SRC_SWRSR, 1); - /* - * Initialize the operation of the SRC internal circuits - * see rsnd_src_start() - */ - rsnd_mod_write(mod, SRC_SRCIR, 1); - /* Set channel number and output bit length */ rsnd_mod_write(mod, SRC_ADINR, rsnd_get_adinr(mod)); @@ -269,6 +280,12 @@ static int rsnd_src_init(struct rsnd_mod *mod, clk_prepare_enable(src->clk); + /* + * Initialize the operation of the SRC internal circuits + * see rsnd_src_start() + */ + rsnd_mod_write(mod, SRC_SRCIR, 1); + return 0; } @@ -282,32 +299,20 @@ static int rsnd_src_quit(struct rsnd_mod *mod, return 0; } -static int rsnd_src_start(struct rsnd_mod *mod, - struct rsnd_dai *rdai) +static int rsnd_src_start(struct rsnd_mod *mod) { - struct rsnd_src *src = rsnd_mod_to_src(mod); - /* * Cancel the initialization and operate the SRC function - * see rsnd_src_set_convert_rate() + * see rsnd_src_init() */ rsnd_mod_write(mod, SRC_SRCIR, 0); - if (rsnd_src_convert_rate(src)) - rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1); - return 0; } - -static int rsnd_src_stop(struct rsnd_mod *mod, - struct rsnd_dai *rdai) +static int rsnd_src_stop(struct rsnd_mod *mod) { - struct rsnd_src *src = rsnd_mod_to_src(mod); - - if (rsnd_src_convert_rate(src)) - rsnd_mod_write(mod, SRC_ROUTE_MODE0, 0); - + /* nothing to do */ return 0; } @@ -414,6 +419,7 @@ static int rsnd_src_set_convert_timing_gen1(struct rsnd_mod *mod, static int rsnd_src_set_convert_rate_gen1(struct rsnd_mod *mod, struct rsnd_dai *rdai) { + struct rsnd_src *src = rsnd_mod_to_src(mod); int ret; ret = rsnd_src_set_convert_rate(mod, rdai); @@ -427,6 +433,10 @@ static int rsnd_src_set_convert_rate_gen1(struct rsnd_mod *mod, rsnd_mod_write(mod, SRC_MNFSR, rsnd_mod_read(mod, SRC_IFSVR) / 100 * 98); + /* Gen1/Gen2 are not compatible */ + if (rsnd_src_convert_rate(src)) + rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1); + /* no SRC_BFSSR settings, since SRC_SRCCR::BUFMD is 0 */ return 0; @@ -438,7 +448,8 @@ static int rsnd_src_probe_gen1(struct rsnd_mod *mod, struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct device *dev = rsnd_priv_to_dev(priv); - dev_dbg(dev, "%s (Gen1) is probed\n", rsnd_mod_name(mod)); + dev_dbg(dev, "%s[%d] (Gen1) is probed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); return 0; } @@ -474,7 +485,7 @@ static int rsnd_src_start_gen1(struct rsnd_mod *mod, rsnd_mod_bset(mod, SRC_ROUTE_CTRL, (1 << id), (1 << id)); - return rsnd_src_start(mod, rdai); + return rsnd_src_start(mod); } static int rsnd_src_stop_gen1(struct rsnd_mod *mod, @@ -484,7 +495,7 @@ static int rsnd_src_stop_gen1(struct rsnd_mod *mod, rsnd_mod_bset(mod, SRC_ROUTE_CTRL, (1 << id), 0); - return rsnd_src_stop(mod, rdai); + return rsnd_src_stop(mod); } static struct rsnd_mod_ops rsnd_src_gen1_ops = { @@ -507,16 +518,17 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod, struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); struct rsnd_src *src = rsnd_mod_to_src(mod); + u32 convert_rate = rsnd_src_convert_rate(src); uint ratio; int ret; /* 6 - 1/6 are very enough ratio for SRC_BSDSR */ - if (!rsnd_src_convert_rate(src)) + if (!convert_rate) ratio = 0; - else if (rsnd_src_convert_rate(src) > runtime->rate) - ratio = 100 * rsnd_src_convert_rate(src) / runtime->rate; + else if (convert_rate > runtime->rate) + ratio = 100 * convert_rate / runtime->rate; else - ratio = 100 * runtime->rate / rsnd_src_convert_rate(src); + ratio = 100 * runtime->rate / convert_rate; if (ratio > 600) { dev_err(dev, "FSO/FSI ratio error\n"); @@ -529,6 +541,11 @@ static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod, rsnd_mod_write(mod, SRC_SRCCR, 0x00011110); + if (convert_rate) { + /* Gen1/Gen2 are not compatible */ + rsnd_mod_write(mod, SRC_ROUTE_MODE0, 1); + } + switch (rsnd_mod_id(mod)) { case 5: case 6: @@ -578,9 +595,11 @@ static int rsnd_src_probe_gen2(struct rsnd_mod *mod, rsnd_info_is_playback(priv, src), src->info->dma_id); if (ret < 0) - dev_err(dev, "SRC DMA failed\n"); - - dev_dbg(dev, "%s (Gen2) is probed\n", rsnd_mod_name(mod)); + dev_err(dev, "%s[%d] (Gen2) failed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + else + dev_dbg(dev, "%s[%d] (Gen2) is probed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } @@ -624,7 +643,7 @@ static int rsnd_src_start_gen2(struct rsnd_mod *mod, rsnd_mod_write(mod, SRC_CTRL, val); - return rsnd_src_start(mod, rdai); + return rsnd_src_start(mod); } static int rsnd_src_stop_gen2(struct rsnd_mod *mod, @@ -636,7 +655,7 @@ static int rsnd_src_stop_gen2(struct rsnd_mod *mod, rsnd_dma_stop(rsnd_mod_to_dma(&src->mod)); - return rsnd_src_stop(mod, rdai); + return rsnd_src_stop(mod); } static struct rsnd_mod_ops rsnd_src_gen2_ops = { diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 34e84009162..3844fbef466 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -68,7 +68,6 @@ struct rsnd_ssi { struct rsnd_dai *rdai; u32 cr_own; u32 cr_clk; - u32 cr_etc; int err; unsigned int usrcnt; unsigned int rate; @@ -83,7 +82,7 @@ struct rsnd_ssi { #define rsnd_ssi_nr(priv) ((priv)->ssi_nr) #define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod) #define rsnd_dma_to_ssi(dma) rsnd_mod_to_ssi(rsnd_dma_to_mod(dma)) -#define rsnd_ssi_pio_available(ssi) ((ssi)->info->pio_irq > 0) +#define rsnd_ssi_pio_available(ssi) ((ssi)->info->irq > 0) #define rsnd_ssi_dma_available(ssi) \ rsnd_dma_available(rsnd_mod_to_dma(&(ssi)->mod)) #define rsnd_ssi_clk_from_parent(ssi) ((ssi)->parent) @@ -96,6 +95,9 @@ static int rsnd_ssi_use_busif(struct rsnd_mod *mod) struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); int use_busif = 0; + if (!rsnd_ssi_is_dma_mode(mod)) + return 0; + if (!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_NO_BUSIF)) use_busif = 1; if (rsnd_io_to_mod_src(io)) @@ -159,7 +161,8 @@ static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi, ssi->cr_clk = FORCE | SWL_32 | SCKD | SWSD | CKDV(j); - dev_dbg(dev, "ssi%d outputs %u Hz\n", + dev_dbg(dev, "%s[%d] outputs %u Hz\n", + rsnd_mod_name(&ssi->mod), rsnd_mod_id(&ssi->mod), rate); return 0; @@ -184,6 +187,7 @@ static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi, { struct rsnd_priv *priv = rsnd_mod_to_priv(&ssi->mod); struct device *dev = rsnd_priv_to_dev(priv); + u32 cr_mode; u32 cr; if (0 == ssi->usrcnt) { @@ -197,16 +201,29 @@ static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi, } } + cr_mode = rsnd_ssi_is_dma_mode(&ssi->mod) ? + DMEN : /* DMA : enable DMA */ + DIEN; /* PIO : enable Data interrupt */ + + cr = ssi->cr_own | ssi->cr_clk | - ssi->cr_etc | - EN; + cr_mode | + UIEN | OIEN | EN; rsnd_mod_write(&ssi->mod, SSICR, cr); + /* enable WS continue */ + if (rsnd_dai_is_clk_master(rdai)) + rsnd_mod_write(&ssi->mod, SSIWSR, CONT); + + /* clear error status */ + rsnd_mod_write(&ssi->mod, SSISR, 0); + ssi->usrcnt++; - dev_dbg(dev, "ssi%d hw started\n", rsnd_mod_id(&ssi->mod)); + dev_dbg(dev, "%s[%d] hw started\n", + rsnd_mod_name(&ssi->mod), rsnd_mod_id(&ssi->mod)); } static void rsnd_ssi_hw_stop(struct rsnd_ssi *ssi, @@ -249,7 +266,8 @@ static void rsnd_ssi_hw_stop(struct rsnd_ssi *ssi, clk_disable_unprepare(ssi->clk); } - dev_dbg(dev, "ssi%d hw stopped\n", rsnd_mod_id(&ssi->mod)); + dev_dbg(dev, "%s[%d] hw stopped\n", + rsnd_mod_name(&ssi->mod), rsnd_mod_id(&ssi->mod)); } /* @@ -334,25 +352,54 @@ static void rsnd_ssi_record_error(struct rsnd_ssi *ssi, u32 status) } } -/* - * SSI PIO - */ -static irqreturn_t rsnd_ssi_pio_interrupt(int irq, void *data) +static int rsnd_ssi_start(struct rsnd_mod *mod, + struct rsnd_dai *rdai) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + + rsnd_src_ssiu_start(mod, rdai, rsnd_ssi_use_busif(mod)); + + rsnd_ssi_hw_start(ssi, rdai, io); + + rsnd_src_ssi_irq_enable(mod, rdai); + + return 0; +} + +static int rsnd_ssi_stop(struct rsnd_mod *mod, + struct rsnd_dai *rdai) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + rsnd_src_ssi_irq_disable(mod, rdai); + + rsnd_ssi_record_error(ssi, rsnd_mod_read(mod, SSISR)); + + rsnd_ssi_hw_stop(ssi, rdai); + + rsnd_src_ssiu_stop(mod, rdai); + + return 0; +} + +static irqreturn_t rsnd_ssi_interrupt(int irq, void *data) { struct rsnd_ssi *ssi = data; + struct rsnd_dai *rdai = ssi->rdai; struct rsnd_mod *mod = &ssi->mod; struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); u32 status = rsnd_mod_read(mod, SSISR); - irqreturn_t ret = IRQ_NONE; - if (io && (status & DIRQ)) { - struct rsnd_dai *rdai = ssi->rdai; + if (!io) + return IRQ_NONE; + + /* PIO only */ + if (status & DIRQ) { struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); u32 *buf = (u32 *)(runtime->dma_area + rsnd_dai_pointer_offset(io, 0)); - rsnd_ssi_record_error(ssi, status); - /* * 8/16/32 data can be assesse to TDR/RDR register * directly as 32bit data @@ -364,73 +411,60 @@ static irqreturn_t rsnd_ssi_pio_interrupt(int irq, void *data) *buf = rsnd_mod_read(mod, SSIRDR); rsnd_dai_pointer_update(io, sizeof(*buf)); + } + + /* PIO / DMA */ + if (status & (UIRQ | OIRQ)) { + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + + /* + * restart SSI + */ + rsnd_ssi_stop(mod, rdai); + rsnd_ssi_start(mod, rdai); - ret = IRQ_HANDLED; + dev_dbg(dev, "%s[%d] restart\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); } - return ret; + rsnd_ssi_record_error(ssi, status); + + return IRQ_HANDLED; } +/* + * SSI PIO + */ static int rsnd_ssi_pio_probe(struct rsnd_mod *mod, struct rsnd_dai *rdai) { struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - int irq = ssi->info->pio_irq; int ret; - ret = devm_request_irq(dev, irq, - rsnd_ssi_pio_interrupt, + ret = devm_request_irq(dev, ssi->info->irq, + rsnd_ssi_interrupt, IRQF_SHARED, dev_name(dev), ssi); if (ret) - dev_err(dev, "SSI request interrupt failed\n"); - - dev_dbg(dev, "%s (PIO) is probed\n", rsnd_mod_name(mod)); + dev_err(dev, "%s[%d] (PIO) request interrupt failed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + else + dev_dbg(dev, "%s[%d] (PIO) is probed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } -static int rsnd_ssi_pio_start(struct rsnd_mod *mod, - struct rsnd_dai *rdai) -{ - struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - - /* enable PIO IRQ */ - ssi->cr_etc = UIEN | OIEN | DIEN; - - rsnd_src_ssiu_start(mod, rdai, 0); - - rsnd_src_enable_ssi_irq(mod, rdai); - - rsnd_ssi_hw_start(ssi, rdai, io); - - return 0; -} - -static int rsnd_ssi_pio_stop(struct rsnd_mod *mod, - struct rsnd_dai *rdai) -{ - struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - - ssi->cr_etc = 0; - - rsnd_ssi_hw_stop(ssi, rdai); - - rsnd_src_ssiu_stop(mod, rdai, 0); - - return 0; -} - static struct rsnd_mod_ops rsnd_ssi_pio_ops = { .name = SSI_NAME, .probe = rsnd_ssi_pio_probe, .init = rsnd_ssi_init, .quit = rsnd_ssi_quit, - .start = rsnd_ssi_pio_start, - .stop = rsnd_ssi_pio_stop, + .start = rsnd_ssi_start, + .stop = rsnd_ssi_stop, }; static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, @@ -442,15 +476,28 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, int dma_id = ssi->info->dma_id; int ret; + ret = devm_request_irq(dev, ssi->info->irq, + rsnd_ssi_interrupt, + IRQF_SHARED, + dev_name(dev), ssi); + if (ret) + goto rsnd_ssi_dma_probe_fail; + ret = rsnd_dma_init( priv, rsnd_mod_to_dma(mod), rsnd_info_is_playback(priv, ssi), dma_id); + if (ret) + goto rsnd_ssi_dma_probe_fail; - if (ret < 0) - dev_err(dev, "SSI DMA failed\n"); + dev_dbg(dev, "%s[%d] (DMA) is probed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + + return ret; - dev_dbg(dev, "%s (DMA) is probed\n", rsnd_mod_name(mod)); +rsnd_ssi_dma_probe_fail: + dev_err(dev, "%s[%d] (DMA) is failed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); return ret; } @@ -458,30 +505,48 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, static int rsnd_ssi_dma_remove(struct rsnd_mod *mod, struct rsnd_dai *rdai) { + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct device *dev = rsnd_priv_to_dev(priv); + int irq = ssi->info->irq; + rsnd_dma_quit(rsnd_mod_to_priv(mod), rsnd_mod_to_dma(mod)); + /* PIO will request IRQ again */ + devm_free_irq(dev, irq, ssi); + return 0; } -static int rsnd_ssi_dma_start(struct rsnd_mod *mod, - struct rsnd_dai *rdai) +static int rsnd_ssi_fallback(struct rsnd_mod *mod, + struct rsnd_dai *rdai) { - struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_dma *dma = rsnd_mod_to_dma(&ssi->mod); - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); - /* enable DMA transfer */ - ssi->cr_etc = DMEN; + /* + * fallback to PIO + * + * SSI .probe might be called again. + * see + * rsnd_rdai_continuance_probe() + */ + mod->ops = &rsnd_ssi_pio_ops; - rsnd_src_ssiu_start(mod, rdai, rsnd_ssi_use_busif(mod)); + dev_info(dev, "%s[%d] fallback to PIO mode\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); - rsnd_dma_start(dma); + return 0; +} - rsnd_ssi_hw_start(ssi, ssi->rdai, io); +static int rsnd_ssi_dma_start(struct rsnd_mod *mod, + struct rsnd_dai *rdai) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); - /* enable WS continue */ - if (rsnd_dai_is_clk_master(rdai)) - rsnd_mod_write(&ssi->mod, SSIWSR, CONT); + rsnd_ssi_start(mod, rdai); + + rsnd_dma_start(dma); return 0; } @@ -489,18 +554,11 @@ static int rsnd_ssi_dma_start(struct rsnd_mod *mod, static int rsnd_ssi_dma_stop(struct rsnd_mod *mod, struct rsnd_dai *rdai) { - struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_dma *dma = rsnd_mod_to_dma(&ssi->mod); - - ssi->cr_etc = 0; - - rsnd_ssi_record_error(ssi, rsnd_mod_read(mod, SSISR)); - - rsnd_ssi_hw_stop(ssi, rdai); + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); rsnd_dma_stop(dma); - rsnd_src_ssiu_stop(mod, rdai, 1); + rsnd_ssi_stop(mod, rdai); return 0; } @@ -519,8 +577,15 @@ static struct rsnd_mod_ops rsnd_ssi_dma_ops = { .quit = rsnd_ssi_quit, .start = rsnd_ssi_dma_start, .stop = rsnd_ssi_dma_stop, + .fallback = rsnd_ssi_fallback, }; +int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod) +{ + return mod->ops == &rsnd_ssi_dma_ops; +} + + /* * Non SSI */ @@ -614,7 +679,7 @@ static void rsnd_of_parse_ssi(struct platform_device *pdev, /* * irq */ - ssi_info->pio_irq = irq_of_parse_and_map(np, 0); + ssi_info->irq = irq_of_parse_and_map(np, 0); /* * DMA diff --git a/sound/soc/soc-ac97.c b/sound/soc/soc-ac97.c new file mode 100644 index 00000000000..2e10e9a3837 --- /dev/null +++ b/sound/soc/soc-ac97.c @@ -0,0 +1,256 @@ +/* + * soc-ac97.c -- ALSA SoC Audio Layer AC97 support + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * Copyright (C) 2010 Slimlogic Ltd. + * Copyright (C) 2010 Texas Instruments Inc. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * with code, comments and ideas from :- + * Richard Purdie <richard@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/of_gpio.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/slab.h> +#include <sound/ac97_codec.h> +#include <sound/soc.h> + +struct snd_ac97_reset_cfg { + struct pinctrl *pctl; + struct pinctrl_state *pstate_reset; + struct pinctrl_state *pstate_warm_reset; + struct pinctrl_state *pstate_run; + int gpio_sdata; + int gpio_sync; + int gpio_reset; +}; + +static struct snd_ac97_bus soc_ac97_bus = { + .ops = NULL, /* Gets initialized in snd_soc_set_ac97_ops() */ +}; + +static void soc_ac97_device_release(struct device *dev) +{ + kfree(to_ac97_t(dev)); +} + +/** + * snd_soc_new_ac97_codec - initailise AC97 device + * @codec: audio codec + * + * Initialises AC97 codec resources for use by ad-hoc devices only. + */ +struct snd_ac97 *snd_soc_new_ac97_codec(struct snd_soc_codec *codec) +{ + struct snd_ac97 *ac97; + int ret; + + ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL); + if (ac97 == NULL) + return ERR_PTR(-ENOMEM); + + ac97->bus = &soc_ac97_bus; + ac97->num = 0; + + ac97->dev.bus = &ac97_bus_type; + ac97->dev.parent = codec->component.card->dev; + ac97->dev.release = soc_ac97_device_release; + + dev_set_name(&ac97->dev, "%d-%d:%s", + codec->component.card->snd_card->number, 0, + codec->component.name); + + ret = device_register(&ac97->dev); + if (ret) { + put_device(&ac97->dev); + return ERR_PTR(ret); + } + + return ac97; +} +EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec); + +/** + * snd_soc_free_ac97_codec - free AC97 codec device + * @codec: audio codec + * + * Frees AC97 codec device resources. + */ +void snd_soc_free_ac97_codec(struct snd_ac97 *ac97) +{ + device_del(&ac97->dev); + ac97->bus = NULL; + put_device(&ac97->dev); +} +EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec); + +static struct snd_ac97_reset_cfg snd_ac97_rst_cfg; + +static void snd_soc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_warm_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 1); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static void snd_soc_ac97_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_sdata, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 0); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 1); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static int snd_soc_ac97_parse_pinctl(struct device *dev, + struct snd_ac97_reset_cfg *cfg) +{ + struct pinctrl *p; + struct pinctrl_state *state; + int gpio; + int ret; + + p = devm_pinctrl_get(dev); + if (IS_ERR(p)) { + dev_err(dev, "Failed to get pinctrl\n"); + return PTR_ERR(p); + } + cfg->pctl = p; + + state = pinctrl_lookup_state(p, "ac97-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-reset\n"); + return PTR_ERR(state); + } + cfg->pstate_reset = state; + + state = pinctrl_lookup_state(p, "ac97-warm-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-warm-reset\n"); + return PTR_ERR(state); + } + cfg->pstate_warm_reset = state; + + state = pinctrl_lookup_state(p, "ac97-running"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-running\n"); + return PTR_ERR(state); + } + cfg->pstate_run = state; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 0); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sync gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sync"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sync gpio\n"); + return ret; + } + cfg->gpio_sync = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 1); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sdata gpio %d\n", gpio); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sdata"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sdata gpio\n"); + return ret; + } + cfg->gpio_sdata = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 2); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-reset gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link reset"); + if (ret) { + dev_err(dev, "Failed requesting ac97-reset gpio\n"); + return ret; + } + cfg->gpio_reset = gpio; + + return 0; +} + +struct snd_ac97_bus_ops *soc_ac97_ops; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) +{ + if (ops == soc_ac97_ops) + return 0; + + if (soc_ac97_ops && ops) + return -EBUSY; + + soc_ac97_ops = ops; + soc_ac97_bus.ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops); + +/** + * snd_soc_set_ac97_ops_of_reset - Set ac97 ops with generic ac97 reset functions + * + * This function sets the reset and warm_reset properties of ops and parses + * the device node of pdev to get pinctrl states and gpio numbers to use. + */ +int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_ac97_reset_cfg cfg; + int ret; + + ret = snd_soc_ac97_parse_pinctl(dev, &cfg); + if (ret) + return ret; + + ret = snd_soc_set_ac97_ops(ops); + if (ret) + return ret; + + ops->warm_reset = snd_soc_ac97_warm_reset; + ops->reset = snd_soc_ac97_reset; + + snd_ac97_rst_cfg = cfg; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops_of_reset); diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index a9f82b5aba9..07f43356f96 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -15,56 +15,6 @@ #include <linux/export.h> #include <linux/slab.h> -#include <trace/events/asoc.h> - -static bool snd_soc_set_cache_val(void *base, unsigned int idx, - unsigned int val, unsigned int word_size) -{ - switch (word_size) { - case 1: { - u8 *cache = base; - if (cache[idx] == val) - return true; - cache[idx] = val; - break; - } - case 2: { - u16 *cache = base; - if (cache[idx] == val) - return true; - cache[idx] = val; - break; - } - default: - WARN(1, "Invalid word_size %d\n", word_size); - break; - } - return false; -} - -static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, - unsigned int word_size) -{ - if (!base) - return -1; - - switch (word_size) { - case 1: { - const u8 *cache = base; - return cache[idx]; - } - case 2: { - const u16 *cache = base; - return cache[idx]; - } - default: - WARN(1, "Invalid word_size %d\n", word_size); - break; - } - /* unreachable */ - return -1; -} - int snd_soc_cache_init(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv = codec->driver; @@ -75,8 +25,6 @@ int snd_soc_cache_init(struct snd_soc_codec *codec) if (!reg_size) return 0; - mutex_init(&codec->cache_rw_mutex); - dev_dbg(codec->dev, "ASoC: Initializing cache for %s codec\n", codec->component.name); @@ -103,100 +51,3 @@ int snd_soc_cache_exit(struct snd_soc_codec *codec) codec->reg_cache = NULL; return 0; } - -/** - * snd_soc_cache_read: Fetch the value of a given register from the cache. - * - * @codec: CODEC to configure. - * @reg: The register index. - * @value: The value to be returned. - */ -int snd_soc_cache_read(struct snd_soc_codec *codec, - unsigned int reg, unsigned int *value) -{ - if (!value) - return -EINVAL; - - mutex_lock(&codec->cache_rw_mutex); - if (!ZERO_OR_NULL_PTR(codec->reg_cache)) - *value = snd_soc_get_cache_val(codec->reg_cache, reg, - codec->driver->reg_word_size); - mutex_unlock(&codec->cache_rw_mutex); - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_cache_read); - -/** - * snd_soc_cache_write: Set the value of a given register in the cache. - * - * @codec: CODEC to configure. - * @reg: The register index. - * @value: The new register value. - */ -int snd_soc_cache_write(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - mutex_lock(&codec->cache_rw_mutex); - if (!ZERO_OR_NULL_PTR(codec->reg_cache)) - snd_soc_set_cache_val(codec->reg_cache, reg, value, - codec->driver->reg_word_size); - mutex_unlock(&codec->cache_rw_mutex); - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_cache_write); - -static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) -{ - int i; - int ret; - const struct snd_soc_codec_driver *codec_drv; - unsigned int val; - - codec_drv = codec->driver; - for (i = 0; i < codec_drv->reg_cache_size; ++i) { - ret = snd_soc_cache_read(codec, i, &val); - if (ret) - return ret; - if (codec_drv->reg_cache_default) - if (snd_soc_get_cache_val(codec_drv->reg_cache_default, - i, codec_drv->reg_word_size) == val) - continue; - - ret = snd_soc_write(codec, i, val); - if (ret) - return ret; - dev_dbg(codec->dev, "ASoC: Synced register %#x, value = %#x\n", - i, val); - } - return 0; -} - -/** - * snd_soc_cache_sync: Sync the register cache with the hardware. - * - * @codec: CODEC to configure. - * - * Any registers that should not be synced should be marked as - * volatile. In general drivers can choose not to use the provided - * syncing functionality if they so require. - */ -int snd_soc_cache_sync(struct snd_soc_codec *codec) -{ - const char *name = "flat"; - int ret; - - if (!codec->cache_sync) - return 0; - - dev_dbg(codec->dev, "ASoC: Syncing cache for %s codec\n", - codec->component.name); - trace_snd_soc_cache_sync(codec, name, "start"); - ret = snd_soc_flat_cache_sync(codec); - if (!ret) - codec->cache_sync = 0; - trace_snd_soc_cache_sync(codec, name, "end"); - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_cache_sync); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index cecfab3cc94..590a82f01d0 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -258,10 +258,7 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); - else - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; @@ -456,11 +453,7 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream, if (ret < 0) goto out; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); - else - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); - + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; out: diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4c8f8a23a0e..935721062c2 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -34,9 +34,6 @@ #include <linux/ctype.h> #include <linux/slab.h> #include <linux/of.h> -#include <linux/gpio.h> -#include <linux/of_gpio.h> -#include <sound/ac97_codec.h> #include <sound/core.h> #include <sound/jack.h> #include <sound/pcm.h> @@ -69,16 +66,6 @@ static int pmdown_time = 5000; module_param(pmdown_time, int, 0); MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)"); -struct snd_ac97_reset_cfg { - struct pinctrl *pctl; - struct pinctrl_state *pstate_reset; - struct pinctrl_state *pstate_warm_reset; - struct pinctrl_state *pstate_run; - int gpio_sdata; - int gpio_sync; - int gpio_reset; -}; - /* returns the minimum number of bytes needed to represent * a particular given value */ static int min_bytes_needed(unsigned long val) @@ -309,9 +296,6 @@ static void soc_init_codec_debugfs(struct snd_soc_component *component) { struct snd_soc_codec *codec = snd_soc_component_to_codec(component); - debugfs_create_bool("cache_sync", 0444, codec->component.debugfs_root, - &codec->cache_sync); - codec->debugfs_reg = debugfs_create_file("codec_reg", 0644, codec->component.debugfs_root, codec, &codec_reg_fops); @@ -499,40 +483,6 @@ struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); -#ifdef CONFIG_SND_SOC_AC97_BUS -/* unregister ac97 codec */ -static int soc_ac97_dev_unregister(struct snd_soc_codec *codec) -{ - if (codec->ac97->dev.bus) - device_unregister(&codec->ac97->dev); - return 0; -} - -/* stop no dev release warning */ -static void soc_ac97_device_release(struct device *dev){} - -/* register ac97 codec to bus */ -static int soc_ac97_dev_register(struct snd_soc_codec *codec) -{ - int err; - - codec->ac97->dev.bus = &ac97_bus_type; - codec->ac97->dev.parent = codec->component.card->dev; - codec->ac97->dev.release = soc_ac97_device_release; - - dev_set_name(&codec->ac97->dev, "%d-%d:%s", - codec->component.card->snd_card->number, 0, - codec->component.name); - err = device_register(&codec->ac97->dev); - if (err < 0) { - dev_err(codec->dev, "ASoC: Can't register ac97 bus\n"); - codec->ac97->dev.bus = NULL; - return err; - } - return 0; -} -#endif - static void codec2codec_close_delayed_work(struct work_struct *work) { /* Currently nothing to do for c2c links @@ -592,17 +542,12 @@ int snd_soc_suspend(struct device *dev) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_platform *platform = card->rtd[i].platform; if (card->rtd[i].dai_link->ignore_suspend) continue; - if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) + if (cpu_dai->driver->suspend && !cpu_dai->driver->bus_control) cpu_dai->driver->suspend(cpu_dai); - if (platform->driver->suspend && !platform->suspended) { - platform->driver->suspend(cpu_dai); - platform->suspended = 1; - } } /* close any waiting streams and save state */ @@ -629,8 +574,8 @@ int snd_soc_suspend(struct device *dev) SND_SOC_DAPM_STREAM_SUSPEND); } - /* Recheck all analogue paths too */ - dapm_mark_io_dirty(&card->dapm); + /* Recheck all endpoints too, their state is affected by suspend */ + dapm_mark_endpoints_dirty(card); snd_soc_dapm_sync(&card->dapm); /* suspend all CODECs */ @@ -656,7 +601,6 @@ int snd_soc_suspend(struct device *dev) if (codec->driver->suspend) codec->driver->suspend(codec); codec->suspended = 1; - codec->cache_sync = 1; if (codec->component.regmap) regcache_mark_dirty(codec->component.regmap); /* deactivate pins to sleep state */ @@ -676,7 +620,7 @@ int snd_soc_suspend(struct device *dev) if (card->rtd[i].dai_link->ignore_suspend) continue; - if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) + if (cpu_dai->driver->suspend && cpu_dai->driver->bus_control) cpu_dai->driver->suspend(cpu_dai); /* deactivate pins to sleep state */ @@ -712,14 +656,14 @@ static void soc_resume_deferred(struct work_struct *work) if (card->resume_pre) card->resume_pre(card); - /* resume AC97 DAIs */ + /* resume control bus DAIs */ for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; if (card->rtd[i].dai_link->ignore_suspend) continue; - if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control) + if (cpu_dai->driver->resume && cpu_dai->driver->bus_control) cpu_dai->driver->resume(cpu_dai); } @@ -775,17 +719,12 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_platform *platform = card->rtd[i].platform; if (card->rtd[i].dai_link->ignore_suspend) continue; - if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control) + if (cpu_dai->driver->resume && !cpu_dai->driver->bus_control) cpu_dai->driver->resume(cpu_dai); - if (platform->driver->resume && platform->suspended) { - platform->driver->resume(cpu_dai); - platform->suspended = 0; - } } if (card->resume_post) @@ -796,8 +735,8 @@ static void soc_resume_deferred(struct work_struct *work) /* userspace can access us now we are back as we were before */ snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); - /* Recheck all analogue paths too */ - dapm_mark_io_dirty(&card->dapm); + /* Recheck all endpoints too, their state is affected by suspend */ + dapm_mark_endpoints_dirty(card); snd_soc_dapm_sync(&card->dapm); } @@ -805,7 +744,8 @@ static void soc_resume_deferred(struct work_struct *work) int snd_soc_resume(struct device *dev) { struct snd_soc_card *card = dev_get_drvdata(dev); - int i, ac97_control = 0; + bool bus_control = false; + int i; /* If the card is not initialized yet there is nothing to do */ if (!card->instantiated) @@ -828,17 +768,18 @@ int snd_soc_resume(struct device *dev) } } - /* AC97 devices might have other drivers hanging off them so - * need to resume immediately. Other drivers don't have that - * problem and may take a substantial amount of time to resume + /* + * DAIs that also act as the control bus master might have other drivers + * hanging off them so need to resume immediately. Other drivers don't + * have that problem and may take a substantial amount of time to resume * due to I/O costs and anti-pop so handle them out of line. */ for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - ac97_control |= cpu_dai->driver->ac97_control; + bus_control |= cpu_dai->driver->bus_control; } - if (ac97_control) { - dev_dbg(dev, "ASoC: Resuming AC97 immediately\n"); + if (bus_control) { + dev_dbg(dev, "ASoC: Resuming control bus master immediately\n"); soc_resume_deferred(&card->deferred_resume_work); } else { dev_dbg(dev, "ASoC: Scheduling resume work\n"); @@ -884,7 +825,7 @@ static struct snd_soc_dai *snd_soc_find_dai( list_for_each_entry(component, &component_list, list) { if (dlc->of_node && component->dev->of_node != dlc->of_node) continue; - if (dlc->name && strcmp(dev_name(component->dev), dlc->name)) + if (dlc->name && strcmp(component->name, dlc->name)) continue; list_for_each_entry(dai, &component->dai_list, list) { if (dlc->dai_name && strcmp(dai->name, dlc->dai_name)) @@ -1251,25 +1192,22 @@ static int soc_probe_link_components(struct snd_soc_card *card, int num, return 0; } -static int soc_probe_codec_dai(struct snd_soc_card *card, - struct snd_soc_dai *codec_dai, - int order) +static int soc_probe_dai(struct snd_soc_dai *dai, int order) { int ret; - if (!codec_dai->probed && codec_dai->driver->probe_order == order) { - if (codec_dai->driver->probe) { - ret = codec_dai->driver->probe(codec_dai); + if (!dai->probed && dai->driver->probe_order == order) { + if (dai->driver->probe) { + ret = dai->driver->probe(dai); if (ret < 0) { - dev_err(codec_dai->dev, - "ASoC: failed to probe CODEC DAI %s: %d\n", - codec_dai->name, ret); + dev_err(dai->dev, + "ASoC: failed to probe DAI %s: %d\n", + dai->name, ret); return ret; } } - /* mark codec_dai as probed and add to card dai list */ - codec_dai->probed = 1; + dai->probed = 1; } return 0; @@ -1319,40 +1257,22 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int i, ret; dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", card->name, num, order); - /* config components */ - cpu_dai->platform = platform; - cpu_dai->card = card; - for (i = 0; i < rtd->num_codecs; i++) - rtd->codec_dais[i]->card = card; - /* set default power off timeout */ rtd->pmdown_time = pmdown_time; - /* probe the cpu_dai */ - if (!cpu_dai->probed && - cpu_dai->driver->probe_order == order) { - if (cpu_dai->driver->probe) { - ret = cpu_dai->driver->probe(cpu_dai); - if (ret < 0) { - dev_err(cpu_dai->dev, - "ASoC: failed to probe CPU DAI %s: %d\n", - cpu_dai->name, ret); - return ret; - } - } - cpu_dai->probed = 1; - } + ret = soc_probe_dai(cpu_dai, order); + if (ret) + return ret; /* probe the CODEC DAI */ for (i = 0; i < rtd->num_codecs; i++) { - ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order); + ret = soc_probe_dai(rtd->codec_dais[i], order); if (ret) return ret; } @@ -1422,84 +1342,9 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) } } - /* add platform data for AC97 devices */ - for (i = 0; i < rtd->num_codecs; i++) { - if (rtd->codec_dais[i]->driver->ac97_control) - snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97, - rtd->cpu_dai->ac97_pdata); - } - return 0; } -#ifdef CONFIG_SND_SOC_AC97_BUS -static int soc_register_ac97_codec(struct snd_soc_codec *codec, - struct snd_soc_dai *codec_dai) -{ - int ret; - - /* Only instantiate AC97 if not already done by the adaptor - * for the generic AC97 subsystem. - */ - if (codec_dai->driver->ac97_control && !codec->ac97_registered) { - /* - * It is possible that the AC97 device is already registered to - * the device subsystem. This happens when the device is created - * via snd_ac97_mixer(). Currently only SoC codec that does so - * is the generic AC97 glue but others migh emerge. - * - * In those cases we don't try to register the device again. - */ - if (!codec->ac97_created) - return 0; - - ret = soc_ac97_dev_register(codec); - if (ret < 0) { - dev_err(codec->dev, - "ASoC: AC97 device register failed: %d\n", ret); - return ret; - } - - codec->ac97_registered = 1; - } - return 0; -} - -static void soc_unregister_ac97_codec(struct snd_soc_codec *codec) -{ - if (codec->ac97_registered) { - soc_ac97_dev_unregister(codec); - codec->ac97_registered = 0; - } -} - -static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) -{ - int i, ret; - - for (i = 0; i < rtd->num_codecs; i++) { - struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; - - ret = soc_register_ac97_codec(codec_dai->codec, codec_dai); - if (ret) { - while (--i >= 0) - soc_unregister_ac97_codec(codec_dai->codec); - return ret; - } - } - - return 0; -} - -static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) -{ - int i; - - for (i = 0; i < rtd->num_codecs; i++) - soc_unregister_ac97_codec(rtd->codec_dais[i]->codec); -} -#endif - static int soc_bind_aux_dev(struct snd_soc_card *card, int num) { struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; @@ -1793,20 +1638,6 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) goto probe_aux_dev_err; } -#ifdef CONFIG_SND_SOC_AC97_BUS - /* register any AC97 codecs */ - for (i = 0; i < card->num_rtd; i++) { - ret = soc_register_ac97_dai_link(&card->rtd[i]); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to register AC97: %d\n", ret); - while (--i >= 0) - soc_unregister_ac97_dai_link(&card->rtd[i]); - goto probe_aux_dev_err; - } - } -#endif - card->instantiated = 1; snd_soc_dapm_sync(&card->dapm); mutex_unlock(&card->mutex); @@ -1949,216 +1780,6 @@ static struct platform_driver soc_driver = { }; /** - * snd_soc_new_ac97_codec - initailise AC97 device - * @codec: audio codec - * @ops: AC97 bus operations - * @num: AC97 codec number - * - * Initialises AC97 codec resources for use by ad-hoc devices only. - */ -int snd_soc_new_ac97_codec(struct snd_soc_codec *codec, - struct snd_ac97_bus_ops *ops, int num) -{ - codec->ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL); - if (codec->ac97 == NULL) - return -ENOMEM; - - codec->ac97->bus = kzalloc(sizeof(struct snd_ac97_bus), GFP_KERNEL); - if (codec->ac97->bus == NULL) { - kfree(codec->ac97); - codec->ac97 = NULL; - return -ENOMEM; - } - - codec->ac97->bus->ops = ops; - codec->ac97->num = num; - - /* - * Mark the AC97 device to be created by us. This way we ensure that the - * device will be registered with the device subsystem later on. - */ - codec->ac97_created = 1; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec); - -static struct snd_ac97_reset_cfg snd_ac97_rst_cfg; - -static void snd_soc_ac97_warm_reset(struct snd_ac97 *ac97) -{ - struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; - - pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_warm_reset); - - gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 1); - - udelay(10); - - gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); - - pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); - msleep(2); -} - -static void snd_soc_ac97_reset(struct snd_ac97 *ac97) -{ - struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; - - pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_reset); - - gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); - gpio_direction_output(snd_ac97_rst_cfg.gpio_sdata, 0); - gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 0); - - udelay(10); - - gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 1); - - pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); - msleep(2); -} - -static int snd_soc_ac97_parse_pinctl(struct device *dev, - struct snd_ac97_reset_cfg *cfg) -{ - struct pinctrl *p; - struct pinctrl_state *state; - int gpio; - int ret; - - p = devm_pinctrl_get(dev); - if (IS_ERR(p)) { - dev_err(dev, "Failed to get pinctrl\n"); - return PTR_ERR(p); - } - cfg->pctl = p; - - state = pinctrl_lookup_state(p, "ac97-reset"); - if (IS_ERR(state)) { - dev_err(dev, "Can't find pinctrl state ac97-reset\n"); - return PTR_ERR(state); - } - cfg->pstate_reset = state; - - state = pinctrl_lookup_state(p, "ac97-warm-reset"); - if (IS_ERR(state)) { - dev_err(dev, "Can't find pinctrl state ac97-warm-reset\n"); - return PTR_ERR(state); - } - cfg->pstate_warm_reset = state; - - state = pinctrl_lookup_state(p, "ac97-running"); - if (IS_ERR(state)) { - dev_err(dev, "Can't find pinctrl state ac97-running\n"); - return PTR_ERR(state); - } - cfg->pstate_run = state; - - gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 0); - if (gpio < 0) { - dev_err(dev, "Can't find ac97-sync gpio\n"); - return gpio; - } - ret = devm_gpio_request(dev, gpio, "AC97 link sync"); - if (ret) { - dev_err(dev, "Failed requesting ac97-sync gpio\n"); - return ret; - } - cfg->gpio_sync = gpio; - - gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 1); - if (gpio < 0) { - dev_err(dev, "Can't find ac97-sdata gpio %d\n", gpio); - return gpio; - } - ret = devm_gpio_request(dev, gpio, "AC97 link sdata"); - if (ret) { - dev_err(dev, "Failed requesting ac97-sdata gpio\n"); - return ret; - } - cfg->gpio_sdata = gpio; - - gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 2); - if (gpio < 0) { - dev_err(dev, "Can't find ac97-reset gpio\n"); - return gpio; - } - ret = devm_gpio_request(dev, gpio, "AC97 link reset"); - if (ret) { - dev_err(dev, "Failed requesting ac97-reset gpio\n"); - return ret; - } - cfg->gpio_reset = gpio; - - return 0; -} - -struct snd_ac97_bus_ops *soc_ac97_ops; -EXPORT_SYMBOL_GPL(soc_ac97_ops); - -int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) -{ - if (ops == soc_ac97_ops) - return 0; - - if (soc_ac97_ops && ops) - return -EBUSY; - - soc_ac97_ops = ops; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops); - -/** - * snd_soc_set_ac97_ops_of_reset - Set ac97 ops with generic ac97 reset functions - * - * This function sets the reset and warm_reset properties of ops and parses - * the device node of pdev to get pinctrl states and gpio numbers to use. - */ -int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops, - struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct snd_ac97_reset_cfg cfg; - int ret; - - ret = snd_soc_ac97_parse_pinctl(dev, &cfg); - if (ret) - return ret; - - ret = snd_soc_set_ac97_ops(ops); - if (ret) - return ret; - - ops->warm_reset = snd_soc_ac97_warm_reset; - ops->reset = snd_soc_ac97_reset; - - snd_ac97_rst_cfg = cfg; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops_of_reset); - -/** - * snd_soc_free_ac97_codec - free AC97 codec device - * @codec: audio codec - * - * Frees AC97 codec device resources. - */ -void snd_soc_free_ac97_codec(struct snd_soc_codec *codec) -{ -#ifdef CONFIG_SND_SOC_AC97_BUS - soc_unregister_ac97_codec(codec); -#endif - kfree(codec->ac97->bus); - kfree(codec->ac97); - codec->ac97 = NULL; - codec->ac97_created = 0; -} -EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec); - -/** * snd_soc_cnew - create new control * @_template: control template * @data: control private data @@ -2326,7 +1947,7 @@ EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); int snd_soc_add_dai_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls) { - struct snd_card *card = dai->card->snd_card; + struct snd_card *card = dai->component->card->snd_card; return snd_soc_add_controls(card, dai->dev, controls, num_controls, NULL, dai); @@ -2334,1020 +1955,6 @@ int snd_soc_add_dai_controls(struct snd_soc_dai *dai, EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls); /** - * snd_soc_info_enum_double - enumerated double mixer info callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Callback to provide information about a double enumerated - * mixer control. - * - * Returns 0 for success. - */ -int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = e->shift_l == e->shift_r ? 1 : 2; - uinfo->value.enumerated.items = e->items; - - if (uinfo->value.enumerated.item >= e->items) - uinfo->value.enumerated.item = e->items - 1; - strlcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item], - sizeof(uinfo->value.enumerated.name)); - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); - -/** - * snd_soc_get_enum_double - enumerated double mixer get callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to get the value of a double enumerated mixer. - * - * Returns 0 for success. - */ -int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned int val, item; - unsigned int reg_val; - int ret; - - ret = snd_soc_component_read(component, e->reg, ®_val); - if (ret) - return ret; - val = (reg_val >> e->shift_l) & e->mask; - item = snd_soc_enum_val_to_item(e, val); - ucontrol->value.enumerated.item[0] = item; - if (e->shift_l != e->shift_r) { - val = (reg_val >> e->shift_l) & e->mask; - item = snd_soc_enum_val_to_item(e, val); - ucontrol->value.enumerated.item[1] = item; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_enum_double); - -/** - * snd_soc_put_enum_double - enumerated double mixer put callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to set the value of a double enumerated mixer. - * - * Returns 0 for success. - */ -int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned int *item = ucontrol->value.enumerated.item; - unsigned int val; - unsigned int mask; - - if (item[0] >= e->items) - return -EINVAL; - val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; - mask = e->mask << e->shift_l; - if (e->shift_l != e->shift_r) { - if (item[1] >= e->items) - return -EINVAL; - val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; - mask |= e->mask << e->shift_r; - } - - return snd_soc_component_update_bits(component, e->reg, mask, val); -} -EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); - -/** - * snd_soc_read_signed - Read a codec register and interprete as signed value - * @component: component - * @reg: Register to read - * @mask: Mask to use after shifting the register value - * @shift: Right shift of register value - * @sign_bit: Bit that describes if a number is negative or not. - * @signed_val: Pointer to where the read value should be stored - * - * This functions reads a codec register. The register value is shifted right - * by 'shift' bits and masked with the given 'mask'. Afterwards it translates - * the given registervalue into a signed integer if sign_bit is non-zero. - * - * Returns 0 on sucess, otherwise an error value - */ -static int snd_soc_read_signed(struct snd_soc_component *component, - unsigned int reg, unsigned int mask, unsigned int shift, - unsigned int sign_bit, int *signed_val) -{ - int ret; - unsigned int val; - - ret = snd_soc_component_read(component, reg, &val); - if (ret < 0) - return ret; - - val = (val >> shift) & mask; - - if (!sign_bit) { - *signed_val = val; - return 0; - } - - /* non-negative number */ - if (!(val & BIT(sign_bit))) { - *signed_val = val; - return 0; - } - - ret = val; - - /* - * The register most probably does not contain a full-sized int. - * Instead we have an arbitrary number of bits in a signed - * representation which has to be translated into a full-sized int. - * This is done by filling up all bits above the sign-bit. - */ - ret |= ~((int)(BIT(sign_bit) - 1)); - - *signed_val = ret; - - return 0; -} - -/** - * snd_soc_info_volsw - single mixer info callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Callback to provide information about a single mixer control, or a double - * mixer control that spans 2 registers. - * - * Returns 0 for success. - */ -int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int platform_max; - - if (!mc->platform_max) - mc->platform_max = mc->max; - platform_max = mc->platform_max; - - if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - else - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - - uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = platform_max - mc->min; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_volsw); - -/** - * snd_soc_get_volsw - single mixer get callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to get the value of a single mixer control, or a double mixer - * control that spans 2 registers. - * - * Returns 0 for success. - */ -int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int reg2 = mc->rreg; - unsigned int shift = mc->shift; - unsigned int rshift = mc->rshift; - int max = mc->max; - int min = mc->min; - int sign_bit = mc->sign_bit; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - int val; - int ret; - - if (sign_bit) - mask = BIT(sign_bit + 1) - 1; - - ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val); - if (ret) - return ret; - - ucontrol->value.integer.value[0] = val - min; - if (invert) - ucontrol->value.integer.value[0] = - max - ucontrol->value.integer.value[0]; - - if (snd_soc_volsw_is_stereo(mc)) { - if (reg == reg2) - ret = snd_soc_read_signed(component, reg, mask, rshift, - sign_bit, &val); - else - ret = snd_soc_read_signed(component, reg2, mask, shift, - sign_bit, &val); - if (ret) - return ret; - - ucontrol->value.integer.value[1] = val - min; - if (invert) - ucontrol->value.integer.value[1] = - max - ucontrol->value.integer.value[1]; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_volsw); - -/** - * snd_soc_put_volsw - single mixer put callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to set the value of a single mixer control, or a double mixer - * control that spans 2 registers. - * - * Returns 0 for success. - */ -int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int reg2 = mc->rreg; - unsigned int shift = mc->shift; - unsigned int rshift = mc->rshift; - int max = mc->max; - int min = mc->min; - unsigned int sign_bit = mc->sign_bit; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - int err; - bool type_2r = false; - unsigned int val2 = 0; - unsigned int val, val_mask; - - if (sign_bit) - mask = BIT(sign_bit + 1) - 1; - - val = ((ucontrol->value.integer.value[0] + min) & mask); - if (invert) - val = max - val; - val_mask = mask << shift; - val = val << shift; - if (snd_soc_volsw_is_stereo(mc)) { - val2 = ((ucontrol->value.integer.value[1] + min) & mask); - if (invert) - val2 = max - val2; - if (reg == reg2) { - val_mask |= mask << rshift; - val |= val2 << rshift; - } else { - val2 = val2 << shift; - type_2r = true; - } - } - err = snd_soc_component_update_bits(component, reg, val_mask, val); - if (err < 0) - return err; - - if (type_2r) - err = snd_soc_component_update_bits(component, reg2, val_mask, - val2); - - return err; -} -EXPORT_SYMBOL_GPL(snd_soc_put_volsw); - -/** - * snd_soc_get_volsw_sx - single mixer get callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to get the value of a single mixer control, or a double mixer - * control that spans 2 registers. - * - * Returns 0 for success. - */ -int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int reg2 = mc->rreg; - unsigned int shift = mc->shift; - unsigned int rshift = mc->rshift; - int max = mc->max; - int min = mc->min; - int mask = (1 << (fls(min + max) - 1)) - 1; - unsigned int val; - int ret; - - ret = snd_soc_component_read(component, reg, &val); - if (ret < 0) - return ret; - - ucontrol->value.integer.value[0] = ((val >> shift) - min) & mask; - - if (snd_soc_volsw_is_stereo(mc)) { - ret = snd_soc_component_read(component, reg2, &val); - if (ret < 0) - return ret; - - val = ((val >> rshift) - min) & mask; - ucontrol->value.integer.value[1] = val; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx); - -/** - * snd_soc_put_volsw_sx - double mixer set callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Callback to set the value of a double mixer control that spans 2 registers. - * - * Returns 0 for success. - */ -int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - - unsigned int reg = mc->reg; - unsigned int reg2 = mc->rreg; - unsigned int shift = mc->shift; - unsigned int rshift = mc->rshift; - int max = mc->max; - int min = mc->min; - int mask = (1 << (fls(min + max) - 1)) - 1; - int err = 0; - unsigned int val, val_mask, val2 = 0; - - val_mask = mask << shift; - val = (ucontrol->value.integer.value[0] + min) & mask; - val = val << shift; - - err = snd_soc_component_update_bits(component, reg, val_mask, val); - if (err < 0) - return err; - - if (snd_soc_volsw_is_stereo(mc)) { - val_mask = mask << rshift; - val2 = (ucontrol->value.integer.value[1] + min) & mask; - val2 = val2 << rshift; - - err = snd_soc_component_update_bits(component, reg2, val_mask, - val2); - } - return err; -} -EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); - -/** - * snd_soc_info_volsw_s8 - signed mixer info callback - * @kcontrol: mixer control - * @uinfo: control element information - * - * Callback to provide information about a signed mixer control. - * - * Returns 0 for success. - */ -int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int platform_max; - int min = mc->min; - - if (!mc->platform_max) - mc->platform_max = mc->max; - platform_max = mc->platform_max; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 2; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = platform_max - min; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); - -/** - * snd_soc_get_volsw_s8 - signed mixer get callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to get the value of a signed mixer control. - * - * Returns 0 for success. - */ -int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - unsigned int reg = mc->reg; - unsigned int val; - int min = mc->min; - int ret; - - ret = snd_soc_component_read(component, reg, &val); - if (ret) - return ret; - - ucontrol->value.integer.value[0] = - ((signed char)(val & 0xff))-min; - ucontrol->value.integer.value[1] = - ((signed char)((val >> 8) & 0xff))-min; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); - -/** - * snd_soc_put_volsw_sgn - signed mixer put callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to set the value of a signed mixer control. - * - * Returns 0 for success. - */ -int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - unsigned int reg = mc->reg; - int min = mc->min; - unsigned int val; - - val = (ucontrol->value.integer.value[0]+min) & 0xff; - val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8; - - return snd_soc_component_update_bits(component, reg, 0xffff, val); -} -EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); - -/** - * snd_soc_info_volsw_range - single mixer info callback with range. - * @kcontrol: mixer control - * @uinfo: control element information - * - * Callback to provide information, within a range, about a single - * mixer control. - * - * returns 0 for success. - */ -int snd_soc_info_volsw_range(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int platform_max; - int min = mc->min; - - if (!mc->platform_max) - mc->platform_max = mc->max; - platform_max = mc->platform_max; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = platform_max - min; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_volsw_range); - -/** - * snd_soc_put_volsw_range - single mixer put value callback with range. - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to set the value, within a range, for a single mixer control. - * - * Returns 0 for success. - */ -int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - unsigned int reg = mc->reg; - unsigned int rreg = mc->rreg; - unsigned int shift = mc->shift; - int min = mc->min; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - unsigned int val, val_mask; - int ret; - - if (invert) - val = (max - ucontrol->value.integer.value[0]) & mask; - else - val = ((ucontrol->value.integer.value[0] + min) & mask); - val_mask = mask << shift; - val = val << shift; - - ret = snd_soc_component_update_bits(component, reg, val_mask, val); - if (ret < 0) - return ret; - - if (snd_soc_volsw_is_stereo(mc)) { - if (invert) - val = (max - ucontrol->value.integer.value[1]) & mask; - else - val = ((ucontrol->value.integer.value[1] + min) & mask); - val_mask = mask << shift; - val = val << shift; - - ret = snd_soc_component_update_bits(component, rreg, val_mask, - val); - } - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_put_volsw_range); - -/** - * snd_soc_get_volsw_range - single mixer get callback with range - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback to get the value, within a range, of a single mixer control. - * - * Returns 0 for success. - */ -int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int rreg = mc->rreg; - unsigned int shift = mc->shift; - int min = mc->min; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - unsigned int val; - int ret; - - ret = snd_soc_component_read(component, reg, &val); - if (ret) - return ret; - - ucontrol->value.integer.value[0] = (val >> shift) & mask; - if (invert) - ucontrol->value.integer.value[0] = - max - ucontrol->value.integer.value[0]; - else - ucontrol->value.integer.value[0] = - ucontrol->value.integer.value[0] - min; - - if (snd_soc_volsw_is_stereo(mc)) { - ret = snd_soc_component_read(component, rreg, &val); - if (ret) - return ret; - - ucontrol->value.integer.value[1] = (val >> shift) & mask; - if (invert) - ucontrol->value.integer.value[1] = - max - ucontrol->value.integer.value[1]; - else - ucontrol->value.integer.value[1] = - ucontrol->value.integer.value[1] - min; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range); - -/** - * snd_soc_limit_volume - Set new limit to an existing volume control. - * - * @codec: where to look for the control - * @name: Name of the control - * @max: new maximum limit - * - * Return 0 for success, else error. - */ -int snd_soc_limit_volume(struct snd_soc_codec *codec, - const char *name, int max) -{ - struct snd_card *card = codec->component.card->snd_card; - struct snd_kcontrol *kctl; - struct soc_mixer_control *mc; - int found = 0; - int ret = -EINVAL; - - /* Sanity check for name and max */ - if (unlikely(!name || max <= 0)) - return -EINVAL; - - list_for_each_entry(kctl, &card->controls, list) { - if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) { - found = 1; - break; - } - } - if (found) { - mc = (struct soc_mixer_control *)kctl->private_value; - if (max <= mc->max) { - mc->platform_max = max; - ret = 0; - } - } - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_limit_volume); - -int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_bytes *params = (void *)kcontrol->private_value; - - uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = params->num_regs * component->val_bytes; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_info); - -int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_bytes *params = (void *)kcontrol->private_value; - int ret; - - if (component->regmap) - ret = regmap_raw_read(component->regmap, params->base, - ucontrol->value.bytes.data, - params->num_regs * component->val_bytes); - else - ret = -EINVAL; - - /* Hide any masked bytes to ensure consistent data reporting */ - if (ret == 0 && params->mask) { - switch (component->val_bytes) { - case 1: - ucontrol->value.bytes.data[0] &= ~params->mask; - break; - case 2: - ((u16 *)(&ucontrol->value.bytes.data))[0] - &= cpu_to_be16(~params->mask); - break; - case 4: - ((u32 *)(&ucontrol->value.bytes.data))[0] - &= cpu_to_be32(~params->mask); - break; - default: - return -EINVAL; - } - } - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_get); - -int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_bytes *params = (void *)kcontrol->private_value; - int ret, len; - unsigned int val, mask; - void *data; - - if (!component->regmap || !params->num_regs) - return -EINVAL; - - len = params->num_regs * component->val_bytes; - - data = kmemdup(ucontrol->value.bytes.data, len, GFP_KERNEL | GFP_DMA); - if (!data) - return -ENOMEM; - - /* - * If we've got a mask then we need to preserve the register - * bits. We shouldn't modify the incoming data so take a - * copy. - */ - if (params->mask) { - ret = regmap_read(component->regmap, params->base, &val); - if (ret != 0) - goto out; - - val &= params->mask; - - switch (component->val_bytes) { - case 1: - ((u8 *)data)[0] &= ~params->mask; - ((u8 *)data)[0] |= val; - break; - case 2: - mask = ~params->mask; - ret = regmap_parse_val(component->regmap, - &mask, &mask); - if (ret != 0) - goto out; - - ((u16 *)data)[0] &= mask; - - ret = regmap_parse_val(component->regmap, - &val, &val); - if (ret != 0) - goto out; - - ((u16 *)data)[0] |= val; - break; - case 4: - mask = ~params->mask; - ret = regmap_parse_val(component->regmap, - &mask, &mask); - if (ret != 0) - goto out; - - ((u32 *)data)[0] &= mask; - - ret = regmap_parse_val(component->regmap, - &val, &val); - if (ret != 0) - goto out; - - ((u32 *)data)[0] |= val; - break; - default: - ret = -EINVAL; - goto out; - } - } - - ret = regmap_raw_write(component->regmap, params->base, - data, len); - -out: - kfree(data); - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_put); - -int snd_soc_bytes_info_ext(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *ucontrol) -{ - struct soc_bytes_ext *params = (void *)kcontrol->private_value; - - ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES; - ucontrol->count = params->max; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_info_ext); - -int snd_soc_bytes_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag, - unsigned int size, unsigned int __user *tlv) -{ - struct soc_bytes_ext *params = (void *)kcontrol->private_value; - unsigned int count = size < params->max ? size : params->max; - int ret = -ENXIO; - - switch (op_flag) { - case SNDRV_CTL_TLV_OP_READ: - if (params->get) - ret = params->get(tlv, count); - break; - case SNDRV_CTL_TLV_OP_WRITE: - if (params->put) - ret = params->put(tlv, count); - break; - } - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_bytes_tlv_callback); - -/** - * snd_soc_info_xr_sx - signed multi register info callback - * @kcontrol: mreg control - * @uinfo: control element information - * - * Callback to provide information of a control that can - * span multiple codec registers which together - * forms a single signed value in a MSB/LSB manner. - * - * Returns 0 for success. - */ -int snd_soc_info_xr_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct soc_mreg_control *mc = - (struct soc_mreg_control *)kcontrol->private_value; - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = 1; - uinfo->value.integer.min = mc->min; - uinfo->value.integer.max = mc->max; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_info_xr_sx); - -/** - * snd_soc_get_xr_sx - signed multi register get callback - * @kcontrol: mreg control - * @ucontrol: control element information - * - * Callback to get the value of a control that can span - * multiple codec registers which together forms a single - * signed value in a MSB/LSB manner. The control supports - * specifying total no of bits used to allow for bitfields - * across the multiple codec registers. - * - * Returns 0 for success. - */ -int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mreg_control *mc = - (struct soc_mreg_control *)kcontrol->private_value; - unsigned int regbase = mc->regbase; - unsigned int regcount = mc->regcount; - unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; - unsigned int regwmask = (1<<regwshift)-1; - unsigned int invert = mc->invert; - unsigned long mask = (1UL<<mc->nbits)-1; - long min = mc->min; - long max = mc->max; - long val = 0; - unsigned int regval; - unsigned int i; - int ret; - - for (i = 0; i < regcount; i++) { - ret = snd_soc_component_read(component, regbase+i, ®val); - if (ret) - return ret; - val |= (regval & regwmask) << (regwshift*(regcount-i-1)); - } - val &= mask; - if (min < 0 && val > max) - val |= ~mask; - if (invert) - val = max - val; - ucontrol->value.integer.value[0] = val; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_xr_sx); - -/** - * snd_soc_put_xr_sx - signed multi register get callback - * @kcontrol: mreg control - * @ucontrol: control element information - * - * Callback to set the value of a control that can span - * multiple codec registers which together forms a single - * signed value in a MSB/LSB manner. The control supports - * specifying total no of bits used to allow for bitfields - * across the multiple codec registers. - * - * Returns 0 for success. - */ -int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mreg_control *mc = - (struct soc_mreg_control *)kcontrol->private_value; - unsigned int regbase = mc->regbase; - unsigned int regcount = mc->regcount; - unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; - unsigned int regwmask = (1<<regwshift)-1; - unsigned int invert = mc->invert; - unsigned long mask = (1UL<<mc->nbits)-1; - long max = mc->max; - long val = ucontrol->value.integer.value[0]; - unsigned int i, regval, regmask; - int err; - - if (invert) - val = max - val; - val &= mask; - for (i = 0; i < regcount; i++) { - regval = (val >> (regwshift*(regcount-i-1))) & regwmask; - regmask = (mask >> (regwshift*(regcount-i-1))) & regwmask; - err = snd_soc_component_update_bits(component, regbase+i, - regmask, regval); - if (err < 0) - return err; - } - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_put_xr_sx); - -/** - * snd_soc_get_strobe - strobe get callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback get the value of a strobe mixer control. - * - * Returns 0 for success. - */ -int snd_soc_get_strobe(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - unsigned int mask = 1 << shift; - unsigned int invert = mc->invert != 0; - unsigned int val; - int ret; - - ret = snd_soc_component_read(component, reg, &val); - if (ret) - return ret; - - val &= mask; - - if (shift != 0 && val != 0) - val = val >> shift; - ucontrol->value.enumerated.item[0] = val ^ invert; - - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_get_strobe); - -/** - * snd_soc_put_strobe - strobe put callback - * @kcontrol: mixer control - * @ucontrol: control element information - * - * Callback strobe a register bit to high then low (or the inverse) - * in one pass of a single mixer enum control. - * - * Returns 1 for success. - */ -int snd_soc_put_strobe(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - unsigned int mask = 1 << shift; - unsigned int invert = mc->invert != 0; - unsigned int strobe = ucontrol->value.enumerated.item[0] != 0; - unsigned int val1 = (strobe ^ invert) ? mask : 0; - unsigned int val2 = (strobe ^ invert) ? 0 : mask; - int err; - - err = snd_soc_component_update_bits(component, reg, mask, val1); - if (err < 0) - return err; - - return snd_soc_component_update_bits(component, reg, mask, val2); -} -EXPORT_SYMBOL_GPL(snd_soc_put_strobe); - -/** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI * @clk_id: DAI specific clock ID @@ -3996,22 +2603,62 @@ static int snd_soc_component_initialize(struct snd_soc_component *component, return 0; } -static void snd_soc_component_init_regmap(struct snd_soc_component *component) +static void snd_soc_component_setup_regmap(struct snd_soc_component *component) { - if (!component->regmap) - component->regmap = dev_get_regmap(component->dev, NULL); - if (component->regmap) { - int val_bytes = regmap_get_val_bytes(component->regmap); - /* Errors are legitimate for non-integer byte multiples */ - if (val_bytes > 0) - component->val_bytes = val_bytes; - } + int val_bytes = regmap_get_val_bytes(component->regmap); + + /* Errors are legitimate for non-integer byte multiples */ + if (val_bytes > 0) + component->val_bytes = val_bytes; +} + +#ifdef CONFIG_REGMAP + +/** + * snd_soc_component_init_regmap() - Initialize regmap instance for the component + * @component: The component for which to initialize the regmap instance + * @regmap: The regmap instance that should be used by the component + * + * This function allows deferred assignment of the regmap instance that is + * associated with the component. Only use this if the regmap instance is not + * yet ready when the component is registered. The function must also be called + * before the first IO attempt of the component. + */ +void snd_soc_component_init_regmap(struct snd_soc_component *component, + struct regmap *regmap) +{ + component->regmap = regmap; + snd_soc_component_setup_regmap(component); +} +EXPORT_SYMBOL_GPL(snd_soc_component_init_regmap); + +/** + * snd_soc_component_exit_regmap() - De-initialize regmap instance for the component + * @component: The component for which to de-initialize the regmap instance + * + * Calls regmap_exit() on the regmap instance associated to the component and + * removes the regmap instance from the component. + * + * This function should only be used if snd_soc_component_init_regmap() was used + * to initialize the regmap instance. + */ +void snd_soc_component_exit_regmap(struct snd_soc_component *component) +{ + regmap_exit(component->regmap); + component->regmap = NULL; } +EXPORT_SYMBOL_GPL(snd_soc_component_exit_regmap); + +#endif static void snd_soc_component_add_unlocked(struct snd_soc_component *component) { - if (!component->write && !component->read) - snd_soc_component_init_regmap(component); + if (!component->write && !component->read) { + if (!component->regmap) + component->regmap = dev_get_regmap(component->dev, NULL); + if (component->regmap) + snd_soc_component_setup_regmap(component); + } list_add(&component->list, &component_list); } @@ -4362,7 +3009,6 @@ int snd_soc_register_codec(struct device *dev, codec->dev = dev; codec->driver = codec_drv; codec->component.val_bytes = codec_drv->reg_word_size; - mutex_init(&codec->mutex); #ifdef CONFIG_DEBUG_FS codec->component.init_debugfs = soc_init_codec_debugfs; @@ -4585,7 +3231,7 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, const char *propname) { struct device_node *np = card->dev->of_node; - int num_routes; + int num_routes, old_routes; struct snd_soc_dapm_route *routes; int i, ret; @@ -4603,7 +3249,9 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, return -EINVAL; } - routes = devm_kzalloc(card->dev, num_routes * sizeof(*routes), + old_routes = card->num_dapm_routes; + routes = devm_kzalloc(card->dev, + (old_routes + num_routes) * sizeof(*routes), GFP_KERNEL); if (!routes) { dev_err(card->dev, @@ -4611,9 +3259,11 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, return -EINVAL; } + memcpy(routes, card->dapm_routes, old_routes * sizeof(*routes)); + for (i = 0; i < num_routes; i++) { ret = of_property_read_string_index(np, propname, - 2 * i, &routes[i].sink); + 2 * i, &routes[old_routes + i].sink); if (ret) { dev_err(card->dev, "ASoC: Property '%s' index %d could not be read: %d\n", @@ -4621,7 +3271,7 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, return -EINVAL; } ret = of_property_read_string_index(np, propname, - (2 * i) + 1, &routes[i].source); + (2 * i) + 1, &routes[old_routes + i].source); if (ret) { dev_err(card->dev, "ASoC: Property '%s' index %d could not be read: %d\n", @@ -4630,7 +3280,7 @@ int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, } } - card->num_dapm_routes = num_routes; + card->num_dapm_routes += num_routes; card->dapm_routes = routes; return 0; @@ -4750,36 +3400,30 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt); -int snd_soc_of_get_dai_name(struct device_node *of_node, - const char **dai_name) +static int snd_soc_get_dai_name(struct of_phandle_args *args, + const char **dai_name) { struct snd_soc_component *pos; - struct of_phandle_args args; - int ret; - - ret = of_parse_phandle_with_args(of_node, "sound-dai", - "#sound-dai-cells", 0, &args); - if (ret) - return ret; - - ret = -EPROBE_DEFER; + int ret = -EPROBE_DEFER; mutex_lock(&client_mutex); list_for_each_entry(pos, &component_list, list) { - if (pos->dev->of_node != args.np) + if (pos->dev->of_node != args->np) continue; if (pos->driver->of_xlate_dai_name) { - ret = pos->driver->of_xlate_dai_name(pos, &args, dai_name); + ret = pos->driver->of_xlate_dai_name(pos, + args, + dai_name); } else { int id = -1; - switch (args.args_count) { + switch (args->args_count) { case 0: id = 0; /* same as dai_drv[0] */ break; case 1: - id = args.args[0]; + id = args->args[0]; break; default: /* not supported */ @@ -4801,6 +3445,21 @@ int snd_soc_of_get_dai_name(struct device_node *of_node, break; } mutex_unlock(&client_mutex); + return ret; +} + +int snd_soc_of_get_dai_name(struct device_node *of_node, + const char **dai_name) +{ + struct of_phandle_args args; + int ret; + + ret = of_parse_phandle_with_args(of_node, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) + return ret; + + ret = snd_soc_get_dai_name(&args, dai_name); of_node_put(args.np); @@ -4808,6 +3467,77 @@ int snd_soc_of_get_dai_name(struct device_node *of_node, } EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name); +/* + * snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree + * @dev: Card device + * @of_node: Device node + * @dai_link: DAI link + * + * Builds an array of CODEC DAI components from the DAI link property + * 'sound-dai'. + * The array is set in the DAI link and the number of DAIs is set accordingly. + * The device nodes in the array (of_node) must be dereferenced by the caller. + * + * Returns 0 for success + */ +int snd_soc_of_get_dai_link_codecs(struct device *dev, + struct device_node *of_node, + struct snd_soc_dai_link *dai_link) +{ + struct of_phandle_args args; + struct snd_soc_dai_link_component *component; + char *name; + int index, num_codecs, ret; + + /* Count the number of CODECs */ + name = "sound-dai"; + num_codecs = of_count_phandle_with_args(of_node, name, + "#sound-dai-cells"); + if (num_codecs <= 0) { + if (num_codecs == -ENOENT) + dev_err(dev, "No 'sound-dai' property\n"); + else + dev_err(dev, "Bad phandle in 'sound-dai'\n"); + return num_codecs; + } + component = devm_kzalloc(dev, + sizeof *component * num_codecs, + GFP_KERNEL); + if (!component) + return -ENOMEM; + dai_link->codecs = component; + dai_link->num_codecs = num_codecs; + + /* Parse the list */ + for (index = 0, component = dai_link->codecs; + index < dai_link->num_codecs; + index++, component++) { + ret = of_parse_phandle_with_args(of_node, name, + "#sound-dai-cells", + index, &args); + if (ret) + goto err; + component->of_node = args.np; + ret = snd_soc_get_dai_name(&args, &component->dai_name); + if (ret < 0) + goto err; + } + return 0; +err: + for (index = 0, component = dai_link->codecs; + index < dai_link->num_codecs; + index++, component++) { + if (!component->of_node) + break; + of_node_put(component->of_node); + component->of_node = NULL; + } + dai_link->codecs = NULL; + dai_link->num_codecs = 0; + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_codecs); + static int __init snd_soc_init(void) { #ifdef CONFIG_DEBUG_FS diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c61cb9cedbc..c5136bb1f98 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -159,27 +159,135 @@ static void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason) } } -void dapm_mark_io_dirty(struct snd_soc_dapm_context *dapm) +/* + * dapm_widget_invalidate_input_paths() - Invalidate the cached number of input + * paths + * @w: The widget for which to invalidate the cached number of input paths + * + * The function resets the cached number of inputs for the specified widget and + * all widgets that can be reached via outgoing paths from the widget. + * + * This function must be called if the number of input paths for a widget might + * have changed. E.g. if the source state of a widget changes or a path is added + * or activated with the widget as the sink. + */ +static void dapm_widget_invalidate_input_paths(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_widget *sink; + struct snd_soc_dapm_path *p; + LIST_HEAD(list); + + dapm_assert_locked(w->dapm); + + if (w->inputs == -1) + return; + + w->inputs = -1; + list_add_tail(&w->work_list, &list); + + list_for_each_entry(w, &list, work_list) { + list_for_each_entry(p, &w->sinks, list_source) { + if (p->is_supply || p->weak || !p->connect) + continue; + sink = p->sink; + if (sink->inputs != -1) { + sink->inputs = -1; + list_add_tail(&sink->work_list, &list); + } + } + } +} + +/* + * dapm_widget_invalidate_output_paths() - Invalidate the cached number of + * output paths + * @w: The widget for which to invalidate the cached number of output paths + * + * Resets the cached number of outputs for the specified widget and all widgets + * that can be reached via incoming paths from the widget. + * + * This function must be called if the number of output paths for a widget might + * have changed. E.g. if the sink state of a widget changes or a path is added + * or activated with the widget as the source. + */ +static void dapm_widget_invalidate_output_paths(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_widget *source; + struct snd_soc_dapm_path *p; + LIST_HEAD(list); + + dapm_assert_locked(w->dapm); + + if (w->outputs == -1) + return; + + w->outputs = -1; + list_add_tail(&w->work_list, &list); + + list_for_each_entry(w, &list, work_list) { + list_for_each_entry(p, &w->sources, list_sink) { + if (p->is_supply || p->weak || !p->connect) + continue; + source = p->source; + if (source->outputs != -1) { + source->outputs = -1; + list_add_tail(&source->work_list, &list); + } + } + } +} + +/* + * dapm_path_invalidate() - Invalidates the cached number of inputs and outputs + * for the widgets connected to a path + * @p: The path to invalidate + * + * Resets the cached number of inputs for the sink of the path and the cached + * number of outputs for the source of the path. + * + * This function must be called when a path is added, removed or the connected + * state changes. + */ +static void dapm_path_invalidate(struct snd_soc_dapm_path *p) +{ + /* + * Weak paths or supply paths do not influence the number of input or + * output paths of their neighbors. + */ + if (p->weak || p->is_supply) + return; + + /* + * The number of connected endpoints is the sum of the number of + * connected endpoints of all neighbors. If a node with 0 connected + * endpoints is either connected or disconnected that sum won't change, + * so there is no need to re-check the path. + */ + if (p->source->inputs != 0) + dapm_widget_invalidate_input_paths(p->sink); + if (p->sink->outputs != 0) + dapm_widget_invalidate_output_paths(p->source); +} + +void dapm_mark_endpoints_dirty(struct snd_soc_card *card) { - struct snd_soc_card *card = dapm->card; struct snd_soc_dapm_widget *w; mutex_lock(&card->dapm_mutex); list_for_each_entry(w, &card->widgets, list) { - switch (w->id) { - case snd_soc_dapm_input: - case snd_soc_dapm_output: - dapm_mark_dirty(w, "Rechecking inputs and outputs"); - break; - default: - break; + if (w->is_sink || w->is_source) { + dapm_mark_dirty(w, "Rechecking endpoints"); + if (w->is_sink) + dapm_widget_invalidate_output_paths(w); + if (w->is_source) + dapm_widget_invalidate_input_paths(w); } } mutex_unlock(&card->dapm_mutex); } -EXPORT_SYMBOL_GPL(dapm_mark_io_dirty); +EXPORT_SYMBOL_GPL(dapm_mark_endpoints_dirty); /* create a new dapm widget */ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( @@ -386,8 +494,6 @@ static void dapm_reset(struct snd_soc_card *card) list_for_each_entry(w, &card->widgets, list) { w->new_power = w->power; w->power_checked = false; - w->inputs = -1; - w->outputs = -1; } } @@ -469,10 +575,9 @@ out: /* connect mux widget to its interconnecting audio paths */ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, - struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, - struct snd_soc_dapm_path *path, const char *control_name, - const struct snd_kcontrol_new *kcontrol) + struct snd_soc_dapm_path *path, const char *control_name) { + const struct snd_kcontrol_new *kcontrol = &path->sink->kcontrol_news[0]; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, item; int i; @@ -493,10 +598,7 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, for (i = 0; i < e->items; i++) { if (!(strcmp(control_name, e->texts[i]))) { - list_add(&path->list, &dapm->card->paths); - list_add(&path->list_sink, &dest->sources); - list_add(&path->list_source, &src->sinks); - path->name = (char*)e->texts[i]; + path->name = e->texts[i]; if (i == item) path->connect = 1; else @@ -509,11 +611,10 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, } /* set up initial codec paths */ -static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w, - struct snd_soc_dapm_path *p, int i) +static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i) { struct soc_mixer_control *mc = (struct soc_mixer_control *) - w->kcontrol_news[i].private_value; + p->sink->kcontrol_news[i].private_value; unsigned int reg = mc->reg; unsigned int shift = mc->shift; unsigned int max = mc->max; @@ -522,7 +623,7 @@ static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w, unsigned int val; if (reg != SND_SOC_NOPM) { - soc_dapm_read(w->dapm, reg, &val); + soc_dapm_read(p->sink->dapm, reg, &val); val = (val >> shift) & mask; if (invert) val = max - val; @@ -534,19 +635,15 @@ static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w, /* connect mixer widget to its interconnecting audio paths */ static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm, - struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, struct snd_soc_dapm_path *path, const char *control_name) { int i; /* search for mixer kcontrol */ - for (i = 0; i < dest->num_kcontrols; i++) { - if (!strcmp(control_name, dest->kcontrol_news[i].name)) { - list_add(&path->list, &dapm->card->paths); - list_add(&path->list_sink, &dest->sources); - list_add(&path->list_source, &src->sinks); - path->name = dest->kcontrol_news[i].name; - dapm_set_mixer_path_status(dest, path, i); + for (i = 0; i < path->sink->num_kcontrols; i++) { + if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) { + path->name = path->sink->kcontrol_news[i].name; + dapm_set_mixer_path_status(path, i); return 0; } } @@ -738,8 +835,10 @@ static int dapm_new_mux(struct snd_soc_dapm_widget *w) if (ret < 0) return ret; - list_for_each_entry(path, &w->sources, list_sink) - dapm_kcontrol_add_path(w->kcontrols[0], path); + list_for_each_entry(path, &w->sources, list_sink) { + if (path->name) + dapm_kcontrol_add_path(w->kcontrols[0], path); + } return 0; } @@ -754,34 +853,6 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w) return 0; } -/* reset 'walked' bit for each dapm path */ -static void dapm_clear_walk_output(struct snd_soc_dapm_context *dapm, - struct list_head *sink) -{ - struct snd_soc_dapm_path *p; - - list_for_each_entry(p, sink, list_source) { - if (p->walked) { - p->walked = 0; - dapm_clear_walk_output(dapm, &p->sink->sinks); - } - } -} - -static void dapm_clear_walk_input(struct snd_soc_dapm_context *dapm, - struct list_head *source) -{ - struct snd_soc_dapm_path *p; - - list_for_each_entry(p, source, list_sink) { - if (p->walked) { - p->walked = 0; - dapm_clear_walk_input(dapm, &p->source->sources); - } - } -} - - /* We implement power down on suspend by checking the power state of * the ALSA card - when we are suspending the ALSA state for the card * is set to D3. @@ -856,61 +927,23 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, DAPM_UPDATE_STAT(widget, path_checks); - switch (widget->id) { - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: - case snd_soc_dapm_clock_supply: - case snd_soc_dapm_kcontrol: - return 0; - default: - break; - } - - switch (widget->id) { - case snd_soc_dapm_adc: - case snd_soc_dapm_aif_out: - case snd_soc_dapm_dai_out: - if (widget->active) { - widget->outputs = snd_soc_dapm_suspend_check(widget); - return widget->outputs; - } - default: - break; - } - - if (widget->connected) { - /* connected pin ? */ - if (widget->id == snd_soc_dapm_output && !widget->ext) { - widget->outputs = snd_soc_dapm_suspend_check(widget); - return widget->outputs; - } - - /* connected jack or spk ? */ - if (widget->id == snd_soc_dapm_hp || - widget->id == snd_soc_dapm_spk || - (widget->id == snd_soc_dapm_line && - !list_empty(&widget->sources))) { - widget->outputs = snd_soc_dapm_suspend_check(widget); - return widget->outputs; - } + if (widget->is_sink && widget->connected) { + widget->outputs = snd_soc_dapm_suspend_check(widget); + return widget->outputs; } list_for_each_entry(path, &widget->sinks, list_source) { DAPM_UPDATE_STAT(widget, neighbour_checks); - if (path->weak) + if (path->weak || path->is_supply) continue; if (path->walking) return 1; - if (path->walked) - continue; - trace_snd_soc_dapm_output_path(widget, path); - if (path->sink && path->connect) { - path->walked = 1; + if (path->connect) { path->walking = 1; /* do we need to add this widget to the list ? */ @@ -952,73 +985,23 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, DAPM_UPDATE_STAT(widget, path_checks); - switch (widget->id) { - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: - case snd_soc_dapm_clock_supply: - case snd_soc_dapm_kcontrol: - return 0; - default: - break; - } - - /* active stream ? */ - switch (widget->id) { - case snd_soc_dapm_dac: - case snd_soc_dapm_aif_in: - case snd_soc_dapm_dai_in: - if (widget->active) { - widget->inputs = snd_soc_dapm_suspend_check(widget); - return widget->inputs; - } - default: - break; - } - - if (widget->connected) { - /* connected pin ? */ - if (widget->id == snd_soc_dapm_input && !widget->ext) { - widget->inputs = snd_soc_dapm_suspend_check(widget); - return widget->inputs; - } - - /* connected VMID/Bias for lower pops */ - if (widget->id == snd_soc_dapm_vmid) { - widget->inputs = snd_soc_dapm_suspend_check(widget); - return widget->inputs; - } - - /* connected jack ? */ - if (widget->id == snd_soc_dapm_mic || - (widget->id == snd_soc_dapm_line && - !list_empty(&widget->sinks))) { - widget->inputs = snd_soc_dapm_suspend_check(widget); - return widget->inputs; - } - - /* signal generator */ - if (widget->id == snd_soc_dapm_siggen) { - widget->inputs = snd_soc_dapm_suspend_check(widget); - return widget->inputs; - } + if (widget->is_source && widget->connected) { + widget->inputs = snd_soc_dapm_suspend_check(widget); + return widget->inputs; } list_for_each_entry(path, &widget->sources, list_sink) { DAPM_UPDATE_STAT(widget, neighbour_checks); - if (path->weak) + if (path->weak || path->is_supply) continue; if (path->walking) return 1; - if (path->walked) - continue; - trace_snd_soc_dapm_input_path(widget, path); - if (path->source && path->connect) { - path->walked = 1; + if (path->connect) { path->walking = 1; /* do we need to add this widget to the list ? */ @@ -1060,21 +1043,25 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget_list **list) { - struct snd_soc_card *card = dai->card; + struct snd_soc_card *card = dai->component->card; + struct snd_soc_dapm_widget *w; int paths; mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); - dapm_reset(card); - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For is_connected_{output,input}_ep fully discover the graph we need + * to reset the cached number of inputs and outputs. + */ + list_for_each_entry(w, &card->widgets, list) { + w->inputs = -1; + w->outputs = -1; + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) paths = is_connected_output_ep(dai->playback_widget, list); - dapm_clear_walk_output(&card->dapm, - &dai->playback_widget->sinks); - } else { + else paths = is_connected_input_ep(dai->capture_widget, list); - dapm_clear_walk_input(&card->dapm, - &dai->capture_widget->sources); - } trace_snd_soc_dapm_connected(paths, stream); mutex_unlock(&card->dapm_mutex); @@ -1163,44 +1150,10 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); in = is_connected_input_ep(w, NULL); - dapm_clear_walk_input(w->dapm, &w->sources); out = is_connected_output_ep(w, NULL); - dapm_clear_walk_output(w->dapm, &w->sinks); return out != 0 && in != 0; } -/* Check to see if an ADC has power */ -static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) -{ - int in; - - DAPM_UPDATE_STAT(w, power_checks); - - if (w->active) { - in = is_connected_input_ep(w, NULL); - dapm_clear_walk_input(w->dapm, &w->sources); - return in != 0; - } else { - return dapm_generic_check_power(w); - } -} - -/* Check to see if a DAC has power */ -static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) -{ - int out; - - DAPM_UPDATE_STAT(w, power_checks); - - if (w->active) { - out = is_connected_output_ep(w, NULL); - dapm_clear_walk_output(w->dapm, &w->sinks); - return out != 0; - } else { - return dapm_generic_check_power(w); - } -} - /* Check to see if a power supply is needed */ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) { @@ -1219,9 +1172,6 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) !path->connected(path->source, path->sink)) continue; - if (!path->sink) - continue; - if (dapm_widget_power_check(path->sink)) return 1; } @@ -1636,27 +1586,14 @@ static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, /* If we changed our power state perhaps our neigbours changed * also. */ - list_for_each_entry(path, &w->sources, list_sink) { - if (path->source) { - dapm_widget_set_peer_power(path->source, power, + list_for_each_entry(path, &w->sources, list_sink) + dapm_widget_set_peer_power(path->source, power, path->connect); + + /* Supplies can't affect their outputs, only their inputs */ + if (!w->is_supply) { + list_for_each_entry(path, &w->sinks, list_source) + dapm_widget_set_peer_power(path->sink, power, path->connect); - } - } - switch (w->id) { - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: - case snd_soc_dapm_clock_supply: - case snd_soc_dapm_kcontrol: - /* Supplies can't affect their outputs, only their inputs */ - break; - default: - list_for_each_entry(path, &w->sinks, list_source) { - if (path->sink) { - dapm_widget_set_peer_power(path->sink, power, - path->connect); - } - } - break; } if (power) @@ -1863,10 +1800,14 @@ static ssize_t dapm_widget_power_read_file(struct file *file, if (!buf) return -ENOMEM; - in = is_connected_input_ep(w, NULL); - dapm_clear_walk_input(w->dapm, &w->sources); - out = is_connected_output_ep(w, NULL); - dapm_clear_walk_output(w->dapm, &w->sinks); + /* Supply widgets are not handled by is_connected_{input,output}_ep() */ + if (w->is_supply) { + in = 0; + out = 0; + } else { + in = is_connected_input_ep(w, NULL); + out = is_connected_output_ep(w, NULL); + } ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", w->name, w->power ? "On" : "Off", @@ -2011,32 +1952,45 @@ static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) #endif +/* + * soc_dapm_connect_path() - Connects or disconnects a path + * @path: The path to update + * @connect: The new connect state of the path. True if the path is connected, + * false if it is disconneted. + * @reason: The reason why the path changed (for debugging only) + */ +static void soc_dapm_connect_path(struct snd_soc_dapm_path *path, + bool connect, const char *reason) +{ + if (path->connect == connect) + return; + + path->connect = connect; + dapm_mark_dirty(path->source, reason); + dapm_mark_dirty(path->sink, reason); + dapm_path_invalidate(path); +} + /* test and update the power status of a mux widget */ static int soc_dapm_mux_update_power(struct snd_soc_card *card, struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e) { struct snd_soc_dapm_path *path; int found = 0; + bool connect; lockdep_assert_held(&card->dapm_mutex); /* find dapm widget path assoc with kcontrol */ dapm_kcontrol_for_each_path(path, kcontrol) { - if (!path->name || !e->texts[mux]) - continue; - found = 1; /* we now need to match the string in the enum to the path */ - if (!(strcmp(path->name, e->texts[mux]))) { - path->connect = 1; /* new connection */ - dapm_mark_dirty(path->source, "mux connection"); - } else { - if (path->connect) - dapm_mark_dirty(path->source, - "mux disconnection"); - path->connect = 0; /* old connection must be powered down */ - } - dapm_mark_dirty(path->sink, "mux change"); + if (!(strcmp(path->name, e->texts[mux]))) + connect = true; + else + connect = false; + + soc_dapm_connect_path(path, connect, "mux update"); } if (found) @@ -2075,9 +2029,7 @@ static int soc_dapm_mixer_update_power(struct snd_soc_card *card, /* find dapm widget path assoc with kcontrol */ dapm_kcontrol_for_each_path(path, kcontrol) { found = 1; - path->connect = connect; - dapm_mark_dirty(path->source, "mixer connection"); - dapm_mark_dirty(path->sink, "mixer update"); + soc_dapm_connect_path(path, connect, "mixer update"); } if (found) @@ -2255,8 +2207,11 @@ static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, return -EINVAL; } - if (w->connected != status) + if (w->connected != status) { dapm_mark_dirty(w, "pin configuration"); + dapm_widget_invalidate_input_paths(w); + dapm_widget_invalidate_output_paths(w); + } w->connected = status; if (status == 0) @@ -2309,6 +2264,53 @@ int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm) } EXPORT_SYMBOL_GPL(snd_soc_dapm_sync); +/* + * dapm_update_widget_flags() - Re-compute widget sink and source flags + * @w: The widget for which to update the flags + * + * Some widgets have a dynamic category which depends on which neighbors they + * are connected to. This function update the category for these widgets. + * + * This function must be called whenever a path is added or removed to a widget. + */ +static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + + switch (w->id) { + case snd_soc_dapm_input: + w->is_source = 1; + list_for_each_entry(p, &w->sources, list_sink) { + if (p->source->id == snd_soc_dapm_micbias || + p->source->id == snd_soc_dapm_mic || + p->source->id == snd_soc_dapm_line || + p->source->id == snd_soc_dapm_output) { + w->is_source = 0; + break; + } + } + break; + case snd_soc_dapm_output: + w->is_sink = 1; + list_for_each_entry(p, &w->sinks, list_source) { + if (p->sink->id == snd_soc_dapm_spk || + p->sink->id == snd_soc_dapm_hp || + p->sink->id == snd_soc_dapm_line || + p->sink->id == snd_soc_dapm_input) { + w->is_sink = 0; + break; + } + } + break; + case snd_soc_dapm_line: + w->is_sink = !list_empty(&w->sources); + w->is_source = !list_empty(&w->sinks); + break; + default: + break; + } +} + static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink, const char *control, @@ -2318,6 +2320,27 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_path *path; int ret; + if (wsink->is_supply && !wsource->is_supply) { + dev_err(dapm->dev, + "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n", + wsource->name, wsink->name); + return -EINVAL; + } + + if (connected && !wsource->is_supply) { + dev_err(dapm->dev, + "connected() callback only supported for supply widgets (%s -> %s)\n", + wsource->name, wsink->name); + return -EINVAL; + } + + if (wsource->is_supply && control) { + dev_err(dapm->dev, + "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n", + wsource->name, control, wsink->name); + return -EINVAL; + } + path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL); if (!path) return -ENOMEM; @@ -2330,85 +2353,49 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, INIT_LIST_HEAD(&path->list_source); INIT_LIST_HEAD(&path->list_sink); - /* check for external widgets */ - if (wsink->id == snd_soc_dapm_input) { - if (wsource->id == snd_soc_dapm_micbias || - wsource->id == snd_soc_dapm_mic || - wsource->id == snd_soc_dapm_line || - wsource->id == snd_soc_dapm_output) - wsink->ext = 1; - } - if (wsource->id == snd_soc_dapm_output) { - if (wsink->id == snd_soc_dapm_spk || - wsink->id == snd_soc_dapm_hp || - wsink->id == snd_soc_dapm_line || - wsink->id == snd_soc_dapm_input) - wsource->ext = 1; - } - - dapm_mark_dirty(wsource, "Route added"); - dapm_mark_dirty(wsink, "Route added"); + if (wsource->is_supply || wsink->is_supply) + path->is_supply = 1; /* connect static paths */ if (control == NULL) { - list_add(&path->list, &dapm->card->paths); - list_add(&path->list_sink, &wsink->sources); - list_add(&path->list_source, &wsource->sinks); path->connect = 1; - return 0; - } - - /* connect dynamic paths */ - switch (wsink->id) { - case snd_soc_dapm_adc: - case snd_soc_dapm_dac: - case snd_soc_dapm_pga: - case snd_soc_dapm_out_drv: - case snd_soc_dapm_input: - case snd_soc_dapm_output: - case snd_soc_dapm_siggen: - case snd_soc_dapm_micbias: - case snd_soc_dapm_vmid: - case snd_soc_dapm_pre: - case snd_soc_dapm_post: - case snd_soc_dapm_supply: - case snd_soc_dapm_regulator_supply: - case snd_soc_dapm_clock_supply: - case snd_soc_dapm_aif_in: - case snd_soc_dapm_aif_out: - case snd_soc_dapm_dai_in: - case snd_soc_dapm_dai_out: - case snd_soc_dapm_dai_link: - case snd_soc_dapm_kcontrol: - list_add(&path->list, &dapm->card->paths); - list_add(&path->list_sink, &wsink->sources); - list_add(&path->list_source, &wsource->sinks); - path->connect = 1; - return 0; - case snd_soc_dapm_mux: - ret = dapm_connect_mux(dapm, wsource, wsink, path, control, - &wsink->kcontrol_news[0]); - if (ret != 0) - goto err; - break; - case snd_soc_dapm_switch: - case snd_soc_dapm_mixer: - case snd_soc_dapm_mixer_named_ctl: - ret = dapm_connect_mixer(dapm, wsource, wsink, path, control); - if (ret != 0) + } else { + /* connect dynamic paths */ + switch (wsink->id) { + case snd_soc_dapm_mux: + ret = dapm_connect_mux(dapm, path, control); + if (ret != 0) + goto err; + break; + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + ret = dapm_connect_mixer(dapm, path, control); + if (ret != 0) + goto err; + break; + default: + dev_err(dapm->dev, + "Control not supported for path %s -> [%s] -> %s\n", + wsource->name, control, wsink->name); + ret = -EINVAL; goto err; - break; - case snd_soc_dapm_hp: - case snd_soc_dapm_mic: - case snd_soc_dapm_line: - case snd_soc_dapm_spk: - list_add(&path->list, &dapm->card->paths); - list_add(&path->list_sink, &wsink->sources); - list_add(&path->list_source, &wsource->sinks); - path->connect = 0; - return 0; + } } + list_add(&path->list, &dapm->card->paths); + list_add(&path->list_sink, &wsink->sources); + list_add(&path->list_source, &wsource->sinks); + + dapm_update_widget_flags(wsource); + dapm_update_widget_flags(wsink); + + dapm_mark_dirty(wsource, "Route added"); + dapm_mark_dirty(wsink, "Route added"); + + if (dapm->card->instantiated && path->connect) + dapm_path_invalidate(path); + return 0; err: kfree(path); @@ -2489,6 +2476,7 @@ err: static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_route *route) { + struct snd_soc_dapm_widget *wsource, *wsink; struct snd_soc_dapm_path *path, *p; const char *sink; const char *source; @@ -2526,10 +2514,19 @@ static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm, } if (path) { - dapm_mark_dirty(path->source, "Route removed"); - dapm_mark_dirty(path->sink, "Route removed"); + wsource = path->source; + wsink = path->sink; + + dapm_mark_dirty(wsource, "Route removed"); + dapm_mark_dirty(wsink, "Route removed"); + if (path->connect) + dapm_path_invalidate(path); dapm_free_path(path); + + /* Update any path related flags */ + dapm_update_widget_flags(wsource); + dapm_update_widget_flags(wsink); } else { dev_warn(dapm->dev, "ASoC: Route %s->%s does not exist\n", source, sink); @@ -3087,40 +3084,44 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, } switch (w->id) { - case snd_soc_dapm_switch: - case snd_soc_dapm_mixer: - case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_mic: + case snd_soc_dapm_input: + w->is_source = 1; w->power_check = dapm_generic_check_power; break; - case snd_soc_dapm_mux: + case snd_soc_dapm_spk: + case snd_soc_dapm_hp: + case snd_soc_dapm_output: + w->is_sink = 1; w->power_check = dapm_generic_check_power; break; - case snd_soc_dapm_dai_out: - w->power_check = dapm_adc_check_power; - break; - case snd_soc_dapm_dai_in: - w->power_check = dapm_dac_check_power; + case snd_soc_dapm_vmid: + case snd_soc_dapm_siggen: + w->is_source = 1; + w->power_check = dapm_always_on_check_power; break; + case snd_soc_dapm_mux: + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: - case snd_soc_dapm_input: - case snd_soc_dapm_output: case snd_soc_dapm_micbias: - case snd_soc_dapm_spk: - case snd_soc_dapm_hp: - case snd_soc_dapm_mic: case snd_soc_dapm_line: case snd_soc_dapm_dai_link: + case snd_soc_dapm_dai_out: + case snd_soc_dapm_dai_in: w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_clock_supply: case snd_soc_dapm_kcontrol: + w->is_supply = 1; w->power_check = dapm_supply_check_power; break; default: @@ -3137,6 +3138,9 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, INIT_LIST_HEAD(&w->dirty); list_add(&w->list, &dapm->card->widgets); + w->inputs = -1; + w->outputs = -1; + /* machine layer set ups unconnected pins and insertions */ w->connected = 1; return w; @@ -3484,6 +3488,14 @@ static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream, case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: break; } + + if (w->id == snd_soc_dapm_dai_in) { + w->is_source = w->active; + dapm_widget_invalidate_input_paths(w); + } else { + w->is_sink = w->active; + dapm_widget_invalidate_output_paths(w); + } } } @@ -3610,7 +3622,15 @@ int snd_soc_dapm_force_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, } dev_dbg(w->dapm->dev, "ASoC: force enable pin %s\n", pin); - w->connected = 1; + if (!w->connected) { + /* + * w->force does not affect the number of input or output paths, + * so we only have to recheck if w->connected is changed + */ + dapm_widget_invalidate_input_paths(w); + dapm_widget_invalidate_output_paths(w); + w->connected = 1; + } w->force = 1; dapm_mark_dirty(w, "force enable"); @@ -3788,35 +3808,54 @@ int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm, } EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); +/** + * dapm_is_external_path() - Checks if a path is a external path + * @card: The card the path belongs to + * @path: The path to check + * + * Returns true if the path is either between two different DAPM contexts or + * between two external pins of the same DAPM context. Otherwise returns + * false. + */ +static bool dapm_is_external_path(struct snd_soc_card *card, + struct snd_soc_dapm_path *path) +{ + dev_dbg(card->dev, + "... Path %s(id:%d dapm:%p) - %s(id:%d dapm:%p)\n", + path->source->name, path->source->id, path->source->dapm, + path->sink->name, path->sink->id, path->sink->dapm); + + /* Connection between two different DAPM contexts */ + if (path->source->dapm != path->sink->dapm) + return true; + + /* Loopback connection from external pin to external pin */ + if (path->sink->id == snd_soc_dapm_input) { + switch (path->source->id) { + case snd_soc_dapm_output: + case snd_soc_dapm_micbias: + return true; + default: + break; + } + } + + return false; +} + static bool snd_soc_dapm_widget_in_card_paths(struct snd_soc_card *card, struct snd_soc_dapm_widget *w) { struct snd_soc_dapm_path *p; - list_for_each_entry(p, &card->paths, list) { - if ((p->source == w) || (p->sink == w)) { - dev_dbg(card->dev, - "... Path %s(id:%d dapm:%p) - %s(id:%d dapm:%p)\n", - p->source->name, p->source->id, p->source->dapm, - p->sink->name, p->sink->id, p->sink->dapm); + list_for_each_entry(p, &w->sources, list_sink) { + if (dapm_is_external_path(card, p)) + return true; + } - /* Connected to something other than the codec */ - if (p->source->dapm != p->sink->dapm) - return true; - /* - * Loopback connection from codec external pin to - * codec external pin - */ - if (p->sink->id == snd_soc_dapm_input) { - switch (p->source->id) { - case snd_soc_dapm_output: - case snd_soc_dapm_micbias: - return true; - default: - break; - } - } - } + list_for_each_entry(p, &w->sinks, list_source) { + if (dapm_is_external_path(card, p)) + return true; } return false; diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index ab47fea997a..4380dcc064a 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -116,7 +116,7 @@ EXPORT_SYMBOL_GPL(snd_soc_jack_report); * * @jack: ASoC jack * @count: Number of zones - * @zone: Array of zones + * @zones: Array of zones * * After this function has been called the zones specified in the * array will be associated with the jack. @@ -309,7 +309,7 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, /* GPIO descriptor */ gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, gpios[i].name, - gpios[i].idx); + gpios[i].idx, GPIOD_IN); if (IS_ERR(gpios[i].desc)) { ret = PTR_ERR(gpios[i].desc); dev_err(gpios[i].gpiod_dev, @@ -327,17 +327,14 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, goto undo; } - ret = gpio_request(gpios[i].gpio, gpios[i].name); + ret = gpio_request_one(gpios[i].gpio, GPIOF_IN, + gpios[i].name); if (ret) goto undo; gpios[i].desc = gpio_to_desc(gpios[i].gpio); } - ret = gpiod_direction_input(gpios[i].desc); - if (ret) - goto err; - INIT_DELAYED_WORK(&gpios[i].work, gpio_work); gpios[i].jack = jack; diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c new file mode 100644 index 00000000000..100d92b5b77 --- /dev/null +++ b/sound/soc/soc-ops.c @@ -0,0 +1,952 @@ +/* + * soc-ops.c -- Generic ASoC operations + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * Copyright (C) 2010 Slimlogic Ltd. + * Copyright (C) 2010 Texas Instruments Inc. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * with code, comments and ideas from :- + * Richard Purdie <richard@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dpcm.h> +#include <sound/initval.h> + +/** + * snd_soc_info_enum_double - enumerated double mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a double enumerated + * mixer control. + * + * Returns 0 for success. + */ +int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2, + e->items, e->texts); +} +EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); + +/** + * snd_soc_get_enum_double - enumerated double mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a double enumerated mixer. + * + * Returns 0 for success. + */ +int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val, item; + unsigned int reg_val; + int ret; + + ret = snd_soc_component_read(component, e->reg, ®_val); + if (ret) + return ret; + val = (reg_val >> e->shift_l) & e->mask; + item = snd_soc_enum_val_to_item(e, val); + ucontrol->value.enumerated.item[0] = item; + if (e->shift_l != e->shift_r) { + val = (reg_val >> e->shift_l) & e->mask; + item = snd_soc_enum_val_to_item(e, val); + ucontrol->value.enumerated.item[1] = item; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_enum_double); + +/** + * snd_soc_put_enum_double - enumerated double mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a double enumerated mixer. + * + * Returns 0 for success. + */ +int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val; + unsigned int mask; + + if (item[0] >= e->items) + return -EINVAL; + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { + if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; + } + + return snd_soc_component_update_bits(component, e->reg, mask, val); +} +EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); + +/** + * snd_soc_read_signed - Read a codec register and interprete as signed value + * @component: component + * @reg: Register to read + * @mask: Mask to use after shifting the register value + * @shift: Right shift of register value + * @sign_bit: Bit that describes if a number is negative or not. + * @signed_val: Pointer to where the read value should be stored + * + * This functions reads a codec register. The register value is shifted right + * by 'shift' bits and masked with the given 'mask'. Afterwards it translates + * the given registervalue into a signed integer if sign_bit is non-zero. + * + * Returns 0 on sucess, otherwise an error value + */ +static int snd_soc_read_signed(struct snd_soc_component *component, + unsigned int reg, unsigned int mask, unsigned int shift, + unsigned int sign_bit, int *signed_val) +{ + int ret; + unsigned int val; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return ret; + + val = (val >> shift) & mask; + + if (!sign_bit) { + *signed_val = val; + return 0; + } + + /* non-negative number */ + if (!(val & BIT(sign_bit))) { + *signed_val = val; + return 0; + } + + ret = val; + + /* + * The register most probably does not contain a full-sized int. + * Instead we have an arbitrary number of bits in a signed + * representation which has to be translated into a full-sized int. + * This is done by filling up all bits above the sign-bit. + */ + ret |= ~((int)(BIT(sign_bit) - 1)); + + *signed_val = ret; + + return 0; +} + +/** + * snd_soc_info_volsw - single mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a single mixer control, or a double + * mixer control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int platform_max; + + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = platform_max - mc->min; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw); + +/** + * snd_soc_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + int sign_bit = mc->sign_bit; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + int val; + int ret; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = val - min; + if (invert) + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + + if (snd_soc_volsw_is_stereo(mc)) { + if (reg == reg2) + ret = snd_soc_read_signed(component, reg, mask, rshift, + sign_bit, &val); + else + ret = snd_soc_read_signed(component, reg2, mask, shift, + sign_bit, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[1] = val - min; + if (invert) + ucontrol->value.integer.value[1] = + max - ucontrol->value.integer.value[1]; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw); + +/** + * snd_soc_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + unsigned int sign_bit = mc->sign_bit; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + int err; + bool type_2r = false; + unsigned int val2 = 0; + unsigned int val, val_mask; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ((ucontrol->value.integer.value[0] + min) & mask); + if (invert) + val = max - val; + val_mask = mask << shift; + val = val << shift; + if (snd_soc_volsw_is_stereo(mc)) { + val2 = ((ucontrol->value.integer.value[1] + min) & mask); + if (invert) + val2 = max - val2; + if (reg == reg2) { + val_mask |= mask << rshift; + val |= val2 << rshift; + } else { + val2 = val2 << shift; + type_2r = true; + } + } + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + + if (type_2r) + err = snd_soc_component_update_bits(component, reg2, val_mask, + val2); + + return err; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw); + +/** + * snd_soc_get_volsw_sx - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + int mask = (1 << (fls(min + max) - 1)) - 1; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return ret; + + ucontrol->value.integer.value[0] = ((val >> shift) - min) & mask; + + if (snd_soc_volsw_is_stereo(mc)) { + ret = snd_soc_component_read(component, reg2, &val); + if (ret < 0) + return ret; + + val = ((val >> rshift) - min) & mask; + ucontrol->value.integer.value[1] = val; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx); + +/** + * snd_soc_put_volsw_sx - double mixer set callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to set the value of a double mixer control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + int mask = (1 << (fls(min + max) - 1)) - 1; + int err = 0; + unsigned int val, val_mask, val2 = 0; + + val_mask = mask << shift; + val = (ucontrol->value.integer.value[0] + min) & mask; + val = val << shift; + + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + + if (snd_soc_volsw_is_stereo(mc)) { + val_mask = mask << rshift; + val2 = (ucontrol->value.integer.value[1] + min) & mask; + val2 = val2 << rshift; + + err = snd_soc_component_update_bits(component, reg2, val_mask, + val2); + } + return err; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); + +/** + * snd_soc_info_volsw_range - single mixer info callback with range. + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information, within a range, about a single + * mixer control. + * + * returns 0 for success. + */ +int snd_soc_info_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int platform_max; + int min = mc->min; + + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = platform_max - min; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_range); + +/** + * snd_soc_put_volsw_range - single mixer put value callback with range. + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value, within a range, for a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int rreg = mc->rreg; + unsigned int shift = mc->shift; + int min = mc->min; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val, val_mask; + int ret; + + if (invert) + val = (max - ucontrol->value.integer.value[0]) & mask; + else + val = ((ucontrol->value.integer.value[0] + min) & mask); + val_mask = mask << shift; + val = val << shift; + + ret = snd_soc_component_update_bits(component, reg, val_mask, val); + if (ret < 0) + return ret; + + if (snd_soc_volsw_is_stereo(mc)) { + if (invert) + val = (max - ucontrol->value.integer.value[1]) & mask; + else + val = ((ucontrol->value.integer.value[1] + min) & mask); + val_mask = mask << shift; + val = val << shift; + + ret = snd_soc_component_update_bits(component, rreg, val_mask, + val); + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_range); + +/** + * snd_soc_get_volsw_range - single mixer get callback with range + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value, within a range, of a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int rreg = mc->rreg; + unsigned int shift = mc->shift; + int min = mc->min; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + else + ucontrol->value.integer.value[0] = + ucontrol->value.integer.value[0] - min; + + if (snd_soc_volsw_is_stereo(mc)) { + ret = snd_soc_component_read(component, rreg, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[1] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[1] = + max - ucontrol->value.integer.value[1]; + else + ucontrol->value.integer.value[1] = + ucontrol->value.integer.value[1] - min; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range); + +/** + * snd_soc_limit_volume - Set new limit to an existing volume control. + * + * @codec: where to look for the control + * @name: Name of the control + * @max: new maximum limit + * + * Return 0 for success, else error. + */ +int snd_soc_limit_volume(struct snd_soc_codec *codec, + const char *name, int max) +{ + struct snd_card *card = codec->component.card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + int found = 0; + int ret = -EINVAL; + + /* Sanity check for name and max */ + if (unlikely(!name || max <= 0)) + return -EINVAL; + + list_for_each_entry(kctl, &card->controls, list) { + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) { + found = 1; + break; + } + } + if (found) { + mc = (struct soc_mixer_control *)kctl->private_value; + if (max <= mc->max) { + mc->platform_max = max; + ret = 0; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_limit_volume); + +int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = params->num_regs * component->val_bytes; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_info); + +int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + int ret; + + if (component->regmap) + ret = regmap_raw_read(component->regmap, params->base, + ucontrol->value.bytes.data, + params->num_regs * component->val_bytes); + else + ret = -EINVAL; + + /* Hide any masked bytes to ensure consistent data reporting */ + if (ret == 0 && params->mask) { + switch (component->val_bytes) { + case 1: + ucontrol->value.bytes.data[0] &= ~params->mask; + break; + case 2: + ((u16 *)(&ucontrol->value.bytes.data))[0] + &= cpu_to_be16(~params->mask); + break; + case 4: + ((u32 *)(&ucontrol->value.bytes.data))[0] + &= cpu_to_be32(~params->mask); + break; + default: + return -EINVAL; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_get); + +int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + int ret, len; + unsigned int val, mask; + void *data; + + if (!component->regmap || !params->num_regs) + return -EINVAL; + + len = params->num_regs * component->val_bytes; + + data = kmemdup(ucontrol->value.bytes.data, len, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + /* + * If we've got a mask then we need to preserve the register + * bits. We shouldn't modify the incoming data so take a + * copy. + */ + if (params->mask) { + ret = regmap_read(component->regmap, params->base, &val); + if (ret != 0) + goto out; + + val &= params->mask; + + switch (component->val_bytes) { + case 1: + ((u8 *)data)[0] &= ~params->mask; + ((u8 *)data)[0] |= val; + break; + case 2: + mask = ~params->mask; + ret = regmap_parse_val(component->regmap, + &mask, &mask); + if (ret != 0) + goto out; + + ((u16 *)data)[0] &= mask; + + ret = regmap_parse_val(component->regmap, + &val, &val); + if (ret != 0) + goto out; + + ((u16 *)data)[0] |= val; + break; + case 4: + mask = ~params->mask; + ret = regmap_parse_val(component->regmap, + &mask, &mask); + if (ret != 0) + goto out; + + ((u32 *)data)[0] &= mask; + + ret = regmap_parse_val(component->regmap, + &val, &val); + if (ret != 0) + goto out; + + ((u32 *)data)[0] |= val; + break; + default: + ret = -EINVAL; + goto out; + } + } + + ret = regmap_raw_write(component->regmap, params->base, + data, len); + +out: + kfree(data); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_put); + +int snd_soc_bytes_info_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *ucontrol) +{ + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES; + ucontrol->count = params->max; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_info_ext); + +int snd_soc_bytes_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + unsigned int count = size < params->max ? size : params->max; + int ret = -ENXIO; + + switch (op_flag) { + case SNDRV_CTL_TLV_OP_READ: + if (params->get) + ret = params->get(tlv, count); + break; + case SNDRV_CTL_TLV_OP_WRITE: + if (params->put) + ret = params->put(tlv, count); + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_tlv_callback); + +/** + * snd_soc_info_xr_sx - signed multi register info callback + * @kcontrol: mreg control + * @uinfo: control element information + * + * Callback to provide information of a control that can + * span multiple codec registers which together + * forms a single signed value in a MSB/LSB manner. + * + * Returns 0 for success. + */ +int snd_soc_info_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = mc->min; + uinfo->value.integer.max = mc->max; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_xr_sx); + +/** + * snd_soc_get_xr_sx - signed multi register get callback + * @kcontrol: mreg control + * @ucontrol: control element information + * + * Callback to get the value of a control that can span + * multiple codec registers which together forms a single + * signed value in a MSB/LSB manner. The control supports + * specifying total no of bits used to allow for bitfields + * across the multiple codec registers. + * + * Returns 0 for success. + */ +int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwmask = (1<<regwshift)-1; + unsigned int invert = mc->invert; + unsigned long mask = (1UL<<mc->nbits)-1; + long min = mc->min; + long max = mc->max; + long val = 0; + unsigned int regval; + unsigned int i; + int ret; + + for (i = 0; i < regcount; i++) { + ret = snd_soc_component_read(component, regbase+i, ®val); + if (ret) + return ret; + val |= (regval & regwmask) << (regwshift*(regcount-i-1)); + } + val &= mask; + if (min < 0 && val > max) + val |= ~mask; + if (invert) + val = max - val; + ucontrol->value.integer.value[0] = val; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_xr_sx); + +/** + * snd_soc_put_xr_sx - signed multi register get callback + * @kcontrol: mreg control + * @ucontrol: control element information + * + * Callback to set the value of a control that can span + * multiple codec registers which together forms a single + * signed value in a MSB/LSB manner. The control supports + * specifying total no of bits used to allow for bitfields + * across the multiple codec registers. + * + * Returns 0 for success. + */ +int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwmask = (1<<regwshift)-1; + unsigned int invert = mc->invert; + unsigned long mask = (1UL<<mc->nbits)-1; + long max = mc->max; + long val = ucontrol->value.integer.value[0]; + unsigned int i, regval, regmask; + int err; + + if (invert) + val = max - val; + val &= mask; + for (i = 0; i < regcount; i++) { + regval = (val >> (regwshift*(regcount-i-1))) & regwmask; + regmask = (mask >> (regwshift*(regcount-i-1))) & regwmask; + err = snd_soc_component_update_bits(component, regbase+i, + regmask, regval); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_put_xr_sx); + +/** + * snd_soc_get_strobe - strobe get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback get the value of a strobe mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = 1 << shift; + unsigned int invert = mc->invert != 0; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret) + return ret; + + val &= mask; + + if (shift != 0 && val != 0) + val = val >> shift; + ucontrol->value.enumerated.item[0] = val ^ invert; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_strobe); + +/** + * snd_soc_put_strobe - strobe put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback strobe a register bit to high then low (or the inverse) + * in one pass of a single mixer enum control. + * + * Returns 1 for success. + */ +int snd_soc_put_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = 1 << shift; + unsigned int invert = mc->invert != 0; + unsigned int strobe = ucontrol->value.enumerated.item[0] != 0; + unsigned int val1 = (strobe ^ invert) ? mask : 0; + unsigned int val2 = (strobe ^ invert) ? 0 : mask; + int err; + + err = snd_soc_component_update_bits(component, reg, mask, val1); + if (err < 0) + return err; + + return snd_soc_component_update_bits(component, reg, mask, val2); +} +EXPORT_SYMBOL_GPL(snd_soc_put_strobe); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 002311afdea..eb87d96e2cf 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -654,6 +654,8 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) codec_dai->rate = 0; } + snd_soc_dai_digital_mute(cpu_dai, 1, substream->stream); + if (cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai); @@ -772,6 +774,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) for (i = 0; i < rtd->num_codecs; i++) snd_soc_dai_digital_mute(rtd->codec_dais[i], 0, substream->stream); + snd_soc_dai_digital_mute(cpu_dai, 0, substream->stream); out: mutex_unlock(&rtd->pcm_mutex); @@ -1522,13 +1525,36 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture); } +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); + +/* Set FE's runtime_update state; the state is protected via PCM stream lock + * for avoiding the race with trigger callback. + * If the state is unset and a trigger is pending while the previous operation, + * process the pending trigger action here. + */ +static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe, + int stream, enum snd_soc_dpcm_update state) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + + snd_pcm_stream_lock_irq(substream); + if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) { + dpcm_fe_dai_do_trigger(substream, + fe->dpcm[stream].trigger_pending - 1); + fe->dpcm[stream].trigger_pending = 0; + } + fe->dpcm[stream].runtime_update = state; + snd_pcm_stream_unlock_irq(substream); +} + static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) { struct snd_soc_pcm_runtime *fe = fe_substream->private_data; struct snd_pcm_runtime *runtime = fe_substream->runtime; int stream = fe_substream->stream, ret = 0; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); ret = dpcm_be_dai_startup(fe, fe_substream->stream); if (ret < 0) { @@ -1550,13 +1576,13 @@ static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) dpcm_set_fe_runtime(fe_substream); snd_pcm_limit_hw_rates(runtime); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return 0; unwind: dpcm_be_dai_startup_unwind(fe, fe_substream->stream); be_err: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return ret; } @@ -1603,7 +1629,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *fe = substream->private_data; int stream = substream->stream; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); /* shutdown the BEs */ dpcm_be_dai_shutdown(fe, substream->stream); @@ -1617,7 +1643,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return 0; } @@ -1641,6 +1667,10 @@ int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) continue; + /* do not free hw if this BE is used by other FE */ + if (be->dpcm[stream].users > 1) + continue; + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && @@ -1665,7 +1695,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) int err, stream = substream->stream; mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name); @@ -1680,7 +1710,7 @@ static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) err = dpcm_be_dai_hw_free(fe, stream); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); mutex_unlock(&fe->card->mutex); return 0; @@ -1773,7 +1803,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, int ret, stream = substream->stream; mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); memcpy(&fe->dpcm[substream->stream].hw_params, params, sizeof(struct snd_pcm_hw_params)); @@ -1796,7 +1826,7 @@ static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; out: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); mutex_unlock(&fe->card->mutex); return ret; } @@ -1910,7 +1940,7 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, } EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger); -static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *fe = substream->private_data; int stream = substream->stream, ret; @@ -1984,6 +2014,23 @@ out: return ret; } +static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream; + + /* if FE's runtime_update is already set, we're in race; + * process this trigger later at exit + */ + if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) { + fe->dpcm[stream].trigger_pending = cmd + 1; + return 0; /* delayed, assuming it's successful */ + } + + /* we're alone, let's trigger */ + return dpcm_fe_dai_do_trigger(substream, cmd); +} + int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; @@ -2027,7 +2074,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); /* there is no point preparing this FE if there are no BEs */ if (list_empty(&fe->dpcm[stream].be_clients)) { @@ -2054,7 +2101,7 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; out: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); mutex_unlock(&fe->card->mutex); return ret; @@ -2201,11 +2248,11 @@ static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream) { int ret; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); ret = dpcm_run_update_startup(fe, stream); if (ret < 0) dev_err(fe->dev, "ASoC: failed to startup some BEs\n"); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return ret; } @@ -2214,11 +2261,11 @@ static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) { int ret; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); ret = dpcm_run_update_shutdown(fe, stream); if (ret < 0) dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n"); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return ret; } @@ -2248,7 +2295,13 @@ int soc_dpcm_runtime_update(struct snd_soc_card *card) fe->dai_link->name); /* skip if FE doesn't have playback capability */ - if (!fe->cpu_dai->driver->playback.channels_min) + if (!fe->cpu_dai->driver->playback.channels_min + || !fe->codec_dai->driver->playback.channels_min) + goto capture; + + /* skip if FE isn't currently playing */ + if (!fe->cpu_dai->playback_active + || !fe->codec_dai->playback_active) goto capture; paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); @@ -2278,7 +2331,13 @@ int soc_dpcm_runtime_update(struct snd_soc_card *card) dpcm_path_put(&list); capture: /* skip if FE doesn't have capture capability */ - if (!fe->cpu_dai->driver->capture.channels_min) + if (!fe->cpu_dai->driver->capture.channels_min + || !fe->codec_dai->driver->capture.channels_min) + continue; + + /* skip if FE isn't currently capturing */ + if (!fe->cpu_dai->capture_active + || !fe->codec_dai->capture_active) continue; paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index 3b0fa12dbff..29a9957d335 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -228,7 +228,7 @@ static int tegra20_ac97_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver tegra20_ac97_dai = { .name = "tegra-ac97-pcm", - .ac97_control = 1, + .bus_control = true, .probe = tegra20_ac97_probe, .playback = { .stream_name = "PCM Playback", diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index a6898831fb9..4ebe3871e61 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -44,6 +44,7 @@ struct tegra_rt5640 { struct tegra_asoc_utils_data util_data; int gpio_hp_det; + enum of_gpio_flags gpio_hp_det_flags; }; static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, @@ -119,6 +120,8 @@ static int tegra_rt5640_asoc_init(struct snd_soc_pcm_runtime *rtd) if (gpio_is_valid(machine->gpio_hp_det)) { tegra_rt5640_hp_jack_gpio.gpio = machine->gpio_hp_det; + tegra_rt5640_hp_jack_gpio.invert = + !!(machine->gpio_hp_det_flags & OF_GPIO_ACTIVE_LOW); snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack, 1, &tegra_rt5640_hp_jack_gpio); @@ -180,7 +183,8 @@ static int tegra_rt5640_probe(struct platform_device *pdev) platform_set_drvdata(pdev, card); snd_soc_card_set_drvdata(card, machine); - machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + machine->gpio_hp_det = of_get_named_gpio_flags( + np, "nvidia,hp-det-gpios", 0, &machine->gpio_hp_det_flags); if (machine->gpio_hp_det == -EPROBE_DEFER) return -EPROBE_DEFER; diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c index 9edd68db9f4..f7135cdaa2c 100644 --- a/sound/soc/txx9/txx9aclc-ac97.c +++ b/sound/soc/txx9/txx9aclc-ac97.c @@ -152,7 +152,7 @@ static int txx9aclc_ac97_remove(struct snd_soc_dai *dai) } static struct snd_soc_dai_driver txx9aclc_ac97_dai = { - .ac97_control = 1, + .bus_control = true, .probe = txx9aclc_ac97_probe, .remove = txx9aclc_ac97_remove, .playback = { diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c index cd71fd889d8..00b7e2d0269 100644 --- a/sound/soc/txx9/txx9aclc.c +++ b/sound/soc/txx9/txx9aclc.c @@ -292,7 +292,7 @@ static int txx9aclc_pcm_new(struct snd_soc_pcm_runtime *rtd) struct snd_card *card = rtd->card->snd_card; struct snd_soc_dai *dai = rtd->cpu_dai; struct snd_pcm *pcm = rtd->pcm; - struct platform_device *pdev = to_platform_device(dai->platform->dev); + struct platform_device *pdev = to_platform_device(rtd->platform->dev); struct txx9aclc_soc_device *dev; struct resource *r; int i; diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c index b3b66aa98dc..9f2d045ee11 100644 --- a/sound/soc/ux500/mop500.c +++ b/sound/soc/ux500/mop500.c @@ -63,12 +63,8 @@ static void mop500_of_node_put(void) int i; for (i = 0; i < 2; i++) { - if (mop500_dai_links[i].cpu_of_node) - of_node_put((struct device_node *) - mop500_dai_links[i].cpu_of_node); - if (mop500_dai_links[i].codec_of_node) - of_node_put((struct device_node *) - mop500_dai_links[i].codec_of_node); + of_node_put(mop500_dai_links[i].cpu_of_node); + of_node_put(mop500_dai_links[i].codec_of_node); } } |